rubocop-minitest 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -3
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile +1 -1
  5. data/README.md +2 -2
  6. data/Rakefile +29 -0
  7. data/config/default.yml +95 -16
  8. data/docs/antora.yml +7 -0
  9. data/docs/modules/ROOT/nav.adoc +6 -0
  10. data/docs/modules/ROOT/pages/cops.adoc +37 -0
  11. data/docs/modules/ROOT/pages/cops_minitest.adoc +1014 -0
  12. data/docs/modules/ROOT/pages/index.adoc +5 -0
  13. data/docs/modules/ROOT/pages/installation.adoc +15 -0
  14. data/docs/modules/ROOT/pages/usage.adoc +32 -0
  15. data/{manual → legacy-docs}/cops.md +0 -0
  16. data/{manual → legacy-docs}/cops_minitest.md +0 -0
  17. data/{manual → legacy-docs}/index.md +0 -0
  18. data/{manual → legacy-docs}/installation.md +0 -0
  19. data/{manual → legacy-docs}/usage.md +0 -0
  20. data/lib/rubocop/cop/generator.rb +56 -0
  21. data/lib/rubocop/cop/minitest/assert_empty_literal.rb +15 -0
  22. data/lib/rubocop/cop/minitest/assert_in_delta.rb +27 -0
  23. data/lib/rubocop/cop/minitest/assert_kind_of.rb +25 -0
  24. data/lib/rubocop/cop/minitest/assert_output.rb +49 -0
  25. data/lib/rubocop/cop/minitest/assert_path_exists.rb +58 -0
  26. data/lib/rubocop/cop/minitest/assert_silent.rb +45 -0
  27. data/lib/rubocop/cop/minitest/assertion_in_lifecycle_hook.rb +43 -0
  28. data/lib/rubocop/cop/minitest/global_expectations.rb +2 -4
  29. data/lib/rubocop/cop/minitest/literal_as_actual_argument.rb +52 -0
  30. data/lib/rubocop/cop/minitest/multiple_assertions.rb +63 -0
  31. data/lib/rubocop/cop/minitest/refute_in_delta.rb +27 -0
  32. data/lib/rubocop/cop/minitest/refute_kind_of.rb +25 -0
  33. data/lib/rubocop/cop/minitest/refute_path_exists.rb +58 -0
  34. data/lib/rubocop/cop/minitest/test_method_name.rb +70 -0
  35. data/lib/rubocop/cop/minitest/unspecified_exception.rb +36 -0
  36. data/lib/rubocop/cop/minitest_cops.rb +15 -0
  37. data/lib/rubocop/cop/mixin/argument_range_helper.rb +10 -0
  38. data/lib/rubocop/cop/mixin/in_delta_mixin.rb +50 -0
  39. data/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb +84 -0
  40. data/lib/rubocop/minitest/version.rb +1 -1
  41. data/mkdocs.yml +2 -2
  42. data/relnotes/v0.10.0.md +21 -0
  43. data/rubocop-minitest.gemspec +3 -3
  44. data/tasks/cops_documentation.rake +83 -52
  45. data/tasks/cut_release.rake +16 -0
  46. metadata +35 -11
@@ -0,0 +1,5 @@
1
+ = RuboCop Minitest
2
+
3
+ A https://github.com/rubocop-hq/rubocop[RuboCop] extension focused on enforcing Minitest best practices and coding conventions.
4
+
5
+ It's based on the community-driven https://minitest.rubystyle.guide[Minitest style guide].
@@ -0,0 +1,15 @@
1
+ = Installation
2
+
3
+ Just install the `rubocop-minitest` gem
4
+
5
+ [source,sh]
6
+ ----
7
+ gem install rubocop-minitest
8
+ ----
9
+
10
+ or if you use bundler put this in your `Gemfile`
11
+
12
+ [source,ruby]
13
+ ----
14
+ gem 'rubocop-minitest'
15
+ ----
@@ -0,0 +1,32 @@
1
+ = Usage
2
+
3
+ You need to tell RuboCop to load the Minitest extension. There are three
4
+ ways to do this:
5
+
6
+ == RuboCop configuration file
7
+
8
+ Put this into your `.rubocop.yml`.
9
+
10
+ [source,yaml]
11
+ ----
12
+ require: rubocop-minitest
13
+ ----
14
+
15
+ Now you can run `rubocop` and it will automatically load the RuboCop Minitest
16
+ cops together with the standard cops.
17
+
18
+ == Command line
19
+
20
+ [source,sh]
21
+ ----
22
+ rubocop --require rubocop-minitest
23
+ ----
24
+
25
+ == Rake task
26
+
27
+ [source,ruby]
28
+ ----
29
+ RuboCop::RakeTask.new do |task|
30
+ task.requires << 'rubocop-minitest'
31
+ end
32
+ ----
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Source and test generator for new cops
6
+ #
7
+ # This generator will take a cop name and generate a source file
8
+ # and test file when given a valid qualified cop name.
9
+ class Generator
10
+ TEST_TEMPLATE = <<~TEST
11
+ # frozen_string_literal: true
12
+
13
+ require 'test_helper'
14
+
15
+ class %<cop_name>sTest < Minitest::Test
16
+ def test_registers_offense_when_using_bad_method
17
+ assert_offense(<<~RUBY)
18
+ bad_method
19
+ ^^^^^^^^^^ Use `#good_method` instead of `#bad_method`.
20
+ RUBY
21
+
22
+ assert_correction(<<~RUBY)
23
+ good_method
24
+ RUBY
25
+ end
26
+
27
+ def test_does_not_register_offense_when_using_good_method
28
+ assert_no_offenses(<<~RUBY)
29
+ good_method
30
+ RUBY
31
+ end
32
+ end
33
+ TEST
34
+
35
+ def write_test
36
+ write_unless_file_exists(test_path, generated_test)
37
+ end
38
+
39
+ private
40
+
41
+ def test_path
42
+ File.join(
43
+ 'test',
44
+ 'rubocop',
45
+ 'cop',
46
+ 'minitest',
47
+ "#{snake_case(badge.cop_name.to_s)}_test.rb"
48
+ )
49
+ end
50
+
51
+ def generated_test
52
+ generate(TEST_TEMPLATE)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -15,6 +15,8 @@ module RuboCop
15
15
  # assert_empty(object)
16
16
  #
17
17
  class AssertEmptyLiteral < Cop
18
+ include ArgumentRangeHelper
19
+
18
20
  MSG = 'Prefer using `assert_empty(%<arguments>s)` over ' \
19
21
  '`assert(%<literal>s, %<arguments>s)`.'
20
22
 
@@ -24,12 +26,25 @@ module RuboCop
24
26
 
25
27
  def on_send(node)
26
28
  assert_with_empty_literal(node) do |literal, matchers|
29
+ return unless literal.values.empty?
30
+
27
31
  args = matchers.map(&:source).join(', ')
28
32
 
29
33
  message = format(MSG, literal: literal.source, arguments: args)
30
34
  add_offense(node, message: message)
31
35
  end
32
36
  end
37
+
38
+ def autocorrect(node)
39
+ assert_with_empty_literal(node) do |_literal, matchers|
40
+ object = matchers.first
41
+
42
+ lambda do |corrector|
43
+ corrector.replace(node.loc.selector, 'assert_empty')
44
+ corrector.replace(first_and_second_arguments_range(node), object.source)
45
+ end
46
+ end
47
+ end
33
48
  end
34
49
  end
35
50
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop enforces the test to use `assert_in_delta`
7
+ # instead of using `assert_equal` to compare floats.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # assert_equal(0.2, actual)
12
+ # assert_equal(0.2, actual, 'message')
13
+ #
14
+ # # good
15
+ # assert_in_delta(0.2, actual)
16
+ # assert_in_delta(0.2, actual, 0.001, 'message')
17
+ #
18
+ class AssertInDelta < Cop
19
+ include InDeltaMixin
20
+
21
+ def_node_matcher :equal_floats_call, <<~PATTERN
22
+ (send nil? :assert_equal $_ $_ $...)
23
+ PATTERN
24
+ end
25
+ end
26
+ end
27
+ 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 test to use `assert_kind_of(Class, object)`
7
+ # over `assert(object.kind_of?(Class))`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # assert(object.kind_of?(Class))
12
+ # assert(object.kind_of?(Class), 'message')
13
+ #
14
+ # # good
15
+ # assert_kind_of(Class, object)
16
+ # assert_kind_of(Class, object, 'message')
17
+ #
18
+ class AssertKindOf < Cop
19
+ extend MinitestCopRule
20
+
21
+ define_rule :assert, target_method: :kind_of?, inverse: true
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop checks for opportunities to use `assert_output`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # $stdout = StringIO.new
11
+ # puts object.method
12
+ # $stdout.rewind
13
+ # assert_match expected, $stdout.read
14
+ #
15
+ # # good
16
+ # assert_output(expected) { puts object.method }
17
+ #
18
+ class AssertOutput < Cop
19
+ include MinitestExplorationHelpers
20
+
21
+ MSG = 'Use `assert_output` instead of mutating %<name>s.'
22
+ OUTPUT_GLOBAL_VARIABLES = %i[$stdout $stderr].freeze
23
+
24
+ def on_gvasgn(node)
25
+ test_case_node = find_test_case(node)
26
+ return unless test_case_node
27
+
28
+ gvar_name = node.children.first
29
+ return unless OUTPUT_GLOBAL_VARIABLES.include?(gvar_name)
30
+
31
+ assertions(test_case_node).each do |assertion|
32
+ add_offense(assertion, message: format(MSG, name: gvar_name)) if references_gvar?(assertion, gvar_name)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def find_test_case(node)
39
+ def_ancestor = node.each_ancestor(:def).first
40
+ def_ancestor if test_case?(def_ancestor)
41
+ end
42
+
43
+ def references_gvar?(assertion, gvar_name)
44
+ assertion.each_descendant(:gvar).any? { |d| d.children.first == gvar_name }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Minitest
6
+ # This cop enforces the test to use `assert_path_exists`
7
+ # instead of using `assert(File.exist?(path))`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # assert(File.exist?(path))
12
+ # assert(File.exist?(path), 'message')
13
+ #
14
+ # # good
15
+ # assert_path_exists(path)
16
+ # assert_path_exists(path, 'message')
17
+ #
18
+ class AssertPathExists < Cop
19
+ MSG = 'Prefer using `%<good_method>s` over `%<bad_method>s`.'
20
+
21
+ def_node_matcher :assert_file_exists, <<~PATTERN
22
+ (send nil? :assert
23
+ (send
24
+ (const _ :File) {:exist? :exists?} $_)
25
+ $...)
26
+ PATTERN
27
+
28
+ def on_send(node)
29
+ assert_file_exists(node) do |path, failure_message|
30
+ failure_message = failure_message.first
31
+ good_method = build_good_method(path, failure_message)
32
+ message = format(MSG, good_method: good_method, bad_method: node.source)
33
+
34
+ add_offense(node, message: message)
35
+ end
36
+ end
37
+
38
+ def autocorrect(node)
39
+ assert_file_exists(node) do |path, failure_message|
40
+ failure_message = failure_message.first
41
+
42
+ lambda do |corrector|
43
+ replacement = build_good_method(path, failure_message)
44
+ corrector.replace(node, replacement)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def build_good_method(path, message)
52
+ args = [path.source, message&.source].compact.join(', ')
53
+ "assert_path_exists(#{args})"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -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
@@ -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
@@ -72,11 +72,9 @@ module RuboCop
72
72
  receiver = node.receiver.source_range
73
73
 
74
74
  if BLOCK_MATCHERS.include?(node.method_name)
75
- corrector.insert_before(receiver, '_ { ')
76
- corrector.insert_after(receiver, ' }')
75
+ corrector.wrap(receiver, '_ { ', ' }')
77
76
  else
78
- corrector.insert_before(receiver, '_(')
79
- corrector.insert_after(receiver, ')')
77
+ corrector.wrap(receiver, '_(', ')')
80
78
  end
81
79
  end
82
80
  end
@@ -0,0 +1,52 @@
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
+
25
+ def on_send(node)
26
+ return unless node.method?(:assert_equal)
27
+
28
+ actual = node.arguments[1]
29
+ return unless actual
30
+
31
+ add_offense(node, location: all_arguments_range(node)) if actual.recursive_basic_literal?
32
+ end
33
+
34
+ def autocorrect(node)
35
+ expected, actual, message = *node.arguments
36
+
37
+ lambda do |corrector|
38
+ new_actual_source =
39
+ if actual.hash_type? && !actual.braces?
40
+ "{#{actual.source}}"
41
+ else
42
+ actual.source
43
+ end
44
+ arguments = [new_actual_source, expected.source, message&.source].compact.join(', ')
45
+
46
+ corrector.replace(node, "assert_equal(#{arguments})")
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end