rubocop-rspec 1.15.1 → 1.16.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -1
- data/Gemfile +0 -1
- data/config/default.yml +105 -0
- data/lib/rubocop-rspec.rb +27 -0
- data/lib/rubocop/cop/rspec/align_left_let_brace.rb +51 -0
- data/lib/rubocop/cop/rspec/align_right_let_brace.rb +51 -0
- data/lib/rubocop/cop/rspec/any_instance.rb +1 -1
- data/lib/rubocop/cop/rspec/cop.rb +4 -2
- data/lib/rubocop/cop/rspec/expect_in_hook.rb +61 -0
- data/lib/rubocop/cop/rspec/factory_girl/dynamic_attribute_defined_statically.rb +84 -0
- data/lib/rubocop/cop/rspec/hook_argument.rb +11 -5
- data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +42 -0
- data/lib/rubocop/cop/rspec/let_before_examples.rb +66 -0
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +3 -5
- data/lib/rubocop/cop/rspec/multiple_subjects.rb +80 -0
- data/lib/rubocop/cop/rspec/named_subject.rb +5 -6
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +337 -0
- data/lib/rubocop/cop/rspec/return_from_stub.rb +83 -0
- data/lib/rubocop/cop/rspec/void_expect.rb +52 -0
- data/lib/rubocop/rspec/align_let_brace.rb +64 -0
- data/lib/rubocop/rspec/config_formatter.rb +7 -4
- data/lib/rubocop/rspec/description_extractor.rb +2 -2
- data/lib/rubocop/rspec/example_group.rb +25 -2
- data/lib/rubocop/rspec/factory_girl.rb +7 -0
- data/lib/rubocop/rspec/language.rb +6 -1
- data/lib/rubocop/rspec/version.rb +1 -1
- data/rubocop-rspec.gemspec +1 -4
- data/spec/project/default_config_spec.rb +8 -4
- data/spec/rubocop/cop/rspec/align_left_let_brace_spec.rb +62 -0
- data/spec/rubocop/cop/rspec/align_right_let_brace_spec.rb +62 -0
- data/spec/rubocop/cop/rspec/any_instance_spec.rb +3 -3
- data/spec/rubocop/cop/rspec/around_block_spec.rb +11 -11
- data/spec/rubocop/cop/rspec/be_eql_spec.rb +7 -7
- data/spec/rubocop/cop/rspec/before_after_all_spec.rb +4 -4
- data/spec/rubocop/cop/rspec/cop_spec.rb +7 -7
- data/spec/rubocop/cop/rspec/describe_class_spec.rb +18 -18
- data/spec/rubocop/cop/rspec/describe_method_spec.rb +4 -4
- data/spec/rubocop/cop/rspec/describe_symbol_spec.rb +6 -6
- data/spec/rubocop/cop/rspec/described_class_spec.rb +18 -18
- data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +5 -5
- data/spec/rubocop/cop/rspec/empty_line_after_final_let_spec.rb +9 -9
- data/spec/rubocop/cop/rspec/empty_line_after_subject_spec.rb +5 -5
- data/spec/rubocop/cop/rspec/example_length_spec.rb +6 -6
- data/spec/rubocop/cop/rspec/example_wording_spec.rb +10 -10
- data/spec/rubocop/cop/rspec/expect_actual_spec.rb +10 -10
- data/spec/rubocop/cop/rspec/expect_in_hook_spec.rb +79 -0
- data/spec/rubocop/cop/rspec/expect_output_spec.rb +7 -7
- data/spec/rubocop/cop/rspec/factory_girl/dynamic_attribute_defined_statically_spec.rb +87 -0
- data/spec/rubocop/cop/rspec/file_path_spec.rb +29 -29
- data/spec/rubocop/cop/rspec/focus_spec.rb +6 -6
- data/spec/rubocop/cop/rspec/hook_argument_spec.rb +35 -23
- data/spec/rubocop/cop/rspec/implicit_expect_spec.rb +10 -10
- data/spec/rubocop/cop/rspec/instance_spy_spec.rb +4 -4
- data/spec/rubocop/cop/rspec/instance_variable_spec.rb +7 -7
- data/spec/rubocop/cop/rspec/invalid_predicate_matcher_spec.rb +37 -0
- data/spec/rubocop/cop/rspec/it_behaves_like_spec.rb +4 -4
- data/spec/rubocop/cop/rspec/iterated_expectation_spec.rb +8 -8
- data/spec/rubocop/cop/rspec/leading_subject_spec.rb +5 -5
- data/spec/rubocop/cop/rspec/let_before_examples_spec.rb +83 -0
- data/spec/rubocop/cop/rspec/let_setup_spec.rb +4 -4
- data/spec/rubocop/cop/rspec/message_chain_spec.rb +2 -2
- data/spec/rubocop/cop/rspec/message_expectation_spec.rb +4 -4
- data/spec/rubocop/cop/rspec/message_spies_spec.rb +18 -18
- data/spec/rubocop/cop/rspec/multiple_describes_spec.rb +3 -3
- data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +45 -9
- data/spec/rubocop/cop/rspec/multiple_subjects_spec.rb +96 -0
- data/spec/rubocop/cop/rspec/named_subject_spec.rb +4 -4
- data/spec/rubocop/cop/rspec/nested_groups_spec.rb +3 -3
- data/spec/rubocop/cop/rspec/not_to_not_spec.rb +4 -4
- data/spec/rubocop/cop/rspec/overwriting_setup_spec.rb +4 -4
- data/spec/rubocop/cop/rspec/predicate_matcher_spec.rb +335 -0
- data/spec/rubocop/cop/rspec/repeated_description_spec.rb +5 -5
- data/spec/rubocop/cop/rspec/repeated_example_spec.rb +5 -5
- data/spec/rubocop/cop/rspec/return_from_stub_spec.rb +85 -0
- data/spec/rubocop/cop/rspec/scattered_let_spec.rb +2 -2
- data/spec/rubocop/cop/rspec/scattered_setup_spec.rb +8 -8
- data/spec/rubocop/cop/rspec/shared_context_spec.rb +10 -10
- data/spec/rubocop/cop/rspec/single_argument_message_chain_spec.rb +10 -10
- data/spec/rubocop/cop/rspec/subject_stub_spec.rb +9 -9
- data/spec/rubocop/cop/rspec/verified_doubles_spec.rb +7 -7
- data/spec/rubocop/cop/rspec/void_expect_spec.rb +47 -0
- data/spec/rubocop/rspec/config_formatter_spec.rb +2 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/expect_offense.rb +17 -0
- metadata +39 -51
- data/spec/expect_violation/expectation_spec.rb +0 -85
- data/spec/support/expect_violation.rb +0 -170
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that right braces for adjacent single line lets are aligned.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# let(:foobar) { blahblah }
|
12
|
+
# let(:baz) { bar }
|
13
|
+
# let(:a) { b }
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# let(:foobar) { blahblah }
|
17
|
+
# let(:baz) { bar }
|
18
|
+
# let(:a) { b }
|
19
|
+
#
|
20
|
+
class AlignRightLetBrace < Cop
|
21
|
+
MSG = 'Align right let brace'.freeze
|
22
|
+
|
23
|
+
def self.autocorrect_incompatible_with
|
24
|
+
[Layout::ExtraSpacing]
|
25
|
+
end
|
26
|
+
|
27
|
+
def investigate(_)
|
28
|
+
token_aligner.offending_tokens.each do |let|
|
29
|
+
add_offense(let, :end)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def autocorrect(let)
|
34
|
+
lambda do |corrector|
|
35
|
+
corrector.insert_before(
|
36
|
+
let.loc.end,
|
37
|
+
token_aligner.indent_for(let)
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def token_aligner
|
45
|
+
@token_aligner ||=
|
46
|
+
RuboCop::RSpec::AlignLetBrace.new(processed_source.ast, :end)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -21,7 +21,7 @@ module RuboCop
|
|
21
21
|
# end
|
22
22
|
# end
|
23
23
|
class AnyInstance < Cop
|
24
|
-
MSG = 'Avoid stubbing using
|
24
|
+
MSG = 'Avoid stubbing using `%<method>s`.'.freeze
|
25
25
|
|
26
26
|
def_node_matcher :disallowed_stub, <<-PATTERN
|
27
27
|
(send _ ${:any_instance :allow_any_instance_of :expect_any_instance_of} ...)
|
@@ -5,9 +5,11 @@ module RuboCop
|
|
5
5
|
# Clone of the the normal RuboCop::Cop::Cop class so we can rewrite
|
6
6
|
# the inherited method without breaking functionality
|
7
7
|
class WorkaroundCop
|
8
|
-
#
|
8
|
+
# Remove the cop inherited method to be a noop. Our RSpec::Cop
|
9
9
|
# class will invoke the inherited hook instead
|
10
|
-
|
10
|
+
class << self
|
11
|
+
remove_method :inherited
|
12
|
+
end
|
11
13
|
|
12
14
|
# Special case `Module#<` so that the rspec support rubocop exports
|
13
15
|
# is compatible with our subclass
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Do not use `expect` in hooks such as `before`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# before do
|
11
|
+
# expect(something).to eq 'foo'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # bad
|
15
|
+
# after do
|
16
|
+
# expect_any_instance_of(Something).to receive(:foo)
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# it do
|
21
|
+
# expect(something).to eq 'foo'
|
22
|
+
# end
|
23
|
+
class ExpectInHook < Cop
|
24
|
+
MSG = 'Do not use `%<expect>s` in `%<hook>s` hook'.freeze
|
25
|
+
HOOKS = Hooks::ALL.node_pattern_union.freeze
|
26
|
+
|
27
|
+
def_node_matcher :hook, <<-PATTERN
|
28
|
+
(block (send _ $#{HOOKS} ...) _ $!nil)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def_node_search :expect, <<-PATTERN
|
32
|
+
{
|
33
|
+
#{Expectations::ALL.send_pattern}
|
34
|
+
#{Expectations::ALL.block_pattern}
|
35
|
+
}
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def on_block(node)
|
39
|
+
hook(node) do |hook_name, body|
|
40
|
+
expect(body) do |expect|
|
41
|
+
method = send_node(expect)
|
42
|
+
add_offense(method, :selector,
|
43
|
+
message(method, hook_name))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def message(expect, hook)
|
51
|
+
format(MSG, expect: expect.method_name, hook: hook)
|
52
|
+
end
|
53
|
+
|
54
|
+
def send_node(node)
|
55
|
+
return node if node.send_type?
|
56
|
+
node.children.first
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module FactoryGirl
|
7
|
+
# Prefer declaring dynamic attribute values in a block.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# kind [:active, :rejected].sample
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# kind { [:active, :rejected].sample }
|
15
|
+
#
|
16
|
+
# # bad
|
17
|
+
# closed_at 1.day.from_now
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# closed_at { 1.day.from_now }
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# kind :static
|
24
|
+
#
|
25
|
+
# # good
|
26
|
+
# comments_count 0
|
27
|
+
#
|
28
|
+
# # good
|
29
|
+
# type User::MAGIC
|
30
|
+
class DynamicAttributeDefinedStatically < Cop
|
31
|
+
MSG = 'Use a block to set a dynamic value to an attribute.'.freeze
|
32
|
+
|
33
|
+
def_node_matcher :dynamic_defined_statically?, <<-PATTERN
|
34
|
+
(send nil _ (send ... ))
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
def_node_search :factory_attributes, <<-PATTERN
|
38
|
+
(block (send nil {:factory :trait} ...) _ { (begin $...) $(send ...) } )
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def on_block(node)
|
42
|
+
return if node.method_name == :trait
|
43
|
+
factory_attributes(node).to_a.flatten.each do |attribute|
|
44
|
+
if dynamic_defined_statically?(attribute)
|
45
|
+
add_offense(attribute, :expression)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def autocorrect(node)
|
51
|
+
if method_uses_parens?(node.location)
|
52
|
+
autocorrect_replacing_parens(node)
|
53
|
+
else
|
54
|
+
autocorrect_without_parens(node)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def method_uses_parens?(location)
|
61
|
+
return false unless location.begin && location.end
|
62
|
+
location.begin.source == '(' && location.end.source == ')'
|
63
|
+
end
|
64
|
+
|
65
|
+
def autocorrect_replacing_parens(node)
|
66
|
+
lambda do |corrector|
|
67
|
+
corrector.replace(node.location.begin, ' { ')
|
68
|
+
corrector.replace(node.location.end, ' }')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def autocorrect_without_parens(node)
|
73
|
+
lambda do |corrector|
|
74
|
+
arguments = node.descendants.first
|
75
|
+
expression = arguments.location.expression
|
76
|
+
corrector.insert_before(expression, '{ ')
|
77
|
+
corrector.insert_after(expression, ' }')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -66,10 +66,10 @@ module RuboCop
|
|
66
66
|
HOOKS = Hooks::ALL.node_pattern_union.freeze
|
67
67
|
|
68
68
|
def_node_matcher :scoped_hook, <<-PATTERN
|
69
|
-
(block $(send
|
69
|
+
(block $(send _ #{HOOKS} (sym ${:each :example})) ...)
|
70
70
|
PATTERN
|
71
71
|
|
72
|
-
def_node_matcher :unscoped_hook, "(block $(send
|
72
|
+
def_node_matcher :unscoped_hook, "(block $(send _ #{HOOKS}) ...)"
|
73
73
|
|
74
74
|
def on_block(node)
|
75
75
|
hook(node) do |method_send, scope_name|
|
@@ -82,11 +82,10 @@ module RuboCop
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def autocorrect(node)
|
85
|
-
scope = "(#{style.inspect})"
|
86
|
-
hook = "#{node.method_name}#{scope}"
|
85
|
+
scope = implicit_style? ? '' : "(#{style.inspect})"
|
87
86
|
|
88
87
|
lambda do |corrector|
|
89
|
-
corrector.replace(node
|
88
|
+
corrector.replace(argument_range(node), scope)
|
90
89
|
end
|
91
90
|
end
|
92
91
|
|
@@ -114,6 +113,13 @@ module RuboCop
|
|
114
113
|
def hook(node, &block)
|
115
114
|
scoped_hook(node, &block) || unscoped_hook(node, &block)
|
116
115
|
end
|
116
|
+
|
117
|
+
def argument_range(send_node)
|
118
|
+
range_between(
|
119
|
+
send_node.loc.selector.end_pos,
|
120
|
+
send_node.loc.expression.end_pos
|
121
|
+
)
|
122
|
+
end
|
117
123
|
end
|
118
124
|
end
|
119
125
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module RSpec
|
4
|
+
# Checks invalid usage for predicate matcher.
|
5
|
+
#
|
6
|
+
# Predicate matcher does not need a question.
|
7
|
+
# This cop checks an unnecessary question in predicate matcher.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# expect(foo).to be_something?
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# expect(foo).to be_something
|
16
|
+
class InvalidPredicateMatcher < Cop
|
17
|
+
MSG = 'Omit `?` from `%<matcher>s`.'.freeze
|
18
|
+
|
19
|
+
def_node_matcher :invalid_predicate_matcher?, <<-PATTERN
|
20
|
+
(send (send nil :expect ...) {:to :not_to :to_not} $(send nil #predicate?))
|
21
|
+
PATTERN
|
22
|
+
|
23
|
+
def on_send(node)
|
24
|
+
invalid_predicate_matcher?(node) do |predicate|
|
25
|
+
add_offense(predicate, :expression)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def predicate?(name)
|
32
|
+
name = name.to_s
|
33
|
+
name.start_with?('be_', 'have_') && name.end_with?('?')
|
34
|
+
end
|
35
|
+
|
36
|
+
def message(predicate)
|
37
|
+
format(MSG, matcher: predicate.method_name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for `let` definitions that come after an example.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # Bad
|
10
|
+
# let(:foo) { bar }
|
11
|
+
#
|
12
|
+
# it 'checks what foo does' do
|
13
|
+
# expect(foo).to be
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# let(:some) { other }
|
17
|
+
#
|
18
|
+
# it 'checks what some does' do
|
19
|
+
# expect(some).to be
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # Good
|
23
|
+
# let(:foo) { bar }
|
24
|
+
# let(:some) { other }
|
25
|
+
#
|
26
|
+
# it 'checks what foo does' do
|
27
|
+
# expect(foo).to be
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# it 'checks what some does' do
|
31
|
+
# expect(some).to be
|
32
|
+
# end
|
33
|
+
class LetBeforeExamples < Cop
|
34
|
+
MSG = 'Move `let` before the examples in the group.'.freeze
|
35
|
+
|
36
|
+
def_node_matcher :let?, '(block (send nil {:let :let!} ...) ...)'
|
37
|
+
def_node_matcher :example_or_group?, <<-PATTERN
|
38
|
+
{
|
39
|
+
#{(Examples::ALL + ExampleGroups::ALL).block_pattern}
|
40
|
+
#{Includes::EXAMPLES.send_pattern}
|
41
|
+
}
|
42
|
+
PATTERN
|
43
|
+
|
44
|
+
def on_block(node)
|
45
|
+
return unless example_group_with_body?(node)
|
46
|
+
|
47
|
+
_describe, _args, body = *node
|
48
|
+
|
49
|
+
check_let_declarations(body)
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_let_declarations(node)
|
53
|
+
example_found = false
|
54
|
+
|
55
|
+
node.each_child_node do |child|
|
56
|
+
if let?(child)
|
57
|
+
add_offense(child, :expression) if example_found
|
58
|
+
elsif example_or_group?(child)
|
59
|
+
example_found = true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -48,14 +48,14 @@ module RuboCop
|
|
48
48
|
class MultipleExpectations < Cop
|
49
49
|
include ConfigurableMax
|
50
50
|
|
51
|
-
MSG = 'Example has too many expectations [
|
51
|
+
MSG = 'Example has too many expectations [%<total>d/%<max>d].'.freeze
|
52
52
|
|
53
53
|
def_node_search :with_aggregated_failures?, '(sym :aggregate_failures)'
|
54
54
|
def_node_search :disabled_aggregated_failures?, <<-PATTERN
|
55
55
|
(pair (sym :aggregate_failures) (false))
|
56
56
|
PATTERN
|
57
57
|
|
58
|
-
def_node_matcher :expect?,
|
58
|
+
def_node_matcher :expect?, Expectations::ALL.send_pattern
|
59
59
|
def_node_matcher :aggregate_failures?, <<-PATTERN
|
60
60
|
(block (send _ :aggregate_failures ...) ...)
|
61
61
|
PATTERN
|
@@ -84,14 +84,12 @@ module RuboCop
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def find_expectation(node, &block)
|
87
|
-
return unless node.is_a?(Parser::AST::Node)
|
88
|
-
|
89
87
|
yield if expect?(node) || aggregate_failures?(node)
|
90
88
|
|
91
89
|
# do not search inside of aggregate_failures block
|
92
90
|
return if aggregate_failures?(node)
|
93
91
|
|
94
|
-
node.
|
92
|
+
node.each_child_node do |child|
|
95
93
|
find_expectation(child, &block)
|
96
94
|
end
|
97
95
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks if an example group defines `subject` multiple times.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# describe Foo do
|
12
|
+
# subject(:user) { User.new }
|
13
|
+
# subject(:post) { Post.new }
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# describe Foo do
|
18
|
+
# let(:user) { User.new }
|
19
|
+
# subject(:post) { Post.new }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# The autocorrect behavior for this cop depends on the type of
|
23
|
+
# duplication:
|
24
|
+
#
|
25
|
+
# - If multiple named subjects are defined then this probably indicates
|
26
|
+
# that the overwritten subjects (all subjects except the last
|
27
|
+
# definition) are effectively being used to define helpers. In this
|
28
|
+
# case they are replaced with `let`.
|
29
|
+
#
|
30
|
+
# - If multiple unnamed subjects are defined though then this can *only*
|
31
|
+
# be dead code and we remove the overwritten subject definitions.
|
32
|
+
#
|
33
|
+
# - If subjects are defined with `subject!` then we don't autocorrect.
|
34
|
+
# This is enough of an edge case that people can just move this to
|
35
|
+
# a `before` hook on their own
|
36
|
+
class MultipleSubjects < Cop
|
37
|
+
MSG = 'Do not set more than one subject per example group'.freeze
|
38
|
+
|
39
|
+
def_node_matcher :named_subject?, <<-PATTERN
|
40
|
+
(block (send nil :subject $sym) args ...)
|
41
|
+
PATTERN
|
42
|
+
|
43
|
+
def on_block(node)
|
44
|
+
return unless example_group?(node)
|
45
|
+
|
46
|
+
subjects = RuboCop::RSpec::ExampleGroup.new(node).subjects
|
47
|
+
|
48
|
+
subjects[0...-1].each do |subject|
|
49
|
+
add_offense(subject, :expression)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def autocorrect(node)
|
54
|
+
return unless node.method_name.equal?(:subject) # Ignore `subject!`
|
55
|
+
|
56
|
+
if named_subject?(node)
|
57
|
+
rename_autocorrect(node)
|
58
|
+
else
|
59
|
+
remove_autocorrect(node)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def rename_autocorrect(node)
|
66
|
+
lambda do |corrector|
|
67
|
+
send_node, _args, _body = *node
|
68
|
+
corrector.replace(send_node.loc.selector, 'let')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove_autocorrect(node)
|
73
|
+
lambda do |corrector|
|
74
|
+
corrector.remove(node.loc.expression)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|