rubocop-rspec 1.7.0 → 3.0.2
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 +5 -5
- data/CHANGELOG.md +955 -79
- data/CODE_OF_CONDUCT.md +17 -0
- data/MIT-LICENSE.md +1 -2
- data/README.md +35 -35
- data/config/default.yml +940 -52
- data/config/obsoletion.yml +30 -0
- data/lib/rubocop/cop/rspec/align_left_let_brace.rb +49 -0
- data/lib/rubocop/cop/rspec/align_right_let_brace.rb +49 -0
- data/lib/rubocop/cop/rspec/any_instance.rb +10 -13
- data/lib/rubocop/cop/rspec/around_block.rb +97 -0
- data/lib/rubocop/cop/rspec/base.rb +26 -0
- data/lib/rubocop/cop/rspec/be.rb +39 -0
- data/lib/rubocop/cop/rspec/be_empty.rb +45 -0
- data/lib/rubocop/cop/rspec/be_eq.rb +47 -0
- data/lib/rubocop/cop/rspec/be_eql.rb +18 -15
- data/lib/rubocop/cop/rspec/be_nil.rb +74 -0
- data/lib/rubocop/cop/rspec/before_after_all.rb +45 -0
- data/lib/rubocop/cop/rspec/change_by_zero.rb +184 -0
- data/lib/rubocop/cop/rspec/class_check.rb +101 -0
- data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
- data/lib/rubocop/cop/rspec/context_method.rb +57 -0
- data/lib/rubocop/cop/rspec/context_wording.rb +117 -0
- data/lib/rubocop/cop/rspec/describe_class.rb +52 -21
- data/lib/rubocop/cop/rspec/describe_method.rb +26 -11
- data/lib/rubocop/cop/rspec/describe_symbol.rb +37 -0
- data/lib/rubocop/cop/rspec/described_class.rb +181 -34
- data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +38 -0
- data/lib/rubocop/cop/rspec/dialect.rb +84 -0
- data/lib/rubocop/cop/rspec/duplicated_metadata.rb +58 -0
- data/lib/rubocop/cop/rspec/empty_example_group.rb +134 -47
- data/lib/rubocop/cop/rspec/empty_hook.rb +49 -0
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +82 -0
- data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +42 -0
- data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +40 -0
- data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +82 -0
- data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +36 -0
- data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
- data/lib/rubocop/cop/rspec/empty_output.rb +47 -0
- data/lib/rubocop/cop/rspec/eq.rb +47 -0
- data/lib/rubocop/cop/rspec/example_length.rb +38 -20
- data/lib/rubocop/cop/rspec/example_without_description.rb +98 -0
- data/lib/rubocop/cop/rspec/example_wording.rb +117 -27
- data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +110 -0
- data/lib/rubocop/cop/rspec/expect_actual.rb +46 -20
- data/lib/rubocop/cop/rspec/expect_change.rb +86 -0
- data/lib/rubocop/cop/rspec/expect_in_hook.rb +50 -0
- data/lib/rubocop/cop/rspec/expect_in_let.rb +42 -0
- data/lib/rubocop/cop/rspec/expect_output.rb +50 -0
- data/lib/rubocop/cop/rspec/focus.rb +79 -25
- data/lib/rubocop/cop/rspec/hook_argument.rb +48 -36
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +81 -0
- data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +37 -0
- data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +68 -0
- data/lib/rubocop/cop/rspec/implicit_expect.rb +100 -0
- data/lib/rubocop/cop/rspec/implicit_subject.rb +167 -0
- data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
- data/lib/rubocop/cop/rspec/instance_spy.rb +74 -0
- data/lib/rubocop/cop/rspec/instance_variable.rb +28 -14
- data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
- data/lib/rubocop/cop/rspec/it_behaves_like.rb +49 -0
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +74 -0
- data/lib/rubocop/cop/rspec/leading_subject.rb +57 -29
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +127 -0
- data/lib/rubocop/cop/rspec/let_before_examples.rb +101 -0
- data/lib/rubocop/cop/rspec/let_setup.rb +32 -16
- data/lib/rubocop/cop/rspec/match_array.rb +59 -0
- data/lib/rubocop/cop/rspec/message_chain.rb +10 -15
- data/lib/rubocop/cop/rspec/message_expectation.rb +12 -9
- data/lib/rubocop/cop/rspec/message_spies.rb +88 -0
- data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
- data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +35 -0
- data/lib/rubocop/cop/rspec/missing_expectation_target_method.rb +54 -0
- data/lib/rubocop/cop/rspec/mixin/comments_help.rb +38 -0
- data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +59 -0
- data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
- data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
- data/lib/rubocop/cop/rspec/mixin/inside_example_group.rb +29 -0
- data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
- data/lib/rubocop/cop/rspec/mixin/metadata.rb +63 -0
- data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +39 -0
- data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
- data/lib/rubocop/cop/rspec/mixin/variable.rb +21 -0
- data/lib/rubocop/cop/rspec/multiple_describes.rb +14 -12
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +86 -26
- data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +146 -0
- data/lib/rubocop/cop/rspec/multiple_subjects.rb +97 -0
- data/lib/rubocop/cop/rspec/named_subject.rb +107 -27
- data/lib/rubocop/cop/rspec/nested_groups.rb +84 -47
- data/lib/rubocop/cop/rspec/no_expectation_example.rb +102 -0
- data/lib/rubocop/cop/rspec/not_to_not.rb +30 -27
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +74 -0
- data/lib/rubocop/cop/rspec/pending.rb +80 -0
- data/lib/rubocop/cop/rspec/pending_without_reason.rb +159 -0
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +341 -0
- data/lib/rubocop/cop/rspec/receive_counts.rb +89 -0
- data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
- data/lib/rubocop/cop/rspec/receive_never.rb +41 -0
- data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
- data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
- data/lib/rubocop/cop/rspec/remove_const.rb +39 -0
- data/lib/rubocop/cop/rspec/repeated_description.rb +98 -0
- data/lib/rubocop/cop/rspec/repeated_example.rb +53 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +100 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +105 -0
- data/lib/rubocop/cop/rspec/repeated_subject_call.rb +125 -0
- data/lib/rubocop/cop/rspec/return_from_stub.rb +169 -0
- data/lib/rubocop/cop/rspec/scattered_let.rb +59 -0
- data/lib/rubocop/cop/rspec/scattered_setup.rb +92 -0
- data/lib/rubocop/cop/rspec/shared_context.rb +107 -0
- data/lib/rubocop/cop/rspec/shared_examples.rb +125 -0
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +93 -0
- data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
- data/lib/rubocop/cop/rspec/sort_metadata.rb +71 -0
- data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
- data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
- data/lib/rubocop/cop/rspec/stubbed_mock.rb +176 -0
- data/lib/rubocop/cop/rspec/subject_declaration.rb +46 -0
- data/lib/rubocop/cop/rspec/subject_stub.rb +93 -74
- data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +69 -0
- data/lib/rubocop/cop/rspec/unspecified_exception.rb +67 -0
- data/lib/rubocop/cop/rspec/variable_definition.rb +77 -0
- data/lib/rubocop/cop/rspec/variable_name.rb +68 -0
- data/lib/rubocop/cop/rspec/verified_double_reference.rb +111 -0
- data/lib/rubocop/cop/rspec/verified_doubles.rb +28 -14
- data/lib/rubocop/cop/rspec/void_expect.rb +60 -0
- data/lib/rubocop/cop/rspec/yield.rb +82 -0
- data/lib/rubocop/cop/rspec_cops.rb +112 -0
- data/lib/rubocop/rspec/align_let_brace.rb +63 -0
- data/lib/rubocop/rspec/concept.rb +33 -0
- data/lib/rubocop/rspec/config_formatter.rb +27 -4
- data/lib/rubocop/rspec/cop/generator.rb +25 -0
- data/lib/rubocop/rspec/corrector/move_node.rb +51 -0
- data/lib/rubocop/rspec/description_extractor.rb +60 -18
- data/lib/rubocop/rspec/example.rb +37 -0
- data/lib/rubocop/rspec/example_group.rb +67 -0
- data/lib/rubocop/rspec/hook.rb +79 -0
- data/lib/rubocop/rspec/inject.rb +3 -1
- data/lib/rubocop/rspec/language.rb +184 -41
- data/lib/rubocop/rspec/node.rb +19 -0
- data/lib/rubocop/rspec/shared_contexts/default_rspec_language_config_context.rb +29 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop/rspec/wording.rb +61 -19
- data/lib/rubocop/rspec.rb +6 -2
- data/lib/rubocop-rspec.rb +45 -34
- metadata +130 -195
- data/Gemfile +0 -13
- data/Rakefile +0 -48
- data/lib/rubocop/cop/rspec/file_path.rb +0 -83
- data/lib/rubocop/rspec/language/node_pattern.rb +0 -16
- data/lib/rubocop/rspec/spec_only.rb +0 -61
- data/lib/rubocop/rspec/top_level_describe.rb +0 -61
- data/lib/rubocop/rspec/util.rb +0 -19
- data/rubocop-rspec.gemspec +0 -42
- data/spec/expect_violation/expectation_spec.rb +0 -85
- data/spec/project/changelog_spec.rb +0 -81
- data/spec/project/default_config_spec.rb +0 -52
- data/spec/project/project_requires_spec.rb +0 -8
- data/spec/rubocop/cop/rspec/any_instance_spec.rb +0 -30
- data/spec/rubocop/cop/rspec/be_eql_spec.rb +0 -59
- data/spec/rubocop/cop/rspec/describe_class_spec.rb +0 -113
- data/spec/rubocop/cop/rspec/describe_method_spec.rb +0 -32
- data/spec/rubocop/cop/rspec/described_class_spec.rb +0 -219
- data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +0 -79
- data/spec/rubocop/cop/rspec/example_length_spec.rb +0 -117
- data/spec/rubocop/cop/rspec/example_wording_spec.rb +0 -82
- data/spec/rubocop/cop/rspec/expect_actual_spec.rb +0 -136
- data/spec/rubocop/cop/rspec/file_path_spec.rb +0 -236
- data/spec/rubocop/cop/rspec/focus_spec.rb +0 -130
- data/spec/rubocop/cop/rspec/hook_argument_spec.rb +0 -189
- data/spec/rubocop/cop/rspec/instance_variable_spec.rb +0 -75
- data/spec/rubocop/cop/rspec/leading_subject_spec.rb +0 -54
- data/spec/rubocop/cop/rspec/let_setup_spec.rb +0 -66
- data/spec/rubocop/cop/rspec/message_chain_spec.rb +0 -21
- data/spec/rubocop/cop/rspec/message_expectation_spec.rb +0 -63
- data/spec/rubocop/cop/rspec/multiple_describes_spec.rb +0 -28
- data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +0 -84
- data/spec/rubocop/cop/rspec/named_subject_spec.rb +0 -62
- data/spec/rubocop/cop/rspec/nested_groups_spec.rb +0 -55
- data/spec/rubocop/cop/rspec/not_to_not_spec.rb +0 -57
- data/spec/rubocop/cop/rspec/subject_stub_spec.rb +0 -183
- data/spec/rubocop/cop/rspec/verified_doubles_spec.rb +0 -71
- data/spec/rubocop/rspec/config_formatter_spec.rb +0 -48
- data/spec/rubocop/rspec/description_extractor_spec.rb +0 -35
- data/spec/rubocop/rspec/language/selector_set_spec.rb +0 -29
- data/spec/rubocop/rspec/spec_only_spec.rb +0 -97
- data/spec/rubocop/rspec/util/one_spec.rb +0 -21
- data/spec/rubocop/rspec/wording_spec.rb +0 -44
- data/spec/shared/rspec_only_cop_behavior.rb +0 -68
- data/spec/spec_helper.rb +0 -41
- data/spec/support/expect_violation.rb +0 -166
@@ -0,0 +1,169 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for consistent style of stub's return setting.
|
7
|
+
#
|
8
|
+
# Enforces either `and_return` or block-style return in the cases
|
9
|
+
# where the returned value is constant. Ignores dynamic returned values
|
10
|
+
# are the result would be different
|
11
|
+
#
|
12
|
+
# This cop can be configured using the `EnforcedStyle` option
|
13
|
+
#
|
14
|
+
# @example `EnforcedStyle: and_return` (default)
|
15
|
+
# # bad
|
16
|
+
# allow(Foo).to receive(:bar) { "baz" }
|
17
|
+
# expect(Foo).to receive(:bar) { "baz" }
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# allow(Foo).to receive(:bar).and_return("baz")
|
21
|
+
# expect(Foo).to receive(:bar).and_return("baz")
|
22
|
+
# # also good as the returned value is dynamic
|
23
|
+
# allow(Foo).to receive(:bar) { bar.baz }
|
24
|
+
#
|
25
|
+
# @example `EnforcedStyle: block`
|
26
|
+
# # bad
|
27
|
+
# allow(Foo).to receive(:bar).and_return("baz")
|
28
|
+
# expect(Foo).to receive(:bar).and_return("baz")
|
29
|
+
#
|
30
|
+
# # good
|
31
|
+
# allow(Foo).to receive(:bar) { "baz" }
|
32
|
+
# expect(Foo).to receive(:bar) { "baz" }
|
33
|
+
# # also good as the returned value is dynamic
|
34
|
+
# allow(Foo).to receive(:bar).and_return(bar.baz)
|
35
|
+
#
|
36
|
+
class ReturnFromStub < Base
|
37
|
+
extend AutoCorrector
|
38
|
+
include ConfigurableEnforcedStyle
|
39
|
+
|
40
|
+
MSG_AND_RETURN = 'Use `and_return` for static values.'
|
41
|
+
MSG_BLOCK = 'Use block for static values.'
|
42
|
+
RESTRICT_ON_SEND = %i[and_return].freeze
|
43
|
+
|
44
|
+
# @!method contains_stub?(node)
|
45
|
+
def_node_search :contains_stub?, '(send nil? :receive (...))'
|
46
|
+
|
47
|
+
# @!method stub_with_block?(node)
|
48
|
+
def_node_matcher :stub_with_block?, '(block #contains_stub? ...)'
|
49
|
+
|
50
|
+
# @!method and_return_value(node)
|
51
|
+
def_node_search :and_return_value, <<~PATTERN
|
52
|
+
$(send _ :and_return $(...))
|
53
|
+
PATTERN
|
54
|
+
|
55
|
+
def on_send(node)
|
56
|
+
return unless style == :block
|
57
|
+
return unless contains_stub?(node)
|
58
|
+
|
59
|
+
check_and_return_call(node)
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
63
|
+
return unless style == :and_return
|
64
|
+
return unless stub_with_block?(node)
|
65
|
+
|
66
|
+
check_block_body(node)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def check_and_return_call(node)
|
72
|
+
and_return_value(node) do |and_return, args|
|
73
|
+
unless dynamic?(args)
|
74
|
+
add_offense(and_return.loc.selector, message: MSG_BLOCK) do |corr|
|
75
|
+
AndReturnCallCorrector.new(and_return).call(corr)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_block_body(block)
|
82
|
+
body = block.body
|
83
|
+
unless dynamic?(body) # rubocop:disable Style/GuardClause
|
84
|
+
add_offense(block.loc.begin, message: MSG_AND_RETURN) do |corrector|
|
85
|
+
BlockBodyCorrector.new(block).call(corrector)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def dynamic?(node)
|
91
|
+
node && !node.recursive_literal_or_const?
|
92
|
+
end
|
93
|
+
|
94
|
+
# :nodoc:
|
95
|
+
class AndReturnCallCorrector
|
96
|
+
def initialize(node)
|
97
|
+
@node = node
|
98
|
+
@receiver = node.receiver
|
99
|
+
@arg = node.first_argument
|
100
|
+
end
|
101
|
+
|
102
|
+
def call(corrector)
|
103
|
+
# Heredoc autocorrection is not yet implemented.
|
104
|
+
return if heredoc?
|
105
|
+
|
106
|
+
corrector.replace(range, " { #{replacement} }")
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
attr_reader :node, :receiver, :arg
|
112
|
+
|
113
|
+
def heredoc?
|
114
|
+
arg.loc.is_a?(Parser::Source::Map::Heredoc)
|
115
|
+
end
|
116
|
+
|
117
|
+
def range
|
118
|
+
Parser::Source::Range.new(
|
119
|
+
node.source_range.source_buffer,
|
120
|
+
receiver.source_range.end_pos,
|
121
|
+
node.source_range.end_pos
|
122
|
+
)
|
123
|
+
end
|
124
|
+
|
125
|
+
def replacement
|
126
|
+
if hash_without_braces?
|
127
|
+
"{ #{arg.source} }"
|
128
|
+
else
|
129
|
+
arg.source
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def hash_without_braces?
|
134
|
+
arg.hash_type? && !arg.braces?
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# :nodoc:
|
139
|
+
class BlockBodyCorrector
|
140
|
+
def initialize(block)
|
141
|
+
@block = block
|
142
|
+
@node = block.parent
|
143
|
+
@body = block.body || NULL_BLOCK_BODY
|
144
|
+
end
|
145
|
+
|
146
|
+
def call(corrector)
|
147
|
+
# Heredoc autocorrection is not yet implemented.
|
148
|
+
return if heredoc?
|
149
|
+
|
150
|
+
corrector.replace(
|
151
|
+
block,
|
152
|
+
"#{block.send_node.source}.and_return(#{body.source})"
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
attr_reader :node, :block, :body
|
159
|
+
|
160
|
+
def heredoc?
|
161
|
+
body.loc.is_a?(Parser::Source::Map::Heredoc)
|
162
|
+
end
|
163
|
+
|
164
|
+
NULL_BLOCK_BODY = Struct.new(:loc, :source).new(nil, 'nil')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for let scattered across the example group.
|
7
|
+
#
|
8
|
+
# Group lets together
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# describe Foo do
|
13
|
+
# let(:foo) { 1 }
|
14
|
+
# subject { Foo }
|
15
|
+
# let(:bar) { 2 }
|
16
|
+
# before { prepare }
|
17
|
+
# let!(:baz) { 3 }
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# describe Foo do
|
22
|
+
# subject { Foo }
|
23
|
+
# before { prepare }
|
24
|
+
# let(:foo) { 1 }
|
25
|
+
# let(:bar) { 2 }
|
26
|
+
# let!(:baz) { 3 }
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
class ScatteredLet < Base
|
30
|
+
extend AutoCorrector
|
31
|
+
|
32
|
+
MSG = 'Group all let/let! blocks in the example group together.'
|
33
|
+
|
34
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
35
|
+
return unless example_group_with_body?(node)
|
36
|
+
|
37
|
+
check_let_declarations(node.body)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def check_let_declarations(body)
|
43
|
+
lets = body.each_child_node.select { |node| let?(node) }
|
44
|
+
|
45
|
+
first_let = lets.first
|
46
|
+
lets.each_with_index do |node, idx|
|
47
|
+
next if node.sibling_index == first_let.sibling_index + idx
|
48
|
+
|
49
|
+
add_offense(node) do |corrector|
|
50
|
+
RuboCop::RSpec::Corrector::MoveNode.new(
|
51
|
+
node, corrector, processed_source
|
52
|
+
).move_after(first_let)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for setup scattered across multiple hooks in an example group.
|
7
|
+
#
|
8
|
+
# Unify `before`, `after`, and `around` hooks when possible.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# describe Foo do
|
13
|
+
# before { setup1 }
|
14
|
+
# before { setup2 }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# describe Foo do
|
19
|
+
# before do
|
20
|
+
# setup1
|
21
|
+
# setup2
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
class ScatteredSetup < Base
|
26
|
+
include FinalEndLocation
|
27
|
+
include RangeHelp
|
28
|
+
extend AutoCorrector
|
29
|
+
|
30
|
+
MSG = 'Do not define multiple `%<hook_name>s` hooks in the same ' \
|
31
|
+
'example group (also defined on %<lines>s).'
|
32
|
+
|
33
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
34
|
+
return unless example_group?(node)
|
35
|
+
|
36
|
+
repeated_hooks(node).each do |occurrences|
|
37
|
+
occurrences.each do |occurrence|
|
38
|
+
message = message(occurrences, occurrence)
|
39
|
+
add_offense(occurrence, message: message) do |corrector|
|
40
|
+
autocorrect(corrector, occurrences.first, occurrence)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def repeated_hooks(node)
|
49
|
+
hooks = RuboCop::RSpec::ExampleGroup.new(node)
|
50
|
+
.hooks
|
51
|
+
.select(&:knowable_scope?)
|
52
|
+
.group_by { |hook| [hook.name, hook.scope, hook.metadata] }
|
53
|
+
.values
|
54
|
+
.reject(&:one?)
|
55
|
+
|
56
|
+
hooks.map do |hook|
|
57
|
+
hook.map(&:to_node)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def lines_msg(numbers)
|
62
|
+
if numbers.size == 1
|
63
|
+
"line #{numbers.first}"
|
64
|
+
else
|
65
|
+
"lines #{numbers.join(', ')}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def message(occurrences, occurrence)
|
70
|
+
lines = occurrences.map(&:first_line)
|
71
|
+
lines_except_current = lines - [occurrence.first_line]
|
72
|
+
format(MSG, hook_name: occurrences.first.method_name,
|
73
|
+
lines: lines_msg(lines_except_current))
|
74
|
+
end
|
75
|
+
|
76
|
+
def autocorrect(corrector, first_occurrence, occurrence)
|
77
|
+
return if first_occurrence == occurrence || !first_occurrence.body
|
78
|
+
|
79
|
+
# Take heredocs into account
|
80
|
+
body = occurrence.body&.source_range&.with(
|
81
|
+
end_pos: final_end_location(occurrence).begin_pos
|
82
|
+
)
|
83
|
+
|
84
|
+
corrector.insert_after(first_occurrence.body,
|
85
|
+
"\n#{body&.source}")
|
86
|
+
corrector.remove(range_by_whole_lines(occurrence.source_range,
|
87
|
+
include_final_newline: true))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for proper shared_context and shared_examples usage.
|
7
|
+
#
|
8
|
+
# If there are no examples defined, use shared_context.
|
9
|
+
# If there is no setup defined, use shared_examples.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# RSpec.shared_context 'only examples here' do
|
14
|
+
# it 'does x' do
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# it 'does y' do
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# RSpec.shared_examples 'only examples here' do
|
23
|
+
# it 'does x' do
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# it 'does y' do
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# # bad
|
32
|
+
# RSpec.shared_examples 'only setup here' do
|
33
|
+
# subject(:foo) { :bar }
|
34
|
+
#
|
35
|
+
# let(:baz) { :bazz }
|
36
|
+
#
|
37
|
+
# before do
|
38
|
+
# something
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# # good
|
43
|
+
# RSpec.shared_context 'only setup here' do
|
44
|
+
# subject(:foo) { :bar }
|
45
|
+
#
|
46
|
+
# let(:baz) { :bazz }
|
47
|
+
#
|
48
|
+
# before do
|
49
|
+
# something
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
class SharedContext < Base
|
54
|
+
extend AutoCorrector
|
55
|
+
|
56
|
+
MSG_EXAMPLES = "Use `shared_examples` when you don't define context."
|
57
|
+
MSG_CONTEXT = "Use `shared_context` when you don't define examples."
|
58
|
+
|
59
|
+
# @!method examples?(node)
|
60
|
+
def_node_search :examples?, <<~PATTERN
|
61
|
+
(send nil? {#Includes.examples #Examples.all} ...)
|
62
|
+
PATTERN
|
63
|
+
|
64
|
+
# @!method context?(node)
|
65
|
+
def_node_search :context?, <<~PATTERN
|
66
|
+
(send nil?
|
67
|
+
{#Subjects.all #Helpers.all #Includes.context #Hooks.all} ...
|
68
|
+
)
|
69
|
+
PATTERN
|
70
|
+
|
71
|
+
# @!method shared_context(node)
|
72
|
+
def_node_matcher :shared_context, <<~PATTERN
|
73
|
+
(block (send #rspec? #SharedGroups.context ...) ...)
|
74
|
+
PATTERN
|
75
|
+
|
76
|
+
# @!method shared_example(node)
|
77
|
+
def_node_matcher :shared_example, <<~PATTERN
|
78
|
+
(block (send #rspec? #SharedGroups.examples ...) ...)
|
79
|
+
PATTERN
|
80
|
+
|
81
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
82
|
+
context_with_only_examples(node) do
|
83
|
+
add_offense(node.send_node, message: MSG_EXAMPLES) do |corrector|
|
84
|
+
corrector.replace(node.send_node.loc.selector, 'shared_examples')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
examples_with_only_context(node) do
|
89
|
+
add_offense(node.send_node, message: MSG_CONTEXT) do |corrector|
|
90
|
+
corrector.replace(node.send_node.loc.selector, 'shared_context')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def context_with_only_examples(node)
|
98
|
+
shared_context(node) { yield if examples?(node) && !context?(node) }
|
99
|
+
end
|
100
|
+
|
101
|
+
def examples_with_only_context(node)
|
102
|
+
shared_example(node) { yield if context?(node) && !examples?(node) }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for consistent style for shared example names.
|
7
|
+
#
|
8
|
+
# Enforces either `string` or `symbol` for shared example names.
|
9
|
+
#
|
10
|
+
# This cop can be configured using the `EnforcedStyle` option
|
11
|
+
#
|
12
|
+
# @example `EnforcedStyle: string` (default)
|
13
|
+
# # bad
|
14
|
+
# it_behaves_like :foo_bar_baz
|
15
|
+
# it_should_behave_like :foo_bar_baz
|
16
|
+
# shared_examples :foo_bar_baz
|
17
|
+
# shared_examples_for :foo_bar_baz
|
18
|
+
# include_examples :foo_bar_baz
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# it_behaves_like 'foo bar baz'
|
22
|
+
# it_should_behave_like 'foo bar baz'
|
23
|
+
# shared_examples 'foo bar baz'
|
24
|
+
# shared_examples_for 'foo bar baz'
|
25
|
+
# include_examples 'foo bar baz'
|
26
|
+
#
|
27
|
+
# @example `EnforcedStyle: symbol`
|
28
|
+
# # bad
|
29
|
+
# it_behaves_like 'foo bar baz'
|
30
|
+
# it_should_behave_like 'foo bar baz'
|
31
|
+
# shared_examples 'foo bar baz'
|
32
|
+
# shared_examples_for 'foo bar baz'
|
33
|
+
# include_examples 'foo bar baz'
|
34
|
+
#
|
35
|
+
# # good
|
36
|
+
# it_behaves_like :foo_bar_baz
|
37
|
+
# it_should_behave_like :foo_bar_baz
|
38
|
+
# shared_examples :foo_bar_baz
|
39
|
+
# shared_examples_for :foo_bar_baz
|
40
|
+
# include_examples :foo_bar_baz
|
41
|
+
#
|
42
|
+
class SharedExamples < Base
|
43
|
+
extend AutoCorrector
|
44
|
+
include ConfigurableEnforcedStyle
|
45
|
+
|
46
|
+
# @!method shared_examples(node)
|
47
|
+
def_node_matcher :shared_examples, <<~PATTERN
|
48
|
+
{
|
49
|
+
(send #rspec? #SharedGroups.all $_ ...)
|
50
|
+
(send nil? #Includes.all $_ ...)
|
51
|
+
}
|
52
|
+
PATTERN
|
53
|
+
|
54
|
+
def on_send(node)
|
55
|
+
shared_examples(node) do |ast_node|
|
56
|
+
next unless offense?(ast_node)
|
57
|
+
|
58
|
+
checker = new_checker(ast_node)
|
59
|
+
add_offense(ast_node, message: checker.message) do |corrector|
|
60
|
+
corrector.replace(ast_node, checker.preferred_style)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def offense?(ast_node)
|
68
|
+
if style == :symbol
|
69
|
+
ast_node.str_type?
|
70
|
+
else # string
|
71
|
+
ast_node.sym_type?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def new_checker(ast_node)
|
76
|
+
if style == :symbol
|
77
|
+
SymbolChecker.new(ast_node)
|
78
|
+
else # string
|
79
|
+
StringChecker.new(ast_node)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# :nodoc:
|
84
|
+
class SymbolChecker
|
85
|
+
MSG = 'Prefer %<prefer>s over `%<current>s` ' \
|
86
|
+
'to symbolize shared examples.'
|
87
|
+
|
88
|
+
attr_reader :node
|
89
|
+
|
90
|
+
def initialize(node)
|
91
|
+
@node = node
|
92
|
+
end
|
93
|
+
|
94
|
+
def message
|
95
|
+
format(MSG, prefer: preferred_style, current: node.value.inspect)
|
96
|
+
end
|
97
|
+
|
98
|
+
def preferred_style
|
99
|
+
":#{node.value.to_s.downcase.tr(' ', '_')}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# :nodoc:
|
104
|
+
class StringChecker
|
105
|
+
MSG = 'Prefer %<prefer>s over `%<current>s` ' \
|
106
|
+
'to titleize shared examples.'
|
107
|
+
|
108
|
+
attr_reader :node
|
109
|
+
|
110
|
+
def initialize(node)
|
111
|
+
@node = node
|
112
|
+
end
|
113
|
+
|
114
|
+
def message
|
115
|
+
format(MSG, prefer: preferred_style, current: node.value.inspect)
|
116
|
+
end
|
117
|
+
|
118
|
+
def preferred_style
|
119
|
+
"'#{node.value.to_s.tr('_', ' ')}'"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that chains of messages contain more than one element.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# allow(foo).to receive_message_chain(:bar).and_return(42)
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# allow(foo).to receive(:bar).and_return(42)
|
14
|
+
#
|
15
|
+
# # also good
|
16
|
+
# allow(foo).to receive(:bar, :baz)
|
17
|
+
# allow(foo).to receive("bar.baz")
|
18
|
+
#
|
19
|
+
class SingleArgumentMessageChain < Base
|
20
|
+
extend AutoCorrector
|
21
|
+
|
22
|
+
MSG = 'Use `%<recommended>s` instead of calling ' \
|
23
|
+
'`%<called>s` with a single argument.'
|
24
|
+
RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze
|
25
|
+
|
26
|
+
# @!method message_chain(node)
|
27
|
+
def_node_matcher :message_chain, <<~PATTERN
|
28
|
+
(send _ {:receive_message_chain :stub_chain} $_)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
# @!method single_key_hash?(node)
|
32
|
+
def_node_matcher :single_key_hash?, '(hash pair)'
|
33
|
+
|
34
|
+
def on_send(node)
|
35
|
+
message_chain(node) do |arg|
|
36
|
+
return if valid_usage?(arg)
|
37
|
+
|
38
|
+
method = node.method_name
|
39
|
+
msg = format(MSG, recommended: replacement(method), called: method)
|
40
|
+
|
41
|
+
add_offense(node.loc.selector, message: msg) do |corrector|
|
42
|
+
autocorrect(corrector, node, method, arg)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def autocorrect(corrector, node, method, arg)
|
50
|
+
corrector.replace(node.loc.selector, replacement(method))
|
51
|
+
autocorrect_hash_arg(corrector, arg) if single_key_hash?(arg)
|
52
|
+
autocorrect_array_arg(corrector, arg) if arg.array_type?
|
53
|
+
end
|
54
|
+
|
55
|
+
def valid_usage?(node)
|
56
|
+
return true unless node.literal? || node.array_type?
|
57
|
+
|
58
|
+
case node.type
|
59
|
+
when :hash then !single_key_hash?(node)
|
60
|
+
when :array then !single_element_array?(node)
|
61
|
+
else node.to_s.include?('.')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def single_element_array?(node)
|
66
|
+
node.child_nodes.one?
|
67
|
+
end
|
68
|
+
|
69
|
+
def autocorrect_hash_arg(corrector, arg)
|
70
|
+
key, value = *arg.children.first
|
71
|
+
|
72
|
+
corrector.replace(arg, key_to_arg(key))
|
73
|
+
corrector.insert_after(arg.parent.loc.end,
|
74
|
+
".and_return(#{value.source})")
|
75
|
+
end
|
76
|
+
|
77
|
+
def autocorrect_array_arg(corrector, arg)
|
78
|
+
value = arg.children.first
|
79
|
+
|
80
|
+
corrector.replace(arg, value.source)
|
81
|
+
end
|
82
|
+
|
83
|
+
def key_to_arg(node)
|
84
|
+
node.sym_type? ? ":#{node.value}" : node.source
|
85
|
+
end
|
86
|
+
|
87
|
+
def replacement(method)
|
88
|
+
method.equal?(:receive_message_chain) ? 'receive' : 'stub'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for passing a block to `skip` within examples.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# it 'does something' do
|
11
|
+
# skip 'not yet implemented' do
|
12
|
+
# do_something
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# it 'does something' do
|
18
|
+
# skip 'not yet implemented'
|
19
|
+
# do_something
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # good - when outside example
|
23
|
+
# skip 'not yet implemented' do
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
class SkipBlockInsideExample < Base
|
27
|
+
MSG = "Don't pass a block to `skip` inside examples."
|
28
|
+
|
29
|
+
def on_block(node)
|
30
|
+
return unless node.method?(:skip)
|
31
|
+
return unless inside_example?(node)
|
32
|
+
|
33
|
+
add_offense(node)
|
34
|
+
end
|
35
|
+
|
36
|
+
alias on_numblock on_block
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def inside_example?(node)
|
41
|
+
node.each_ancestor(:block).any? { |ancestor| example?(ancestor) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|