rubocop-rspec 1.41.0 → 1.44.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 +43 -2
- data/config/default.yml +41 -3
- data/lib/rubocop-rspec.rb +2 -1
- data/lib/rubocop/cop/rspec/align_left_let_brace.rb +12 -19
- data/lib/rubocop/cop/rspec/align_right_let_brace.rb +12 -19
- data/lib/rubocop/cop/rspec/any_instance.rb +1 -1
- data/lib/rubocop/cop/rspec/around_block.rb +2 -2
- data/lib/rubocop/cop/rspec/base.rb +76 -0
- data/lib/rubocop/cop/rspec/be.rb +2 -2
- data/lib/rubocop/cop/rspec/be_eql.rb +6 -6
- data/lib/rubocop/cop/rspec/before_after_all.rb +1 -1
- data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +19 -17
- data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +14 -12
- data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +1 -1
- data/lib/rubocop/cop/rspec/context_method.rb +7 -9
- data/lib/rubocop/cop/rspec/context_wording.rb +3 -3
- data/lib/rubocop/cop/rspec/cop.rb +2 -66
- data/lib/rubocop/cop/rspec/describe_class.rb +40 -30
- data/lib/rubocop/cop/rspec/describe_method.rb +14 -6
- data/lib/rubocop/cop/rspec/describe_symbol.rb +2 -2
- data/lib/rubocop/cop/rspec/described_class.rb +12 -9
- data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +1 -1
- data/lib/rubocop/cop/rspec/dialect.rb +5 -12
- data/lib/rubocop/cop/rspec/empty_example_group.rb +124 -6
- data/lib/rubocop/cop/rspec/empty_hook.rb +6 -10
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +5 -7
- data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +5 -9
- data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +8 -8
- data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +5 -9
- data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +6 -6
- data/lib/rubocop/cop/rspec/example_length.rb +1 -1
- data/lib/rubocop/cop/rspec/example_without_description.rb +1 -1
- data/lib/rubocop/cop/rspec/example_wording.rb +10 -11
- data/lib/rubocop/cop/rspec/expect_actual.rb +8 -11
- data/lib/rubocop/cop/rspec/expect_change.rb +10 -35
- data/lib/rubocop/cop/rspec/expect_in_hook.rb +3 -3
- data/lib/rubocop/cop/rspec/expect_output.rb +2 -2
- data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +20 -20
- data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +20 -22
- data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +7 -8
- data/lib/rubocop/cop/rspec/file_path.rb +25 -17
- data/lib/rubocop/cop/rspec/focus.rb +7 -11
- data/lib/rubocop/cop/rspec/hook_argument.rb +16 -23
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +13 -14
- data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +2 -3
- data/lib/rubocop/cop/rspec/implicit_expect.rb +7 -15
- data/lib/rubocop/cop/rspec/implicit_subject.rb +16 -11
- data/lib/rubocop/cop/rspec/instance_spy.rb +18 -12
- data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
- data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +3 -6
- data/lib/rubocop/cop/rspec/it_behaves_like.rb +5 -6
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +1 -1
- data/lib/rubocop/cop/rspec/leading_subject.rb +27 -20
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
- data/lib/rubocop/cop/rspec/let_before_examples.rb +13 -11
- data/lib/rubocop/cop/rspec/let_setup.rb +6 -3
- data/lib/rubocop/cop/rspec/message_chain.rb +7 -6
- data/lib/rubocop/cop/rspec/message_expectation.rb +2 -2
- data/lib/rubocop/cop/rspec/message_spies.rb +2 -3
- data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +1 -1
- data/lib/rubocop/cop/rspec/multiple_describes.rb +11 -8
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +7 -11
- data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +148 -0
- data/lib/rubocop/cop/rspec/multiple_subjects.rb +18 -19
- data/lib/rubocop/cop/rspec/named_subject.rb +2 -2
- data/lib/rubocop/cop/rspec/nested_groups.rb +4 -4
- data/lib/rubocop/cop/rspec/not_to_not.rb +5 -6
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/pending.rb +1 -1
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +30 -67
- data/lib/rubocop/cop/rspec/rails/http_status.rb +5 -9
- data/lib/rubocop/cop/rspec/receive_counts.rb +15 -17
- data/lib/rubocop/cop/rspec/receive_never.rb +12 -12
- data/lib/rubocop/cop/rspec/repeated_description.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_example.rb +2 -2
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +103 -0
- data/lib/rubocop/cop/rspec/return_from_stub.rb +9 -20
- data/lib/rubocop/cop/rspec/scattered_let.rb +8 -11
- data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/shared_context.rb +8 -21
- data/lib/rubocop/cop/rspec/shared_examples.rb +6 -9
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +15 -18
- data/lib/rubocop/cop/rspec/stubbed_mock.rb +172 -0
- data/lib/rubocop/cop/rspec/subject_stub.rb +6 -6
- data/lib/rubocop/cop/rspec/unspecified_exception.rb +1 -1
- data/lib/rubocop/cop/rspec/variable_definition.rb +6 -6
- data/lib/rubocop/cop/rspec/variable_name.rb +28 -9
- data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
- data/lib/rubocop/cop/rspec/void_expect.rb +1 -1
- data/lib/rubocop/cop/rspec/yield.rb +14 -11
- data/lib/rubocop/cop/rspec_cops.rb +3 -0
- data/lib/rubocop/rspec/corrector/move_node.rb +7 -5
- data/lib/rubocop/rspec/description_extractor.rb +1 -1
- data/lib/rubocop/rspec/{blank_line_separation.rb → empty_line_separation.rb} +13 -10
- data/lib/rubocop/rspec/example_group.rb +2 -2
- data/lib/rubocop/rspec/hook.rb +1 -5
- data/lib/rubocop/rspec/language.rb +12 -5
- data/lib/rubocop/rspec/language/node_pattern.rb +6 -1
- data/lib/rubocop/rspec/top_level_describe.rb +2 -2
- data/lib/rubocop/rspec/top_level_group.rb +26 -13
- data/lib/rubocop/rspec/variable.rb +1 -1
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +40 -8
@@ -15,7 +15,7 @@ module RuboCop
|
|
15
15
|
# expect(user).to be_valid
|
16
16
|
# end
|
17
17
|
#
|
18
|
-
class RepeatedExample <
|
18
|
+
class RepeatedExample < Base
|
19
19
|
MSG = "Don't repeat examples within an example group."
|
20
20
|
|
21
21
|
def on_block(node)
|
@@ -41,7 +41,7 @@ module RuboCop
|
|
41
41
|
def example_signature(example)
|
42
42
|
key_parts = [example.metadata, example.implementation]
|
43
43
|
|
44
|
-
if example.definition.
|
44
|
+
if example.definition.method?(:its)
|
45
45
|
key_parts << example.definition.arguments
|
46
46
|
end
|
47
47
|
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
# it { is_expected.to respond_to :each }
|
44
44
|
# end
|
45
45
|
#
|
46
|
-
class RepeatedExampleGroupBody <
|
46
|
+
class RepeatedExampleGroupBody < Base
|
47
47
|
MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
|
48
48
|
|
49
49
|
def_node_matcher :several_example_groups?, <<-PATTERN
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
# # example group
|
44
44
|
# end
|
45
45
|
#
|
46
|
-
class RepeatedExampleGroupDescription <
|
46
|
+
class RepeatedExampleGroupDescription < Base
|
47
47
|
MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
|
48
48
|
|
49
49
|
def_node_matcher :several_example_groups?, <<-PATTERN
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for repeated include of shared examples.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# describe 'foo' do
|
12
|
+
# include_examples 'cool stuff'
|
13
|
+
# include_examples 'cool stuff'
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # bad
|
17
|
+
# describe 'foo' do
|
18
|
+
# it_behaves_like 'a cool', 'thing'
|
19
|
+
# it_behaves_like 'a cool', 'thing'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # bad
|
23
|
+
# context 'foo' do
|
24
|
+
# it_should_behave_like 'a duck'
|
25
|
+
# it_should_behave_like 'a duck'
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # good
|
29
|
+
# describe 'foo' do
|
30
|
+
# include_examples 'cool stuff'
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# describe 'bar' do
|
34
|
+
# include_examples 'cool stuff'
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# describe 'foo' do
|
39
|
+
# it_behaves_like 'a cool', 'thing'
|
40
|
+
# it_behaves_like 'a cool', 'person'
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# # good
|
44
|
+
# context 'foo' do
|
45
|
+
# it_should_behave_like 'a duck'
|
46
|
+
# it_should_behave_like 'a goose'
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
class RepeatedIncludeExample < Base
|
50
|
+
MSG = 'Repeated include of shared_examples %<name>s ' \
|
51
|
+
'on line(s) %<repeat>s'
|
52
|
+
|
53
|
+
def_node_matcher :several_include_examples?, <<-PATTERN
|
54
|
+
(begin <#include_examples? #include_examples? ...>)
|
55
|
+
PATTERN
|
56
|
+
|
57
|
+
def_node_matcher :include_examples?, Includes::EXAMPLES.send_pattern
|
58
|
+
|
59
|
+
def_node_matcher :shared_examples_name, <<-PATTERN
|
60
|
+
(send _ #{Includes::EXAMPLES.node_pattern_union} $_ ...)
|
61
|
+
PATTERN
|
62
|
+
|
63
|
+
def on_begin(node)
|
64
|
+
return unless several_include_examples?(node)
|
65
|
+
|
66
|
+
repeated_include_examples(node).each do |item, repeats|
|
67
|
+
add_offense(item, message: message(item, repeats))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def repeated_include_examples(node)
|
74
|
+
node
|
75
|
+
.children
|
76
|
+
.select { |child| literal_include_examples?(child) }
|
77
|
+
.group_by { |child| signature_keys(child) }
|
78
|
+
.values
|
79
|
+
.reject(&:one?)
|
80
|
+
.flat_map { |items| add_repeated_lines(items) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def literal_include_examples?(node)
|
84
|
+
include_examples?(node) &&
|
85
|
+
node.arguments.all?(&:recursive_literal_or_const?)
|
86
|
+
end
|
87
|
+
|
88
|
+
def add_repeated_lines(items)
|
89
|
+
repeated_lines = items.map(&:first_line)
|
90
|
+
items.map { |item| [item, repeated_lines - [item.first_line]] }
|
91
|
+
end
|
92
|
+
|
93
|
+
def signature_keys(item)
|
94
|
+
item.arguments
|
95
|
+
end
|
96
|
+
|
97
|
+
def message(item, repeats)
|
98
|
+
format(MSG, name: shared_examples_name(item).source, repeat: repeats)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -33,7 +33,8 @@ module RuboCop
|
|
33
33
|
# # also good as the returned value is dynamic
|
34
34
|
# allow(Foo).to receive(:bar) { bar.baz }
|
35
35
|
#
|
36
|
-
class ReturnFromStub <
|
36
|
+
class ReturnFromStub < Base
|
37
|
+
extend AutoCorrector
|
37
38
|
include ConfigurableEnforcedStyle
|
38
39
|
|
39
40
|
MSG_AND_RETURN = 'Use `and_return` for static values.'
|
@@ -59,24 +60,14 @@ module RuboCop
|
|
59
60
|
check_block_body(node)
|
60
61
|
end
|
61
62
|
|
62
|
-
def autocorrect(node)
|
63
|
-
if style == :block
|
64
|
-
AndReturnCallCorrector.new(node)
|
65
|
-
else
|
66
|
-
BlockBodyCorrector.new(node)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
63
|
private
|
71
64
|
|
72
65
|
def check_and_return_call(node)
|
73
66
|
and_return_value(node) do |and_return, args|
|
74
67
|
unless dynamic?(args)
|
75
|
-
add_offense(
|
76
|
-
and_return
|
77
|
-
|
78
|
-
message: MSG_BLOCK
|
79
|
-
)
|
68
|
+
add_offense(and_return.loc.selector, message: MSG_BLOCK) do |corr|
|
69
|
+
AndReturnCallCorrector.new(and_return).call(corr)
|
70
|
+
end
|
80
71
|
end
|
81
72
|
end
|
82
73
|
end
|
@@ -84,11 +75,9 @@ module RuboCop
|
|
84
75
|
def check_block_body(block)
|
85
76
|
body = block.body
|
86
77
|
unless dynamic?(body) # rubocop:disable Style/GuardClause
|
87
|
-
add_offense(
|
88
|
-
block
|
89
|
-
|
90
|
-
message: MSG_AND_RETURN
|
91
|
-
)
|
78
|
+
add_offense(block.loc.begin, message: MSG_AND_RETURN) do |corrector|
|
79
|
+
BlockBodyCorrector.new(block).call(corrector)
|
80
|
+
end
|
92
81
|
end
|
93
82
|
end
|
94
83
|
|
@@ -153,7 +142,7 @@ module RuboCop
|
|
153
142
|
return if heredoc?
|
154
143
|
|
155
144
|
corrector.replace(
|
156
|
-
block
|
145
|
+
block,
|
157
146
|
"#{block.send_node.source}.and_return(#{body.source})"
|
158
147
|
)
|
159
148
|
end
|
@@ -26,7 +26,9 @@ module RuboCop
|
|
26
26
|
# let!(:baz) { 3 }
|
27
27
|
# end
|
28
28
|
#
|
29
|
-
class ScatteredLet <
|
29
|
+
class ScatteredLet < Base
|
30
|
+
extend AutoCorrector
|
31
|
+
|
30
32
|
MSG = 'Group all let/let! blocks in the example group together.'
|
31
33
|
|
32
34
|
def on_block(node)
|
@@ -35,15 +37,6 @@ module RuboCop
|
|
35
37
|
check_let_declarations(node.body)
|
36
38
|
end
|
37
39
|
|
38
|
-
def autocorrect(node)
|
39
|
-
lambda do |corrector|
|
40
|
-
first_let = find_first_let(node.parent)
|
41
|
-
RuboCop::RSpec::Corrector::MoveNode.new(
|
42
|
-
node, corrector, processed_source
|
43
|
-
).move_after(first_let)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
40
|
private
|
48
41
|
|
49
42
|
def check_let_declarations(body)
|
@@ -53,7 +46,11 @@ module RuboCop
|
|
53
46
|
lets.each_with_index do |node, idx|
|
54
47
|
next if node.sibling_index == first_let.sibling_index + idx
|
55
48
|
|
56
|
-
add_offense(node)
|
49
|
+
add_offense(node) do |corrector|
|
50
|
+
RuboCop::RSpec::Corrector::MoveNode.new(
|
51
|
+
node, corrector, processed_source
|
52
|
+
).move_after(first_let)
|
53
|
+
end
|
57
54
|
end
|
58
55
|
end
|
59
56
|
|
@@ -50,7 +50,9 @@ module RuboCop
|
|
50
50
|
# end
|
51
51
|
# end
|
52
52
|
#
|
53
|
-
class SharedContext <
|
53
|
+
class SharedContext < Base
|
54
|
+
extend AutoCorrector
|
55
|
+
|
54
56
|
MSG_EXAMPLES = "Use `shared_examples` when you don't "\
|
55
57
|
'define context.'
|
56
58
|
|
@@ -68,22 +70,14 @@ module RuboCop
|
|
68
70
|
|
69
71
|
def on_block(node)
|
70
72
|
context_with_only_examples(node) do
|
71
|
-
|
73
|
+
add_offense(node.send_node, message: MSG_EXAMPLES) do |corrector|
|
74
|
+
corrector.replace(node.send_node.loc.selector, 'shared_examples')
|
75
|
+
end
|
72
76
|
end
|
73
77
|
|
74
78
|
examples_with_only_context(node) do
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
def autocorrect(node)
|
80
|
-
lambda do |corrector|
|
81
|
-
context_with_only_examples(node.parent) do
|
82
|
-
corrector.replace(node.loc.selector, 'shared_examples')
|
83
|
-
end
|
84
|
-
|
85
|
-
examples_with_only_context(node.parent) do
|
86
|
-
corrector.replace(node.loc.selector, 'shared_context')
|
79
|
+
add_offense(node.send_node, message: MSG_CONTEXT) do |corrector|
|
80
|
+
corrector.replace(node.send_node.loc.selector, 'shared_context')
|
87
81
|
end
|
88
82
|
end
|
89
83
|
end
|
@@ -97,13 +91,6 @@ module RuboCop
|
|
97
91
|
def examples_with_only_context(node)
|
98
92
|
shared_example(node) { yield if context?(node) && !examples?(node) }
|
99
93
|
end
|
100
|
-
|
101
|
-
def add_shared_item_offense(node, message)
|
102
|
-
add_offense(
|
103
|
-
node,
|
104
|
-
message: message
|
105
|
-
)
|
106
|
-
end
|
107
94
|
end
|
108
95
|
end
|
109
96
|
end
|
@@ -20,7 +20,9 @@ module RuboCop
|
|
20
20
|
# shared_examples_for 'foo bar baz'
|
21
21
|
# include_examples 'foo bar baz'
|
22
22
|
#
|
23
|
-
class SharedExamples <
|
23
|
+
class SharedExamples < Base
|
24
|
+
extend AutoCorrector
|
25
|
+
|
24
26
|
def_node_matcher :shared_examples,
|
25
27
|
(SharedGroups::ALL + Includes::ALL).send_pattern
|
26
28
|
|
@@ -30,14 +32,9 @@ module RuboCop
|
|
30
32
|
next unless ast_node&.sym_type?
|
31
33
|
|
32
34
|
checker = Checker.new(ast_node)
|
33
|
-
add_offense(checker.node, message: checker.message)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def autocorrect(node)
|
38
|
-
lambda do |corrector|
|
39
|
-
checker = Checker.new(node)
|
40
|
-
corrector.replace(node.loc.expression, checker.preferred_style)
|
35
|
+
add_offense(checker.node, message: checker.message) do |corrector|
|
36
|
+
corrector.replace(checker.node, checker.preferred_style)
|
37
|
+
end
|
41
38
|
end
|
42
39
|
end
|
43
40
|
|
@@ -16,7 +16,9 @@ module RuboCop
|
|
16
16
|
# allow(foo).to receive(:bar, :baz)
|
17
17
|
# allow(foo).to receive("bar.baz")
|
18
18
|
#
|
19
|
-
class SingleArgumentMessageChain <
|
19
|
+
class SingleArgumentMessageChain < Base
|
20
|
+
extend AutoCorrector
|
21
|
+
|
20
22
|
MSG = 'Use `%<recommended>s` instead of calling '\
|
21
23
|
'`%<called>s` with a single argument.'
|
22
24
|
|
@@ -30,22 +32,23 @@ module RuboCop
|
|
30
32
|
message_chain(node) do |arg|
|
31
33
|
return if valid_usage?(arg)
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
end
|
35
|
+
method = node.method_name
|
36
|
+
msg = format(MSG, recommended: replacement(method), called: method)
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
corrector.replace(node.loc.selector, replacement(node.method_name))
|
40
|
-
message_chain(node) do |arg|
|
41
|
-
autocorrect_hash_arg(corrector, arg) if single_key_hash?(arg)
|
42
|
-
autocorrect_array_arg(corrector, arg) if arg.array_type?
|
38
|
+
add_offense(node.loc.selector, message: msg) do |corrector|
|
39
|
+
autocorrect(corrector, node, method, arg)
|
43
40
|
end
|
44
41
|
end
|
45
42
|
end
|
46
43
|
|
47
44
|
private
|
48
45
|
|
46
|
+
def autocorrect(corrector, node, method, arg)
|
47
|
+
corrector.replace(node.loc.selector, replacement(method))
|
48
|
+
autocorrect_hash_arg(corrector, arg) if single_key_hash?(arg)
|
49
|
+
autocorrect_array_arg(corrector, arg) if arg.array_type?
|
50
|
+
end
|
51
|
+
|
49
52
|
def valid_usage?(node)
|
50
53
|
return true unless node.literal? || node.array_type?
|
51
54
|
|
@@ -63,7 +66,7 @@ module RuboCop
|
|
63
66
|
def autocorrect_hash_arg(corrector, arg)
|
64
67
|
key, value = *arg.children.first
|
65
68
|
|
66
|
-
corrector.replace(arg
|
69
|
+
corrector.replace(arg, key_to_arg(key))
|
67
70
|
corrector.insert_after(arg.parent.loc.end,
|
68
71
|
".and_return(#{value.source})")
|
69
72
|
end
|
@@ -71,7 +74,7 @@ module RuboCop
|
|
71
74
|
def autocorrect_array_arg(corrector, arg)
|
72
75
|
value = arg.children.first
|
73
76
|
|
74
|
-
corrector.replace(arg
|
77
|
+
corrector.replace(arg, value.source)
|
75
78
|
end
|
76
79
|
|
77
80
|
def key_to_arg(node)
|
@@ -82,12 +85,6 @@ module RuboCop
|
|
82
85
|
def replacement(method)
|
83
86
|
method.equal?(:receive_message_chain) ? 'receive' : 'stub'
|
84
87
|
end
|
85
|
-
|
86
|
-
def message(node)
|
87
|
-
method = node.method_name
|
88
|
-
|
89
|
-
format(MSG, recommended: replacement(method), called: method)
|
90
|
-
end
|
91
88
|
end
|
92
89
|
end
|
93
90
|
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that message expectations do not have a configured response.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# expect(foo).to receive(:bar).with(42).and_return("hello world")
|
12
|
+
#
|
13
|
+
# # good (without spies)
|
14
|
+
# allow(foo).to receive(:bar).with(42).and_return("hello world")
|
15
|
+
# expect(foo).to receive(:bar).with(42)
|
16
|
+
#
|
17
|
+
class StubbedMock < Base
|
18
|
+
MSG = 'Prefer `%<replacement>s` over `%<method_name>s` when ' \
|
19
|
+
'configuring a response.'
|
20
|
+
|
21
|
+
# @!method message_expectation?(node)
|
22
|
+
# Match message expectation matcher
|
23
|
+
#
|
24
|
+
# @example source that matches
|
25
|
+
# receive(:foo)
|
26
|
+
#
|
27
|
+
# @example source that matches
|
28
|
+
# receive_message_chain(:foo, :bar)
|
29
|
+
#
|
30
|
+
# @example source that matches
|
31
|
+
# receive(:foo).with('bar')
|
32
|
+
#
|
33
|
+
# @param node [RuboCop::AST::Node]
|
34
|
+
# @return [Array<RuboCop::AST::Node>] matching nodes
|
35
|
+
def_node_matcher :message_expectation?, <<-PATTERN
|
36
|
+
{
|
37
|
+
(send nil? { :receive :receive_message_chain } ...) # receive(:foo)
|
38
|
+
(send (send nil? :receive ...) :with ...) # receive(:foo).with('bar')
|
39
|
+
}
|
40
|
+
PATTERN
|
41
|
+
|
42
|
+
def_node_matcher :configured_response?, <<~PATTERN
|
43
|
+
{ :and_return :and_raise :and_throw :and_yield
|
44
|
+
:and_call_original :and_wrap_original }
|
45
|
+
PATTERN
|
46
|
+
|
47
|
+
# @!method expectation(node)
|
48
|
+
# Match expectation
|
49
|
+
#
|
50
|
+
# @example source that matches
|
51
|
+
# is_expected.to be_in_the_bar
|
52
|
+
#
|
53
|
+
# @example source that matches
|
54
|
+
# expect(cocktail).to contain_exactly(:fresh_orange_juice, :campari)
|
55
|
+
#
|
56
|
+
# @example source that matches
|
57
|
+
# expect_any_instance_of(Officer).to be_alert
|
58
|
+
#
|
59
|
+
# @param node [RuboCop::AST::Node]
|
60
|
+
# @yield [RuboCop::AST::Node] expectation, method name, matcher
|
61
|
+
def_node_matcher :expectation, <<~PATTERN
|
62
|
+
(send
|
63
|
+
$(send nil? $#{Expectations::ALL.node_pattern_union} ...)
|
64
|
+
:to $_)
|
65
|
+
PATTERN
|
66
|
+
|
67
|
+
# @!method matcher_with_configured_response(node)
|
68
|
+
# Match matcher with a configured response
|
69
|
+
#
|
70
|
+
# @example source that matches
|
71
|
+
# receive(:foo).and_return('bar')
|
72
|
+
#
|
73
|
+
# @example source that matches
|
74
|
+
# receive(:lower).and_raise(SomeError)
|
75
|
+
#
|
76
|
+
# @example source that matches
|
77
|
+
# receive(:redirect).and_call_original
|
78
|
+
#
|
79
|
+
# @param node [RuboCop::AST::Node]
|
80
|
+
# @yield [RuboCop::AST::Node] matcher
|
81
|
+
def_node_matcher :matcher_with_configured_response, <<~PATTERN
|
82
|
+
(send #message_expectation? #configured_response? _)
|
83
|
+
PATTERN
|
84
|
+
|
85
|
+
# @!method matcher_with_return_block(node)
|
86
|
+
# Match matcher with a return block
|
87
|
+
#
|
88
|
+
# @example source that matches
|
89
|
+
# receive(:foo) { 'bar' }
|
90
|
+
#
|
91
|
+
# @param node [RuboCop::AST::Node]
|
92
|
+
# @yield [RuboCop::AST::Node] matcher
|
93
|
+
def_node_matcher :matcher_with_return_block, <<~PATTERN
|
94
|
+
(block #message_expectation? args _) # receive(:foo) { 'bar' }
|
95
|
+
PATTERN
|
96
|
+
|
97
|
+
# @!method matcher_with_hash(node)
|
98
|
+
# Match matcher with a configured response defined as a hash
|
99
|
+
#
|
100
|
+
# @example source that matches
|
101
|
+
# receive_messages(foo: 'bar', baz: 'qux')
|
102
|
+
#
|
103
|
+
# @example source that matches
|
104
|
+
# receive_message_chain(:foo, bar: 'baz')
|
105
|
+
#
|
106
|
+
# @param node [RuboCop::AST::Node]
|
107
|
+
# @yield [RuboCop::AST::Node] matcher
|
108
|
+
def_node_matcher :matcher_with_hash, <<~PATTERN
|
109
|
+
{
|
110
|
+
(send nil? :receive_messages hash) # receive_messages(foo: 'bar', baz: 'qux')
|
111
|
+
(send nil? :receive_message_chain ... hash) # receive_message_chain(:foo, bar: 'baz')
|
112
|
+
}
|
113
|
+
PATTERN
|
114
|
+
|
115
|
+
# @!method matcher_with_blockpass(node)
|
116
|
+
# Match matcher with a configured response in block-pass
|
117
|
+
#
|
118
|
+
# @example source that matches
|
119
|
+
# receive(:foo, &canned)
|
120
|
+
#
|
121
|
+
# @example source that matches
|
122
|
+
# receive_message_chain(:foo, :bar, &canned)
|
123
|
+
#
|
124
|
+
# @example source that matches
|
125
|
+
# receive(:foo).with('bar', &canned)
|
126
|
+
#
|
127
|
+
# @param node [RuboCop::AST::Node]
|
128
|
+
# @yield [RuboCop::AST::Node] matcher
|
129
|
+
def_node_matcher :matcher_with_blockpass, <<~PATTERN
|
130
|
+
{
|
131
|
+
(send nil? { :receive :receive_message_chain } ... block_pass) # receive(:foo, &canned)
|
132
|
+
(send (send nil? :receive ...) :with ... block_pass) # receive(:foo).with('foo', &canned)
|
133
|
+
}
|
134
|
+
PATTERN
|
135
|
+
|
136
|
+
def on_send(node)
|
137
|
+
expectation(node, &method(:on_expectation))
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def on_expectation(expectation, method_name, matcher)
|
143
|
+
flag_expectation = lambda do
|
144
|
+
add_offense(expectation, message: msg(method_name))
|
145
|
+
end
|
146
|
+
|
147
|
+
matcher_with_configured_response(matcher, &flag_expectation)
|
148
|
+
matcher_with_return_block(matcher, &flag_expectation)
|
149
|
+
matcher_with_hash(matcher, &flag_expectation)
|
150
|
+
matcher_with_blockpass(matcher, &flag_expectation)
|
151
|
+
end
|
152
|
+
|
153
|
+
def msg(method_name)
|
154
|
+
format(MSG,
|
155
|
+
method_name: method_name,
|
156
|
+
replacement: replacement(method_name))
|
157
|
+
end
|
158
|
+
|
159
|
+
def replacement(method_name)
|
160
|
+
case method_name
|
161
|
+
when :expect
|
162
|
+
:allow
|
163
|
+
when :is_expected
|
164
|
+
'allow(subject)'
|
165
|
+
when :expect_any_instance_of
|
166
|
+
:allow_any_instance_of
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|