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,341 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# A helper for `inflected` style
|
7
|
+
module InflectedHelper
|
8
|
+
include RuboCop::RSpec::Language
|
9
|
+
extend NodePattern::Macros
|
10
|
+
|
11
|
+
MSG_INFLECTED = 'Prefer using `%<matcher_name>s` matcher over ' \
|
12
|
+
'`%<predicate_name>s`.'
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def check_inflected(node)
|
17
|
+
predicate_in_actual?(node) do |predicate, to, matcher|
|
18
|
+
msg = message_inflected(predicate)
|
19
|
+
add_offense(node, message: msg) do |corrector|
|
20
|
+
remove_predicate(corrector, predicate)
|
21
|
+
corrector.replace(node.loc.selector,
|
22
|
+
true?(to, matcher) ? 'to' : 'not_to')
|
23
|
+
rewrite_matcher(corrector, predicate, matcher)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @!method predicate_in_actual?(node)
|
29
|
+
def_node_matcher :predicate_in_actual?, <<~PATTERN
|
30
|
+
(send
|
31
|
+
(send nil? :expect {
|
32
|
+
(block $(send !nil? #predicate? ...) ...)
|
33
|
+
$(send !nil? #predicate? ...)})
|
34
|
+
$#Runners.all
|
35
|
+
$#boolean_matcher? ...)
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
# @!method be_bool?(node)
|
39
|
+
def_node_matcher :be_bool?, <<~PATTERN
|
40
|
+
(send nil? {:be :eq :eql :equal} {true false})
|
41
|
+
PATTERN
|
42
|
+
|
43
|
+
# @!method be_boolthy?(node)
|
44
|
+
def_node_matcher :be_boolthy?, <<~PATTERN
|
45
|
+
(send nil? {:be_truthy :be_falsey :be_falsy :a_truthy_value :a_falsey_value :a_falsy_value})
|
46
|
+
PATTERN
|
47
|
+
|
48
|
+
def boolean_matcher?(node)
|
49
|
+
if cop_config['Strict']
|
50
|
+
be_boolthy?(node)
|
51
|
+
else
|
52
|
+
be_bool?(node) || be_boolthy?(node)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def predicate?(sym)
|
57
|
+
sym.to_s.end_with?('?')
|
58
|
+
end
|
59
|
+
|
60
|
+
def message_inflected(predicate)
|
61
|
+
format(MSG_INFLECTED,
|
62
|
+
predicate_name: predicate.method_name,
|
63
|
+
matcher_name: to_predicate_matcher(predicate.method_name))
|
64
|
+
end
|
65
|
+
|
66
|
+
# rubocop:disable Metrics/MethodLength
|
67
|
+
def to_predicate_matcher(name)
|
68
|
+
case name = name.to_s
|
69
|
+
when 'is_a?'
|
70
|
+
'be_a'
|
71
|
+
when 'instance_of?'
|
72
|
+
'be_an_instance_of'
|
73
|
+
when 'include?', 'respond_to?'
|
74
|
+
name[0..-2]
|
75
|
+
when 'exist?', 'exists?'
|
76
|
+
'exist'
|
77
|
+
when /\Ahas_/
|
78
|
+
name.sub('has_', 'have_')[0..-2]
|
79
|
+
else
|
80
|
+
"be_#{name[0..-2]}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
# rubocop:enable Metrics/MethodLength
|
84
|
+
|
85
|
+
def remove_predicate(corrector, predicate)
|
86
|
+
range = predicate.loc.dot.with(
|
87
|
+
end_pos: predicate.source_range.end_pos
|
88
|
+
)
|
89
|
+
|
90
|
+
corrector.remove(range)
|
91
|
+
|
92
|
+
block_range = LocationHelp.block_with_whitespace(predicate)
|
93
|
+
corrector.remove(block_range) if block_range
|
94
|
+
end
|
95
|
+
|
96
|
+
def rewrite_matcher(corrector, predicate, matcher)
|
97
|
+
args = LocationHelp.arguments_with_whitespace(predicate).source
|
98
|
+
block_loc = LocationHelp.block_with_whitespace(predicate)
|
99
|
+
block = block_loc ? block_loc.source : ''
|
100
|
+
|
101
|
+
corrector.replace(
|
102
|
+
matcher,
|
103
|
+
to_predicate_matcher(predicate.method_name) + args + block
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def true?(to_symbol, matcher)
|
108
|
+
result = case matcher.method_name
|
109
|
+
when :be, :eq
|
110
|
+
matcher.first_argument.true_type?
|
111
|
+
when :be_truthy, :a_truthy_value
|
112
|
+
true
|
113
|
+
when :be_falsey, :be_falsy, :a_falsey_value, :a_falsy_value
|
114
|
+
false
|
115
|
+
end
|
116
|
+
to_symbol == :to ? result : !result
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# A helper for `explicit` style
|
121
|
+
module ExplicitHelper # rubocop:disable Metrics/ModuleLength
|
122
|
+
include RuboCop::RSpec::Language
|
123
|
+
extend NodePattern::Macros
|
124
|
+
|
125
|
+
MSG_EXPLICIT = 'Prefer using `%<predicate_name>s` over ' \
|
126
|
+
'`%<matcher_name>s` matcher.'
|
127
|
+
BUILT_IN_MATCHERS = %w[
|
128
|
+
be_truthy be_falsey be_falsy
|
129
|
+
have_attributes have_received
|
130
|
+
be_between be_within
|
131
|
+
].freeze
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def allowed_explicit_matchers
|
136
|
+
cop_config.fetch('AllowedExplicitMatchers', []) + BUILT_IN_MATCHERS
|
137
|
+
end
|
138
|
+
|
139
|
+
def check_explicit(node) # rubocop:disable Metrics/MethodLength
|
140
|
+
predicate_matcher_block?(node) do |actual, matcher|
|
141
|
+
add_offense(node, message: message_explicit(matcher)) do |corrector|
|
142
|
+
to_node = node.send_node
|
143
|
+
corrector_explicit(corrector, to_node, actual, matcher, to_node)
|
144
|
+
end
|
145
|
+
ignore_node(node.children.first)
|
146
|
+
return
|
147
|
+
end
|
148
|
+
|
149
|
+
return if part_of_ignored_node?(node)
|
150
|
+
|
151
|
+
predicate_matcher?(node) do |actual, matcher|
|
152
|
+
next unless replaceable_matcher?(matcher)
|
153
|
+
|
154
|
+
add_offense(node, message: message_explicit(matcher)) do |corrector|
|
155
|
+
next if uncorrectable_matcher?(node, matcher)
|
156
|
+
|
157
|
+
corrector_explicit(corrector, node, actual, matcher, matcher)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def replaceable_matcher?(matcher)
|
163
|
+
case matcher.method_name.to_s
|
164
|
+
when 'include'
|
165
|
+
matcher.arguments.one?
|
166
|
+
else
|
167
|
+
true
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def uncorrectable_matcher?(node, matcher)
|
172
|
+
heredoc_argument?(matcher) && !same_line?(node, matcher)
|
173
|
+
end
|
174
|
+
|
175
|
+
def heredoc_argument?(matcher)
|
176
|
+
matcher.arguments.select do |arg|
|
177
|
+
arg.str_type? || arg.dstr_type? || arg.xstr_type?
|
178
|
+
end.any?(&:heredoc?)
|
179
|
+
end
|
180
|
+
|
181
|
+
# @!method predicate_matcher?(node)
|
182
|
+
def_node_matcher :predicate_matcher?, <<~PATTERN
|
183
|
+
(send
|
184
|
+
(send nil? :expect $!nil?)
|
185
|
+
#Runners.all
|
186
|
+
{
|
187
|
+
$(send nil? #predicate_matcher_name? ...)
|
188
|
+
(block $(send nil? #predicate_matcher_name? ...) ...)
|
189
|
+
}
|
190
|
+
...
|
191
|
+
)
|
192
|
+
PATTERN
|
193
|
+
|
194
|
+
# @!method predicate_matcher_block?(node)
|
195
|
+
def_node_matcher :predicate_matcher_block?, <<~PATTERN
|
196
|
+
(block
|
197
|
+
(send
|
198
|
+
(send nil? :expect $!nil?)
|
199
|
+
#Runners.all
|
200
|
+
$(send nil? #predicate_matcher_name?))
|
201
|
+
...)
|
202
|
+
PATTERN
|
203
|
+
|
204
|
+
def predicate_matcher_name?(name)
|
205
|
+
name = name.to_s
|
206
|
+
|
207
|
+
return false if allowed_explicit_matchers.include?(name)
|
208
|
+
|
209
|
+
(name.start_with?('be_', 'have_') && !name.end_with?('?')) ||
|
210
|
+
%w[include respond_to].include?(name)
|
211
|
+
end
|
212
|
+
|
213
|
+
def message_explicit(matcher)
|
214
|
+
format(MSG_EXPLICIT,
|
215
|
+
predicate_name: to_predicate_method(matcher.method_name),
|
216
|
+
matcher_name: matcher.method_name)
|
217
|
+
end
|
218
|
+
|
219
|
+
def corrector_explicit(corrector, to_node, actual, matcher, block_child)
|
220
|
+
replacement_matcher = replacement_matcher(to_node)
|
221
|
+
corrector.replace(matcher, replacement_matcher)
|
222
|
+
move_predicate(corrector, actual, matcher, block_child)
|
223
|
+
corrector.replace(to_node.loc.selector, 'to')
|
224
|
+
end
|
225
|
+
|
226
|
+
def move_predicate(corrector, actual, matcher, block_child)
|
227
|
+
predicate = to_predicate_method(matcher.method_name)
|
228
|
+
args = LocationHelp.arguments_with_whitespace(matcher).source
|
229
|
+
block_loc = LocationHelp.block_with_whitespace(block_child)
|
230
|
+
block = block_loc ? block_loc.source : ''
|
231
|
+
|
232
|
+
corrector.remove(block_loc) if block_loc
|
233
|
+
corrector.insert_after(actual, ".#{predicate}" + args + block)
|
234
|
+
end
|
235
|
+
|
236
|
+
# rubocop:disable Metrics/MethodLength
|
237
|
+
def to_predicate_method(matcher)
|
238
|
+
case matcher = matcher.to_s
|
239
|
+
when 'be_a', 'be_an', 'be_a_kind_of', 'a_kind_of', 'be_kind_of'
|
240
|
+
'is_a?'
|
241
|
+
when 'be_an_instance_of', 'be_instance_of', 'an_instance_of'
|
242
|
+
'instance_of?'
|
243
|
+
when 'include'
|
244
|
+
'include?'
|
245
|
+
when 'respond_to'
|
246
|
+
'respond_to?'
|
247
|
+
when /\Ahave_(.+)/
|
248
|
+
"has_#{Regexp.last_match(1)}?"
|
249
|
+
else
|
250
|
+
"#{matcher[/\Abe_(.+)/, 1]}?"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
# rubocop:enable Metrics/MethodLength
|
254
|
+
|
255
|
+
def replacement_matcher(node)
|
256
|
+
case [cop_config['Strict'], node.method?(:to)]
|
257
|
+
when [true, true]
|
258
|
+
'be(true)'
|
259
|
+
when [true, false]
|
260
|
+
'be(false)'
|
261
|
+
when [false, true]
|
262
|
+
'be_truthy'
|
263
|
+
when [false, false]
|
264
|
+
'be_falsey'
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Prefer using predicate matcher over using predicate method directly.
|
270
|
+
#
|
271
|
+
# RSpec defines magic matchers for predicate methods.
|
272
|
+
# This cop recommends to use the predicate matcher instead of using
|
273
|
+
# predicate method directly.
|
274
|
+
#
|
275
|
+
# @example Strict: true, EnforcedStyle: inflected (default)
|
276
|
+
# # bad
|
277
|
+
# expect(foo.something?).to be_truthy
|
278
|
+
#
|
279
|
+
# # good
|
280
|
+
# expect(foo).to be_something
|
281
|
+
#
|
282
|
+
# # also good - It checks "true" strictly.
|
283
|
+
# expect(foo.something?).to be(true)
|
284
|
+
#
|
285
|
+
# @example Strict: false, EnforcedStyle: inflected
|
286
|
+
# # bad
|
287
|
+
# expect(foo.something?).to be_truthy
|
288
|
+
# expect(foo.something?).to be(true)
|
289
|
+
#
|
290
|
+
# # good
|
291
|
+
# expect(foo).to be_something
|
292
|
+
#
|
293
|
+
# @example Strict: true, EnforcedStyle: explicit
|
294
|
+
# # bad
|
295
|
+
# expect(foo).to be_something
|
296
|
+
#
|
297
|
+
# # good - the above code is rewritten to it by this cop
|
298
|
+
# expect(foo.something?).to be(true)
|
299
|
+
#
|
300
|
+
# # bad - no autocorrect
|
301
|
+
# expect(foo)
|
302
|
+
# .to be_something(<<~TEXT)
|
303
|
+
# bar
|
304
|
+
# TEXT
|
305
|
+
#
|
306
|
+
# # good
|
307
|
+
# expect(foo.something?(<<~TEXT)).to be(true)
|
308
|
+
# bar
|
309
|
+
# TEXT
|
310
|
+
#
|
311
|
+
# @example Strict: false, EnforcedStyle: explicit
|
312
|
+
# # bad
|
313
|
+
# expect(foo).to be_something
|
314
|
+
#
|
315
|
+
# # good - the above code is rewritten to it by this cop
|
316
|
+
# expect(foo.something?).to be_truthy
|
317
|
+
#
|
318
|
+
class PredicateMatcher < Base
|
319
|
+
extend AutoCorrector
|
320
|
+
include ConfigurableEnforcedStyle
|
321
|
+
include InflectedHelper
|
322
|
+
include ExplicitHelper
|
323
|
+
|
324
|
+
RESTRICT_ON_SEND = Runners.all
|
325
|
+
|
326
|
+
def on_send(node)
|
327
|
+
case style
|
328
|
+
when :inflected
|
329
|
+
check_inflected(node)
|
330
|
+
when :explicit
|
331
|
+
check_explicit(node)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
336
|
+
check_explicit(node) if style == :explicit
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for `once` and `twice` receive counts matchers usage.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# expect(foo).to receive(:bar).exactly(1).times
|
11
|
+
# expect(foo).to receive(:bar).exactly(2).times
|
12
|
+
# expect(foo).to receive(:bar).at_least(1).times
|
13
|
+
# expect(foo).to receive(:bar).at_least(2).times
|
14
|
+
# expect(foo).to receive(:bar).at_most(1).times
|
15
|
+
# expect(foo).to receive(:bar).at_most(2).times
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# expect(foo).to receive(:bar).once
|
19
|
+
# expect(foo).to receive(:bar).twice
|
20
|
+
# expect(foo).to receive(:bar).at_least(:once)
|
21
|
+
# expect(foo).to receive(:bar).at_least(:twice)
|
22
|
+
# expect(foo).to receive(:bar).at_most(:once)
|
23
|
+
# expect(foo).to receive(:bar).at_most(:twice).times
|
24
|
+
#
|
25
|
+
class ReceiveCounts < Base
|
26
|
+
extend AutoCorrector
|
27
|
+
|
28
|
+
MSG = 'Use `%<alternative>s` instead of `%<original>s`.'
|
29
|
+
|
30
|
+
RESTRICT_ON_SEND = %i[times].freeze
|
31
|
+
|
32
|
+
# @!method receive_counts(node)
|
33
|
+
def_node_matcher :receive_counts, <<~PATTERN
|
34
|
+
(send $(send _ {:exactly :at_least :at_most} (int {1 2})) :times)
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
# @!method stub?(node)
|
38
|
+
def_node_search :stub?, '(send nil? :receive ...)'
|
39
|
+
|
40
|
+
def on_send(node)
|
41
|
+
receive_counts(node) do |offending_node|
|
42
|
+
return unless stub?(offending_node.receiver)
|
43
|
+
|
44
|
+
offending_range = range(node, offending_node)
|
45
|
+
|
46
|
+
msg = message_for(offending_node, offending_range.source)
|
47
|
+
add_offense(offending_range, message: msg) do |corrector|
|
48
|
+
autocorrect(corrector, offending_node, offending_range)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def autocorrect(corrector, node, range)
|
56
|
+
replacement = matcher_for(
|
57
|
+
node.method_name,
|
58
|
+
node.first_argument.source.to_i
|
59
|
+
)
|
60
|
+
|
61
|
+
corrector.replace(range, replacement)
|
62
|
+
end
|
63
|
+
|
64
|
+
def message_for(node, source)
|
65
|
+
alternative = matcher_for(
|
66
|
+
node.method_name,
|
67
|
+
node.first_argument.source.to_i
|
68
|
+
)
|
69
|
+
format(MSG, alternative: alternative, original: source)
|
70
|
+
end
|
71
|
+
|
72
|
+
def matcher_for(method, count)
|
73
|
+
matcher = count == 1 ? 'once' : 'twice'
|
74
|
+
if method == :exactly
|
75
|
+
".#{matcher}"
|
76
|
+
else
|
77
|
+
".#{method}(:#{matcher})"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def range(node, offending_node)
|
82
|
+
offending_node.loc.dot.with(
|
83
|
+
end_pos: node.source_range.end_pos
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for multiple messages stubbed on the same object.
|
7
|
+
#
|
8
|
+
# @safety
|
9
|
+
# The autocorrection is marked as unsafe, because it may change the
|
10
|
+
# order of stubs. This in turn may cause e.g. variables to be called
|
11
|
+
# before they are defined.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# before do
|
16
|
+
# allow(Service).to receive(:foo).and_return(bar)
|
17
|
+
# allow(Service).to receive(:baz).and_return(qux)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# before do
|
22
|
+
# allow(Service).to receive_messages(foo: bar, baz: qux)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # good - ignore same message
|
26
|
+
# before do
|
27
|
+
# allow(Service).to receive(:foo).and_return(bar)
|
28
|
+
# allow(Service).to receive(:foo).and_return(qux)
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
class ReceiveMessages < Base
|
32
|
+
extend AutoCorrector
|
33
|
+
include RangeHelp
|
34
|
+
|
35
|
+
MSG = 'Use `receive_messages` instead of multiple stubs on lines ' \
|
36
|
+
'%<loc>s.'
|
37
|
+
|
38
|
+
# @!method allow_receive_message?(node)
|
39
|
+
def_node_matcher :allow_receive_message?, <<~PATTERN
|
40
|
+
(send (send nil? :allow ...) :to (send (send nil? :receive (sym _)) :and_return !#heredoc_or_splat?))
|
41
|
+
PATTERN
|
42
|
+
|
43
|
+
# @!method allow_argument(node)
|
44
|
+
def_node_matcher :allow_argument, <<~PATTERN
|
45
|
+
(send (send nil? :allow $_ ...) ...)
|
46
|
+
PATTERN
|
47
|
+
|
48
|
+
# @!method receive_node(node)
|
49
|
+
def_node_search :receive_node, <<~PATTERN
|
50
|
+
$(send (send nil? :receive ...) ...)
|
51
|
+
PATTERN
|
52
|
+
|
53
|
+
# @!method receive_arg(node)
|
54
|
+
def_node_search :receive_arg, <<~PATTERN
|
55
|
+
(send (send nil? :receive $_) ...)
|
56
|
+
PATTERN
|
57
|
+
|
58
|
+
# @!method receive_and_return_argument(node)
|
59
|
+
def_node_matcher :receive_and_return_argument, <<~PATTERN
|
60
|
+
(send (send nil? :allow ...) :to (send (send nil? :receive (sym $_)) :and_return $_))
|
61
|
+
PATTERN
|
62
|
+
|
63
|
+
def on_begin(node)
|
64
|
+
repeated_receive_message(node).each do |item, repeated_lines, args|
|
65
|
+
next if repeated_lines.empty?
|
66
|
+
|
67
|
+
register_offense(item, repeated_lines, args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def repeated_receive_message(node)
|
74
|
+
node
|
75
|
+
.children
|
76
|
+
.select { |child| allow_receive_message?(child) }
|
77
|
+
.group_by { |child| allow_argument(child) }
|
78
|
+
.values
|
79
|
+
.reject(&:one?)
|
80
|
+
.flat_map { |items| add_repeated_lines_and_arguments(items) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_repeated_lines_and_arguments(items)
|
84
|
+
uniq_items = uniq_items(items)
|
85
|
+
repeated_lines = uniq_items.map(&:first_line)
|
86
|
+
uniq_items.map do |item|
|
87
|
+
[item, repeated_lines - [item.first_line], arguments(uniq_items)]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def uniq_items(items)
|
92
|
+
items.select do |item|
|
93
|
+
items.none? do |i|
|
94
|
+
receive_arg(item).first == receive_arg(i).first &&
|
95
|
+
!same_line?(item, i)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def arguments(items)
|
101
|
+
items.map do |item|
|
102
|
+
receive_and_return_argument(item) do |receive_arg, return_arg|
|
103
|
+
"#{normalize_receive_arg(receive_arg)}: " \
|
104
|
+
"#{normalize_return_arg(return_arg)}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def normalize_receive_arg(receive_arg)
|
110
|
+
if requires_quotes?(receive_arg)
|
111
|
+
"'#{receive_arg}'"
|
112
|
+
else
|
113
|
+
receive_arg
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def normalize_return_arg(return_arg)
|
118
|
+
if return_arg.hash_type? && !return_arg.braces?
|
119
|
+
"{ #{return_arg.source} }"
|
120
|
+
else
|
121
|
+
return_arg.source
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def register_offense(item, repeated_lines, args)
|
126
|
+
add_offense(item, message: message(repeated_lines)) do |corrector|
|
127
|
+
if item.loc.line > repeated_lines.max
|
128
|
+
replace_to_receive_messages(corrector, item, args)
|
129
|
+
else
|
130
|
+
corrector.remove(item_range_by_whole_lines(item))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def message(repeated_lines)
|
136
|
+
format(MSG, loc: repeated_lines)
|
137
|
+
end
|
138
|
+
|
139
|
+
def replace_to_receive_messages(corrector, item, args)
|
140
|
+
receive_node(item) do |node|
|
141
|
+
corrector.replace(node,
|
142
|
+
"receive_messages(#{args.join(', ')})")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def item_range_by_whole_lines(item)
|
147
|
+
range_by_whole_lines(item.source_range, include_final_newline: true)
|
148
|
+
end
|
149
|
+
|
150
|
+
def heredoc_or_splat?(node)
|
151
|
+
((node.str_type? || node.dstr_type?) && node.heredoc?) ||
|
152
|
+
node.splat_type?
|
153
|
+
end
|
154
|
+
|
155
|
+
def requires_quotes?(value)
|
156
|
+
value.match?(/^:".*?"|=$|^\W+$/)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Prefer `not_to receive(...)` over `receive(...).never`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# expect(foo).to receive(:bar).never
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# expect(foo).not_to receive(:bar)
|
14
|
+
#
|
15
|
+
class ReceiveNever < Base
|
16
|
+
extend AutoCorrector
|
17
|
+
MSG = 'Use `not_to receive` instead of `never`.'
|
18
|
+
RESTRICT_ON_SEND = %i[never].freeze
|
19
|
+
|
20
|
+
# @!method method_on_stub?(node)
|
21
|
+
def_node_search :method_on_stub?, '(send nil? :receive ...)'
|
22
|
+
|
23
|
+
def on_send(node)
|
24
|
+
return unless node.method?(:never) && method_on_stub?(node)
|
25
|
+
|
26
|
+
add_offense(node.loc.selector) do |corrector|
|
27
|
+
autocorrect(corrector, node)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def autocorrect(corrector, node)
|
34
|
+
corrector.replace(node.parent.loc.selector, 'not_to')
|
35
|
+
range = node.loc.dot.with(end_pos: node.loc.selector.end_pos)
|
36
|
+
corrector.remove(range)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Remove redundant `around` hook.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# around do |example|
|
11
|
+
# example.run
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
#
|
16
|
+
class RedundantAround < Base
|
17
|
+
extend AutoCorrector
|
18
|
+
|
19
|
+
MSG = 'Remove redundant `around` hook.'
|
20
|
+
|
21
|
+
RESTRICT_ON_SEND = %i[around].freeze
|
22
|
+
|
23
|
+
def on_block(node)
|
24
|
+
return unless match_redundant_around_hook_block?(node)
|
25
|
+
|
26
|
+
add_offense(node) do |corrector|
|
27
|
+
autocorrect(corrector, node)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias on_numblock on_block
|
31
|
+
|
32
|
+
def on_send(node)
|
33
|
+
return unless match_redundant_around_hook_send?(node)
|
34
|
+
|
35
|
+
add_offense(node) do |corrector|
|
36
|
+
autocorrect(corrector, node)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# @!method match_redundant_around_hook_block?(node)
|
43
|
+
def_node_matcher :match_redundant_around_hook_block?, <<~PATTERN
|
44
|
+
({block numblock} (send _ :around ...) ... (send _ :run))
|
45
|
+
PATTERN
|
46
|
+
|
47
|
+
# @!method match_redundant_around_hook_send?(node)
|
48
|
+
def_node_matcher :match_redundant_around_hook_send?, <<~PATTERN
|
49
|
+
(send
|
50
|
+
_
|
51
|
+
:around
|
52
|
+
...
|
53
|
+
(block-pass
|
54
|
+
(sym :run)
|
55
|
+
)
|
56
|
+
)
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
def autocorrect(corrector, node)
|
60
|
+
corrector.remove(node)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|