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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -3
- data/CHANGELOG.md +22 -0
- data/Gemfile +1 -1
- data/README.md +2 -2
- data/Rakefile +29 -0
- data/config/default.yml +95 -16
- data/docs/antora.yml +7 -0
- data/docs/modules/ROOT/nav.adoc +6 -0
- data/docs/modules/ROOT/pages/cops.adoc +37 -0
- data/docs/modules/ROOT/pages/cops_minitest.adoc +1014 -0
- data/docs/modules/ROOT/pages/index.adoc +5 -0
- data/docs/modules/ROOT/pages/installation.adoc +15 -0
- data/docs/modules/ROOT/pages/usage.adoc +32 -0
- data/{manual → legacy-docs}/cops.md +0 -0
- data/{manual → legacy-docs}/cops_minitest.md +0 -0
- data/{manual → legacy-docs}/index.md +0 -0
- data/{manual → legacy-docs}/installation.md +0 -0
- data/{manual → legacy-docs}/usage.md +0 -0
- data/lib/rubocop/cop/generator.rb +56 -0
- data/lib/rubocop/cop/minitest/assert_empty_literal.rb +15 -0
- data/lib/rubocop/cop/minitest/assert_in_delta.rb +27 -0
- data/lib/rubocop/cop/minitest/assert_kind_of.rb +25 -0
- data/lib/rubocop/cop/minitest/assert_output.rb +49 -0
- data/lib/rubocop/cop/minitest/assert_path_exists.rb +58 -0
- data/lib/rubocop/cop/minitest/assert_silent.rb +45 -0
- data/lib/rubocop/cop/minitest/assertion_in_lifecycle_hook.rb +43 -0
- data/lib/rubocop/cop/minitest/global_expectations.rb +2 -4
- data/lib/rubocop/cop/minitest/literal_as_actual_argument.rb +52 -0
- data/lib/rubocop/cop/minitest/multiple_assertions.rb +63 -0
- data/lib/rubocop/cop/minitest/refute_in_delta.rb +27 -0
- data/lib/rubocop/cop/minitest/refute_kind_of.rb +25 -0
- data/lib/rubocop/cop/minitest/refute_path_exists.rb +58 -0
- data/lib/rubocop/cop/minitest/test_method_name.rb +70 -0
- data/lib/rubocop/cop/minitest/unspecified_exception.rb +36 -0
- data/lib/rubocop/cop/minitest_cops.rb +15 -0
- data/lib/rubocop/cop/mixin/argument_range_helper.rb +10 -0
- data/lib/rubocop/cop/mixin/in_delta_mixin.rb +50 -0
- data/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb +84 -0
- data/lib/rubocop/minitest/version.rb +1 -1
- data/mkdocs.yml +2 -2
- data/relnotes/v0.10.0.md +21 -0
- data/rubocop-minitest.gemspec +3 -3
- data/tasks/cops_documentation.rake +83 -52
- data/tasks/cut_release.rake +16 -0
- metadata +35 -11
@@ -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.
|
76
|
-
corrector.insert_after(receiver, ' }')
|
75
|
+
corrector.wrap(receiver, '_ { ', ' }')
|
77
76
|
else
|
78
|
-
corrector.
|
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
|