diff --git a/changelog/new_add_new_minitest_empty_line_before_assertion_method_cops.md b/changelog/new_add_new_minitest_empty_line_before_assertion_method_cops.md new file mode 100644 index 00000000..e4d1a7cc --- /dev/null +++ b/changelog/new_add_new_minitest_empty_line_before_assertion_method_cops.md @@ -0,0 +1 @@ +* [#157](https://github.com/rubocop/rubocop-minitest/issues/157): Add new `Minitest/EmptyLinesBeforeAssertionMethods` cop. ([@koic][]) diff --git a/config/default.yml b/config/default.yml index 853fa4e2..ee517ecd 100644 --- a/config/default.yml +++ b/config/default.yml @@ -99,6 +99,11 @@ Minitest/AssertWithExpectedArgument: Safe: false VersionAdded: '0.11' +Minitest/EmptyLinesBeforeAssertionMethods: + Description: 'Add empty line before assertion methods.' + Enabled: pending + VersionAdded: '<>' + Minitest/GlobalExpectations: Description: 'This cop checks for deprecated global expectations.' StyleGuide: 'https://minitest.rubystyle.guide#global-expectations' diff --git a/lib/rubocop/cop/minitest/empty_lines_before_assertion_methods.rb b/lib/rubocop/cop/minitest/empty_lines_before_assertion_methods.rb new file mode 100644 index 00000000..f1dd2059 --- /dev/null +++ b/lib/rubocop/cop/minitest/empty_lines_before_assertion_methods.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Minitest + # This cop enforces empty line before assertion methods. + # + # @example + # + # # bad + # do_something + # assert_equal(expected, actual) + # + # # good + # do_something + # + # assert_equal(expected, actual) + # + class EmptyLinesBeforeAssertionMethods < Base + include MinitestExplorationHelpers + extend AutoCorrector + + MSG = 'Add empty line before assertion.' + + def on_send(node) + return unless assertion_method?(node) + return unless (previous_line_node = node.left_sibling) + return if accept_previous_line?(previous_line_node, node) + + previous_line_node = previous_line_node.arguments.last if use_heredoc_argument?(previous_line_node) + return unless no_empty_line?(previous_line_node, node) + + register_offense(node, previous_line_node) + end + + private + + def accept_previous_line?(previous_line_node, node) + return true if previous_line_node.args_type? || node.parent.basic_conditional? + + previous_line_node.send_type? && assertion_method?(previous_line_node) + end + + def use_heredoc_argument?(node) + node.respond_to?(:arguments) && heredoc?(node.arguments.last) + end + + def heredoc?(last_argument) + last_argument.respond_to?(:heredoc?) && last_argument.heredoc? + end + + def no_empty_line?(previous_line_node, node) + previous_line = if heredoc?(previous_line_node) + previous_line_node.loc.heredoc_end.line + else + previous_line_node.loc.last_line + end + + previous_line + 1 == node.loc.line + end + + def register_offense(node, previous_line_node) + add_offense(node) do |corrector| + range = if heredoc?(previous_line_node) + previous_line_node.loc.heredoc_end + else + previous_line_node + end + + corrector.insert_after(range, "\n") + end + end + end + end + end +end diff --git a/lib/rubocop/cop/minitest_cops.rb b/lib/rubocop/cop/minitest_cops.rb index bcaba86b..a063ea29 100644 --- a/lib/rubocop/cop/minitest_cops.rb +++ b/lib/rubocop/cop/minitest_cops.rb @@ -21,6 +21,7 @@ require_relative 'minitest/assert_respond_to' require_relative 'minitest/assert_silent' require_relative 'minitest/assert_truthy' +require_relative 'minitest/empty_lines_before_assertion_methods' require_relative 'minitest/global_expectations' require_relative 'minitest/literal_as_actual_argument' require_relative 'minitest/multiple_assertions' diff --git a/test/project_test.rb b/test/project_test.rb index 6bfd301b..53379bec 100644 --- a/test/project_test.rb +++ b/test/project_test.rb @@ -52,8 +52,8 @@ def test_entry_has_an_issue_number_prefixed_with_sharp def test_entry_has_a_valid_url @issues.each do |issue| number = issue[:number].gsub(/\D/, '') - pattern = - %r{^https://github\.com/rubocop/rubocop-minitest/(?:issues|pull)/#{number}$} + pattern = %r{^https://github\.com/rubocop/rubocop-minitest/(?:issues|pull)/#{number}$} + assert_match(pattern, issue[:url]) end end diff --git a/test/rubocop/cop/minitest/empty_lines_before_assertion_method_tests.rb b/test/rubocop/cop/minitest/empty_lines_before_assertion_method_tests.rb new file mode 100644 index 00000000..f73dde35 --- /dev/null +++ b/test/rubocop/cop/minitest/empty_lines_before_assertion_method_tests.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +require 'test_helper' + +class EmptyLinesBeforeAssertionMethodsTest < Minitest::Test + def test_registers_offense_when_using_method_call_before_assertion_method + assert_offense(<<~RUBY) + def test_do_something + do_something + assert_equal(expected, actual) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add empty line before assertion. + end + RUBY + + assert_correction(<<~RUBY) + def test_do_something + do_something + + assert_equal(expected, actual) + end + RUBY + end + + def test_registers_offense_when_using_method_call_with_line_breaked_args_before_assertion_method + assert_offense(<<~RUBY) + def test_do_something + do_something( + foo, + bar + ) + assert_equal(expected, actual) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add empty line before assertion. + end + RUBY + + assert_correction(<<~RUBY) + def test_do_something + do_something( + foo, + bar + ) + + assert_equal(expected, actual) + end + RUBY + end + + def test_registers_offense_when_using_heredoc_before_assertion_method + assert_offense(<<~RUBY) + def test_do_something + <<~EOS + text + EOS + assert_equal(expected, actual) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add empty line before assertion. + end + RUBY + + assert_correction(<<~RUBY) + def test_do_something + <<~EOS + text + EOS + + assert_equal(expected, actual) + end + RUBY + end + + def test_registers_offense_when_using_method_call_with_heredoc_arg_before_assertion_method + assert_offense(<<~RUBY) + def test_do_something + do_something(<<~EOS) + text + EOS + assert_equal(expected, actual) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add empty line before assertion. + end + RUBY + + assert_correction(<<~RUBY) + def test_do_something + do_something(<<~EOS) + text + EOS + + assert_equal(expected, actual) + end + RUBY + end + + def test_does_not_register_offense_when_using_empty_line_before_assertion_method + assert_no_offenses(<<~RUBY) + def test_do_something + do_something + + assert_equal(expected, actual) + end + RUBY + end + + def test_does_not_register_offense_when_using_assertion_method_at_top_level + assert_no_offenses(<<~RUBY) + assert_equal(expected, actual) + RUBY + end + + def test_does_not_register_offense_when_using_assertion_method_at_top_of_block_body + assert_no_offenses(<<~RUBY) + def test_do_something + do_something do + assert_equal(expected, actual) + end + end + RUBY + end + + def test_does_not_register_offense_when_using_assertion_method_at_top_of_if_body + assert_no_offenses(<<~RUBY) + def test_do_something + if condition + assert_equal(expected, actual) + end + end + RUBY + end + + def test_does_not_register_offense_when_using_assertion_method_at_top_of_while_body + assert_no_offenses(<<~RUBY) + def test_do_something + while condition + assert_equal(expected, actual) + end + end + RUBY + end + + def test_does_not_register_offense_when_using_assertion_method_at_top_of_until_body + assert_no_offenses(<<~RUBY) + def test_do_something + until condition + assert_equal(expected, actual) + end + end + RUBY + end + + def test_does_not_register_offense_when_using_assertion_methods_are_continuous_without_empty_line + assert_no_offenses(<<~RUBY) + def test_do_something + assert_not foo + assert bar + end + RUBY + end +end