rubocop-minitest 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +18 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +5 -1
  4. data/.rubocop.yml +5 -3
  5. data/.rubocop_todo.yml +8 -7
  6. data/CHANGELOG.md +94 -37
  7. data/CONTRIBUTING.md +3 -3
  8. data/Gemfile +2 -2
  9. data/LICENSE.txt +1 -1
  10. data/README.md +20 -4
  11. data/Rakefile +29 -0
  12. data/bin/console +2 -0
  13. data/config/default.yml +100 -16
  14. data/docs/antora.yml +7 -0
  15. data/docs/modules/ROOT/nav.adoc +6 -0
  16. data/docs/modules/ROOT/pages/cops.adoc +49 -0
  17. data/docs/modules/ROOT/pages/cops_minitest.adoc +1050 -0
  18. data/docs/modules/ROOT/pages/index.adoc +5 -0
  19. data/docs/modules/ROOT/pages/installation.adoc +15 -0
  20. data/docs/modules/ROOT/pages/usage.adoc +32 -0
  21. data/{manual → legacy-docs}/cops.md +0 -0
  22. data/{manual → legacy-docs}/cops_minitest.md +16 -16
  23. data/legacy-docs/index.md +1 -0
  24. data/{manual → legacy-docs}/installation.md +0 -0
  25. data/{manual → legacy-docs}/usage.md +0 -0
  26. data/lib/rubocop/cop/generator.rb +56 -0
  27. data/lib/rubocop/cop/minitest/assert_empty_literal.rb +23 -7
  28. data/lib/rubocop/cop/minitest/assert_in_delta.rb +29 -0
  29. data/lib/rubocop/cop/minitest/assert_kind_of.rb +25 -0
  30. data/lib/rubocop/cop/minitest/assert_nil.rb +1 -0
  31. data/lib/rubocop/cop/minitest/assert_output.rb +49 -0
  32. data/lib/rubocop/cop/minitest/assert_path_exists.rb +59 -0
  33. data/lib/rubocop/cop/minitest/assert_silent.rb +45 -0
  34. data/lib/rubocop/cop/minitest/assert_truthy.rb +1 -0
  35. data/lib/rubocop/cop/minitest/assert_with_expected_argument.rb +38 -0
  36. data/lib/rubocop/cop/minitest/assertion_in_lifecycle_hook.rb +43 -0
  37. data/lib/rubocop/cop/minitest/global_expectations.rb +4 -4
  38. data/lib/rubocop/cop/minitest/literal_as_actual_argument.rb +53 -0
  39. data/lib/rubocop/cop/minitest/multiple_assertions.rb +63 -0
  40. data/lib/rubocop/cop/minitest/refute_equal.rb +2 -1
  41. data/lib/rubocop/cop/minitest/refute_false.rb +1 -0
  42. data/lib/rubocop/cop/minitest/refute_in_delta.rb +29 -0
  43. data/lib/rubocop/cop/minitest/refute_kind_of.rb +25 -0
  44. data/lib/rubocop/cop/minitest/refute_nil.rb +1 -0
  45. data/lib/rubocop/cop/minitest/refute_path_exists.rb +59 -0
  46. data/lib/rubocop/cop/minitest/test_method_name.rb +79 -0
  47. data/lib/rubocop/cop/minitest/unspecified_exception.rb +36 -0
  48. data/lib/rubocop/cop/minitest_cops.rb +16 -0
  49. data/lib/rubocop/cop/mixin/argument_range_helper.rb +10 -0
  50. data/lib/rubocop/cop/mixin/in_delta_mixin.rb +50 -0
  51. data/lib/rubocop/cop/mixin/minitest_cop_rule.rb +2 -1
  52. data/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb +97 -0
  53. data/lib/rubocop/minitest/version.rb +8 -1
  54. data/mkdocs.yml +4 -4
  55. data/relnotes/v0.1.0.md +1 -1
  56. data/relnotes/v0.10.0.md +21 -0
  57. data/relnotes/v0.10.1.md +5 -0
  58. data/relnotes/v0.10.2.md +5 -0
  59. data/relnotes/v0.10.3.md +5 -0
  60. data/relnotes/v0.11.0.md +16 -0
  61. data/relnotes/v0.2.0.md +4 -4
  62. data/relnotes/v0.2.1.md +1 -1
  63. data/relnotes/v0.3.0.md +6 -6
  64. data/relnotes/v0.4.0.md +5 -5
  65. data/relnotes/v0.4.1.md +1 -1
  66. data/relnotes/v0.5.0.md +1 -1
  67. data/relnotes/v0.5.1.md +1 -1
  68. data/relnotes/v0.6.0.md +1 -1
  69. data/relnotes/v0.6.1.md +2 -2
  70. data/relnotes/v0.6.2.md +1 -1
  71. data/relnotes/v0.7.0.md +5 -5
  72. data/relnotes/v0.8.0.md +4 -4
  73. data/relnotes/v0.8.1.md +1 -1
  74. data/relnotes/v0.9.0.md +3 -3
  75. data/rubocop-minitest.gemspec +7 -7
  76. data/tasks/cops_documentation.rake +15 -264
  77. data/tasks/cut_release.rake +16 -0
  78. metadata +55 -20
  79. data/manual/index.md +0 -1
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop enforces the test to use `assert_silent { ... }`
7
+ # instead of using `assert_output('', '') { ... }`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # assert_output('', '') { puts object.do_something }
12
+ #
13
+ # # good
14
+ # assert_silent { puts object.do_something }
15
+ #
16
+ class AssertSilent < Cop
17
+ MSG = 'Prefer using `assert_silent` over `assert_output("", "")`.'
18
+
19
+ def_node_matcher :assert_silent_candidate?, <<~PATTERN
20
+ (block
21
+ (send nil? :assert_output
22
+ #empty_string?
23
+ #empty_string?)
24
+ ...)
25
+ PATTERN
26
+
27
+ def on_block(node)
28
+ add_offense(node.send_node) if assert_silent_candidate?(node)
29
+ end
30
+
31
+ def autocorrect(node)
32
+ lambda do |corrector|
33
+ corrector.replace(node, 'assert_silent')
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def empty_string?(node)
40
+ node.str_type? && node.value.empty?
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -20,6 +20,7 @@ module RuboCop
20
20
 
21
21
  MSG = 'Prefer using `assert(%<arguments>s)` over ' \
22
22
  '`assert_equal(true, %<arguments>s)`.'
23
+ RESTRICT_ON_SEND = %i[assert_equal].freeze
23
24
 
24
25
  def_node_matcher :assert_equal_with_truthy, <<~PATTERN
25
26
  (send nil? :assert_equal true $_ $...)
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop tries to detect when a user accidentally used
7
+ # `assert` when they meant to use `assert_equal`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # assert(3, my_list.length)
12
+ # assert(expected, actual)
13
+ #
14
+ # # good
15
+ # assert_equal(3, my_list.length)
16
+ # assert_equal(expected, actual)
17
+ # assert(foo, 'message')
18
+ #
19
+ class AssertWithExpectedArgument < Cop
20
+ MSG = 'Did you mean to use `assert_equal(%<arguments>s)`?'
21
+ RESTRICT_ON_SEND = %i[assert].freeze
22
+
23
+ def_node_matcher :assert_with_two_arguments?, <<~PATTERN
24
+ (send nil? :assert $_ $_)
25
+ PATTERN
26
+
27
+ def on_send(node)
28
+ assert_with_two_arguments?(node) do |_expected, message|
29
+ return if message.str_type? || message.dstr_type?
30
+
31
+ arguments = node.arguments.map(&:source).join(', ')
32
+ add_offense(node, message: format(MSG, arguments: arguments))
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop checks for usage of assertions in lifecycle hooks.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # class FooTest < Minitest::Test
11
+ # def setup
12
+ # assert_equal(foo, bar)
13
+ # end
14
+ # end
15
+ #
16
+ # # good
17
+ # class FooTest < Minitest::Test
18
+ # def test_something
19
+ # assert_equal(foo, bar)
20
+ # end
21
+ # end
22
+ #
23
+ class AssertionInLifecycleHook < Cop
24
+ include MinitestExplorationHelpers
25
+
26
+ MSG = 'Do not use `%<assertion>s` in `%<hook>s` hook.'
27
+
28
+ def on_class(class_node)
29
+ return unless test_class?(class_node)
30
+
31
+ lifecycle_hooks(class_node).each do |hook_node|
32
+ hook_node.each_descendant(:send) do |node|
33
+ if assertion?(node)
34
+ message = format(MSG, assertion: node.method_name, hook: hook_node.method_name)
35
+ add_offense(node, message: message)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -30,6 +30,8 @@ module RuboCop
30
30
 
31
31
  BLOCK_MATCHERS = %i[must_output must_raise must_be_silent must_throw].freeze
32
32
 
33
+ RESTRICT_ON_SEND = VALUE_MATCHERS + BLOCK_MATCHERS
34
+
33
35
  VALUE_MATCHERS_STR = VALUE_MATCHERS.map do |m|
34
36
  ":#{m}"
35
37
  end.join(' ').freeze
@@ -72,11 +74,9 @@ module RuboCop
72
74
  receiver = node.receiver.source_range
73
75
 
74
76
  if BLOCK_MATCHERS.include?(node.method_name)
75
- corrector.insert_before(receiver, '_ { ')
76
- corrector.insert_after(receiver, ' }')
77
+ corrector.wrap(receiver, '_ { ', ' }')
77
78
  else
78
- corrector.insert_before(receiver, '_(')
79
- corrector.insert_after(receiver, ')')
79
+ corrector.wrap(receiver, '_(', ')')
80
80
  end
81
81
  end
82
82
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop enforces correct order of expected and
7
+ # actual arguments for `assert_equal`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # assert_equal foo, 2
12
+ # assert_equal foo, [1, 2]
13
+ # assert_equal foo, [1, 2], 'message'
14
+ #
15
+ # # good
16
+ # assert_equal 2, foo
17
+ # assert_equal [1, 2], foo
18
+ # assert_equal [1, 2], foo, 'message'
19
+ #
20
+ class LiteralAsActualArgument < Cop
21
+ include ArgumentRangeHelper
22
+
23
+ MSG = 'Replace the literal with the first argument.'
24
+ RESTRICT_ON_SEND = %i[assert_equal].freeze
25
+
26
+ def on_send(node)
27
+ return unless node.method?(:assert_equal)
28
+
29
+ actual = node.arguments[1]
30
+ return unless actual
31
+
32
+ add_offense(node, location: all_arguments_range(node)) if actual.recursive_basic_literal?
33
+ end
34
+
35
+ def autocorrect(node)
36
+ expected, actual, message = *node.arguments
37
+
38
+ lambda do |corrector|
39
+ new_actual_source =
40
+ if actual.hash_type? && !actual.braces?
41
+ "{#{actual.source}}"
42
+ else
43
+ actual.source
44
+ end
45
+ arguments = [new_actual_source, expected.source, message&.source].compact.join(', ')
46
+
47
+ corrector.replace(node, "assert_equal(#{arguments})")
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop checks if test cases contain too many assertion calls.
7
+ # The maximum allowed assertion calls is configurable.
8
+ #
9
+ # @example Max: 1
10
+ # # bad
11
+ # class FooTest < Minitest::Test
12
+ # def test_asserts_twice
13
+ # assert_equal(42, do_something)
14
+ # assert_empty(array)
15
+ # end
16
+ # end
17
+ #
18
+ # # good
19
+ # class FooTest < Minitest::Test
20
+ # def test_asserts_once
21
+ # assert_equal(42, do_something)
22
+ # end
23
+ #
24
+ # def test_another_asserts_once
25
+ # assert_empty(array)
26
+ # end
27
+ # end
28
+ #
29
+ class MultipleAssertions < Cop
30
+ include ConfigurableMax
31
+ include MinitestExplorationHelpers
32
+
33
+ MSG = 'Test case has too many assertions [%<total>d/%<max>d].'
34
+
35
+ def on_class(class_node)
36
+ return unless test_class?(class_node)
37
+
38
+ test_cases(class_node).each do |node|
39
+ assertions_count = assertions_count(node)
40
+
41
+ next unless assertions_count > max_assertions
42
+
43
+ self.max = assertions_count
44
+
45
+ message = format(MSG, total: assertions_count, max: max_assertions)
46
+ add_offense(node, location: :name, message: message)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def assertions_count(node)
53
+ base = assertion?(node) ? 1 : 0
54
+ base + node.each_child_node.sum { |c| assertions_count(c) }
55
+ end
56
+
57
+ def max_assertions
58
+ Integer(cop_config.fetch('Max', 3))
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Minitest
6
6
  # This cop enforces the use of `refute_equal(expected, object)`
7
- # over `assert_equal(expected != actual)` or `assert(! expected == actual)`.
7
+ # over `assert(expected != actual)` or `assert(! expected == actual)`.
8
8
  #
9
9
  # @example
10
10
  # # bad
@@ -19,6 +19,7 @@ module RuboCop
19
19
 
20
20
  MSG = 'Prefer using `refute_equal(%<preferred>s)` over ' \
21
21
  '`assert(%<over>s)`.'
22
+ RESTRICT_ON_SEND = %i[assert].freeze
22
23
 
23
24
  def_node_matcher :assert_not_equal, <<~PATTERN
24
25
  (send nil? :assert ${(send $_ :!= $_) (send (send $_ :! ) :== $_) } $... )
@@ -25,6 +25,7 @@ module RuboCop
25
25
  '`assert_equal(false, %<arguments>s)`.'
26
26
  MSG_FOR_ASSERT = 'Prefer using `refute(%<arguments>s)` over ' \
27
27
  '`assert(!%<arguments>s)`.'
28
+ RESTRICT_ON_SEND = %i[assert_equal assert].freeze
28
29
 
29
30
  def_node_matcher :assert_equal_with_false, <<~PATTERN
30
31
  (send nil? :assert_equal false $_ $...)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop enforces the test to use `refute_in_delta`
7
+ # instead of using `refute_equal` to compare floats.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # refute_equal(0.2, actual)
12
+ # refute_equal(0.2, actual, 'message')
13
+ #
14
+ # # good
15
+ # refute_in_delta(0.2, actual)
16
+ # refute_in_delta(0.2, actual, 0.001, 'message')
17
+ #
18
+ class RefuteInDelta < Cop
19
+ include InDeltaMixin
20
+
21
+ RESTRICT_ON_SEND = %i[refute_equal].freeze
22
+
23
+ def_node_matcher :equal_floats_call, <<~PATTERN
24
+ (send nil? :refute_equal $_ $_ $...)
25
+ PATTERN
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop enforces the use of `refute_kind_of(Class, object)`
7
+ # over `refute(object.kind_of?(Class))`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # refute(object.kind_of?(Class))
12
+ # refute(object.kind_of?(Class), 'message')
13
+ #
14
+ # # good
15
+ # refute_kind_of(Class, object)
16
+ # refute_kind_of(Class, object, 'message')
17
+ #
18
+ class RefuteKindOf < Cop
19
+ extend MinitestCopRule
20
+
21
+ define_rule :refute, target_method: :kind_of?, inverse: true
22
+ end
23
+ end
24
+ end
25
+ end
@@ -20,6 +20,7 @@ module RuboCop
20
20
 
21
21
  MSG = 'Prefer using `refute_nil(%<arguments>s)` over ' \
22
22
  '`refute_equal(nil, %<arguments>s)`.'
23
+ RESTRICT_ON_SEND = %i[refute_equal].freeze
23
24
 
24
25
  def_node_matcher :refute_equal_with_nil, <<~PATTERN
25
26
  (send nil? :refute_equal nil $_ $...)
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop enforces the test to use `refute_path_exists`
7
+ # instead of using `refute(File.exist?(path))`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # refute(File.exist?(path))
12
+ # refute(File.exist?(path), 'message')
13
+ #
14
+ # # good
15
+ # refute_path_exists(path)
16
+ # refute_path_exists(path, 'message')
17
+ #
18
+ class RefutePathExists < Cop
19
+ MSG = 'Prefer using `%<good_method>s` over `%<bad_method>s`.'
20
+ RESTRICT_ON_SEND = %i[refute].freeze
21
+
22
+ def_node_matcher :refute_file_exists, <<~PATTERN
23
+ (send nil? :refute
24
+ (send
25
+ (const _ :File) {:exist? :exists?} $_)
26
+ $...)
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ refute_file_exists(node) do |path, failure_message|
31
+ failure_message = failure_message.first
32
+ good_method = build_good_method(path, failure_message)
33
+ message = format(MSG, good_method: good_method, bad_method: node.source)
34
+
35
+ add_offense(node, message: message)
36
+ end
37
+ end
38
+
39
+ def autocorrect(node)
40
+ refute_file_exists(node) do |path, failure_message|
41
+ failure_message = failure_message.first
42
+
43
+ lambda do |corrector|
44
+ replacement = build_good_method(path, failure_message)
45
+ corrector.replace(node, replacement)
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def build_good_method(path, message)
53
+ args = [path.source, message&.source].compact.join(', ')
54
+ "refute_path_exists(#{args})"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop enforces that test method names start with `test_` prefix.
7
+ # It aims to prevent tests that aren't executed by forgetting to start test method name with `test_`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # class FooTest < Minitest::Test
12
+ # def does_something
13
+ # assert_equal 42, do_something
14
+ # end
15
+ # end
16
+ #
17
+ # # good
18
+ # class FooTest < Minitest::Test
19
+ # def test_does_something
20
+ # assert_equal 42, do_something
21
+ # end
22
+ # end
23
+ #
24
+ # # good
25
+ # class FooTest < Minitest::Test
26
+ # def helper_method(argument)
27
+ # end
28
+ # end
29
+ #
30
+ class TestMethodName < Cop
31
+ include MinitestExplorationHelpers
32
+ include DefNode
33
+
34
+ MSG = 'Test method name should start with `test_` prefix.'
35
+
36
+ def on_class(class_node)
37
+ return unless test_class?(class_node)
38
+
39
+ class_elements(class_node).each do |node|
40
+ add_offense(node, location: :name) if offense?(node)
41
+ end
42
+ end
43
+
44
+ def autocorrect(node)
45
+ lambda do |corrector|
46
+ corrector.replace(node.loc.name, "test_#{node.method_name}")
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def class_elements(class_node)
53
+ class_def = class_node.body
54
+ return [] unless class_def
55
+
56
+ if class_def.def_type?
57
+ [class_def]
58
+ else
59
+ class_def.each_child_node(:def).to_a
60
+ end
61
+ end
62
+
63
+ def offense?(node)
64
+ return false if assertions(node).none?
65
+
66
+ public?(node) && node.arguments.empty? && !test_method_name?(node) && !lifecycle_hook_method?(node)
67
+ end
68
+
69
+ def public?(node)
70
+ !non_public?(node)
71
+ end
72
+
73
+ def test_method_name?(node)
74
+ node.method_name.to_s.start_with?('test_')
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end