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,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Prefer negated matchers over `to change.by(0)`.
|
7
|
+
#
|
8
|
+
# In the case of composite expectations, cop suggest using the
|
9
|
+
# negation matchers of `RSpec::Matchers#change`.
|
10
|
+
#
|
11
|
+
# By default the cop does not support autocorrect of
|
12
|
+
# compound expectations, but if you set the
|
13
|
+
# negated matcher for `change`, e.g. `not_change` with
|
14
|
+
# the `NegatedMatcher` option, the cop will perform the autocorrection.
|
15
|
+
#
|
16
|
+
# @example NegatedMatcher: ~ (default)
|
17
|
+
# # bad
|
18
|
+
# expect { run }.to change(Foo, :bar).by(0)
|
19
|
+
# expect { run }.to change { Foo.bar }.by(0)
|
20
|
+
#
|
21
|
+
# # bad - compound expectations (does not support autocorrection)
|
22
|
+
# expect { run }
|
23
|
+
# .to change(Foo, :bar).by(0)
|
24
|
+
# .and change(Foo, :baz).by(0)
|
25
|
+
# expect { run }
|
26
|
+
# .to change { Foo.bar }.by(0)
|
27
|
+
# .and change { Foo.baz }.by(0)
|
28
|
+
#
|
29
|
+
# # good
|
30
|
+
# expect { run }.not_to change(Foo, :bar)
|
31
|
+
# expect { run }.not_to change { Foo.bar }
|
32
|
+
#
|
33
|
+
# # good - compound expectations
|
34
|
+
# define_negated_matcher :not_change, :change
|
35
|
+
# expect { run }
|
36
|
+
# .to not_change(Foo, :bar)
|
37
|
+
# .and not_change(Foo, :baz)
|
38
|
+
# expect { run }
|
39
|
+
# .to not_change { Foo.bar }
|
40
|
+
# .and not_change { Foo.baz }
|
41
|
+
#
|
42
|
+
# @example NegatedMatcher: not_change
|
43
|
+
# # bad (support autocorrection to good case)
|
44
|
+
# expect { run }
|
45
|
+
# .to change(Foo, :bar).by(0)
|
46
|
+
# .and change(Foo, :baz).by(0)
|
47
|
+
# expect { run }
|
48
|
+
# .to change { Foo.bar }.by(0)
|
49
|
+
# .and change { Foo.baz }.by(0)
|
50
|
+
#
|
51
|
+
# # good
|
52
|
+
# define_negated_matcher :not_change, :change
|
53
|
+
# expect { run }
|
54
|
+
# .to not_change(Foo, :bar)
|
55
|
+
# .and not_change(Foo, :baz)
|
56
|
+
# expect { run }
|
57
|
+
# .to not_change { Foo.bar }
|
58
|
+
# .and not_change { Foo.baz }
|
59
|
+
#
|
60
|
+
class ChangeByZero < Base
|
61
|
+
extend AutoCorrector
|
62
|
+
include RangeHelp
|
63
|
+
|
64
|
+
MSG = 'Prefer `not_to change` over `to %<method>s.by(0)`.'
|
65
|
+
MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
|
66
|
+
'over `%<method>s.by(0)`.'
|
67
|
+
CHANGE_METHODS = Set[:change, :a_block_changing, :changing].freeze
|
68
|
+
RESTRICT_ON_SEND = CHANGE_METHODS.freeze
|
69
|
+
|
70
|
+
# @!method expect_change_with_arguments(node)
|
71
|
+
def_node_matcher :expect_change_with_arguments, <<~PATTERN
|
72
|
+
(send
|
73
|
+
$(send nil? CHANGE_METHODS ...) :by
|
74
|
+
(int 0))
|
75
|
+
PATTERN
|
76
|
+
|
77
|
+
# @!method expect_change_with_block(node)
|
78
|
+
def_node_matcher :expect_change_with_block, <<~PATTERN
|
79
|
+
(send
|
80
|
+
(block
|
81
|
+
$(send nil? CHANGE_METHODS)
|
82
|
+
(args)
|
83
|
+
(send (...) _)) :by
|
84
|
+
(int 0))
|
85
|
+
PATTERN
|
86
|
+
|
87
|
+
# @!method change_nodes(node)
|
88
|
+
def_node_search :change_nodes, <<~PATTERN
|
89
|
+
$(send nil? CHANGE_METHODS ...)
|
90
|
+
PATTERN
|
91
|
+
|
92
|
+
def on_send(node)
|
93
|
+
expect_change_with_arguments(node.parent) do |change|
|
94
|
+
register_offense(node.parent, change)
|
95
|
+
end
|
96
|
+
|
97
|
+
expect_change_with_block(node.parent.parent) do |change|
|
98
|
+
register_offense(node.parent.parent, change)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# rubocop:disable Metrics/MethodLength
|
105
|
+
def register_offense(node, change_node)
|
106
|
+
if compound_expectations?(node)
|
107
|
+
add_offense(node.source_range,
|
108
|
+
message: message_compound(change_node)) do |corrector|
|
109
|
+
autocorrect_compound(corrector, node)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
add_offense(node.source_range,
|
113
|
+
message: message(change_node)) do |corrector|
|
114
|
+
autocorrect(corrector, node, change_node)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
# rubocop:enable Metrics/MethodLength
|
119
|
+
|
120
|
+
def compound_expectations?(node)
|
121
|
+
%i[and or & |].include?(node.parent.method_name)
|
122
|
+
end
|
123
|
+
|
124
|
+
def message(change_node)
|
125
|
+
format(MSG, method: change_node.method_name)
|
126
|
+
end
|
127
|
+
|
128
|
+
def message_compound(change_node)
|
129
|
+
format(MSG_COMPOUND, preferred: preferred_method,
|
130
|
+
method: change_node.method_name)
|
131
|
+
end
|
132
|
+
|
133
|
+
def autocorrect(corrector, node, change_node)
|
134
|
+
corrector.replace(node.parent.loc.selector, 'not_to')
|
135
|
+
corrector.replace(change_node.loc.selector, 'change')
|
136
|
+
range = node.loc.dot.with(end_pos: node.source_range.end_pos)
|
137
|
+
corrector.remove(range)
|
138
|
+
end
|
139
|
+
|
140
|
+
def autocorrect_compound(corrector, node)
|
141
|
+
return unless negated_matcher
|
142
|
+
|
143
|
+
change_nodes(node) do |change_node|
|
144
|
+
corrector.replace(change_node.loc.selector, negated_matcher)
|
145
|
+
insert_operator(corrector, node, change_node)
|
146
|
+
remove_by_zero(corrector, node, change_node)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def insert_operator(corrector, node, change_node)
|
151
|
+
operator = node.right_siblings.first
|
152
|
+
return unless %i[& |].include?(operator)
|
153
|
+
|
154
|
+
corrector.insert_after(
|
155
|
+
replace_node(node, change_node), " #{operator}"
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
def replace_node(node, change_node)
|
160
|
+
expect_change_with_arguments(node) ? change_node : change_node.parent
|
161
|
+
end
|
162
|
+
|
163
|
+
def remove_by_zero(corrector, node, change_node)
|
164
|
+
range = node.loc.dot.with(end_pos: node.source_range.end_pos)
|
165
|
+
if change_node.loc.line == range.line
|
166
|
+
corrector.remove(range)
|
167
|
+
else
|
168
|
+
corrector.remove(
|
169
|
+
range_by_whole_lines(range, include_final_newline: true)
|
170
|
+
)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def negated_matcher
|
175
|
+
cop_config['NegatedMatcher']
|
176
|
+
end
|
177
|
+
|
178
|
+
def preferred_method
|
179
|
+
negated_matcher ? "`#{negated_matcher}`" : 'negated matchers'
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Enforces consistent use of `be_a` or `be_kind_of`.
|
7
|
+
#
|
8
|
+
# @example EnforcedStyle: be_a (default)
|
9
|
+
# # bad
|
10
|
+
# expect(object).to be_kind_of(String)
|
11
|
+
# expect(object).to be_a_kind_of(String)
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# expect(object).to be_a(String)
|
15
|
+
# expect(object).to be_an(String)
|
16
|
+
#
|
17
|
+
# @example EnforcedStyle: be_kind_of
|
18
|
+
# # bad
|
19
|
+
# expect(object).to be_a(String)
|
20
|
+
# expect(object).to be_an(String)
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# expect(object).to be_kind_of(String)
|
24
|
+
# expect(object).to be_a_kind_of(String)
|
25
|
+
#
|
26
|
+
class ClassCheck < Base
|
27
|
+
extend AutoCorrector
|
28
|
+
include ConfigurableEnforcedStyle
|
29
|
+
|
30
|
+
MSG = 'Prefer `%<preferred>s` over `%<current>s`.'
|
31
|
+
|
32
|
+
METHOD_NAMES_FOR_BE_A = ::Set[
|
33
|
+
:be_a,
|
34
|
+
:be_an
|
35
|
+
].freeze
|
36
|
+
|
37
|
+
METHOD_NAMES_FOR_KIND_OF = ::Set[
|
38
|
+
:be_a_kind_of,
|
39
|
+
:be_kind_of
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
PREFERRED_METHOD_NAME_BY_STYLE = {
|
43
|
+
be_a: :be_a,
|
44
|
+
be_kind_of: :be_kind_of
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
RESTRICT_ON_SEND = %i[
|
48
|
+
be_a
|
49
|
+
be_a_kind_of
|
50
|
+
be_an
|
51
|
+
be_kind_of
|
52
|
+
].freeze
|
53
|
+
|
54
|
+
def on_send(node)
|
55
|
+
return unless offending?(node)
|
56
|
+
|
57
|
+
add_offense(
|
58
|
+
node.loc.selector,
|
59
|
+
message: format_message(node)
|
60
|
+
) do |corrector|
|
61
|
+
autocorrect(corrector, node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def autocorrect(corrector, node)
|
68
|
+
corrector.replace(node.loc.selector, preferred_method_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
def format_message(node)
|
72
|
+
format(
|
73
|
+
MSG,
|
74
|
+
current: node.method_name,
|
75
|
+
preferred: preferred_method_name
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def offending?(node)
|
80
|
+
!node.receiver && !preferred_method_name?(node.method_name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def preferred_method_name?(method_name)
|
84
|
+
preferred_method_names.include?(method_name)
|
85
|
+
end
|
86
|
+
|
87
|
+
def preferred_method_name
|
88
|
+
PREFERRED_METHOD_NAME_BY_STYLE[style]
|
89
|
+
end
|
90
|
+
|
91
|
+
def preferred_method_names
|
92
|
+
if style == :be_a
|
93
|
+
METHOD_NAMES_FOR_BE_A
|
94
|
+
else
|
95
|
+
METHOD_NAMES_FOR_KIND_OF
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks where `contain_exactly` is used.
|
7
|
+
#
|
8
|
+
# This cop checks for the following:
|
9
|
+
# - Prefer `match_array` when matching array values.
|
10
|
+
# - Prefer `be_empty` when using `contain_exactly` with no arguments.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# it { is_expected.to contain_exactly(*array1, *array2) }
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# it { is_expected.to match_array(array1 + array2) }
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# it { is_expected.to contain_exactly(content, *array) }
|
21
|
+
#
|
22
|
+
class ContainExactly < Base
|
23
|
+
extend AutoCorrector
|
24
|
+
|
25
|
+
MSG = 'Prefer `match_array` when matching array values.'
|
26
|
+
RESTRICT_ON_SEND = %i[contain_exactly].freeze
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
return if node.arguments.empty?
|
30
|
+
|
31
|
+
check_populated_collection(node)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def check_populated_collection(node)
|
37
|
+
return unless node.each_child_node.all?(&:splat_type?)
|
38
|
+
|
39
|
+
add_offense(node) do |corrector|
|
40
|
+
autocorrect_for_populated_array(node, corrector)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def autocorrect_for_populated_array(node, corrector)
|
45
|
+
arrays = node.arguments.map do |splat_node|
|
46
|
+
splat_node.children.first
|
47
|
+
end
|
48
|
+
corrector.replace(
|
49
|
+
node,
|
50
|
+
"match_array(#{arrays.map(&:source).join(' + ')})"
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# `context` should not be used for specifying methods.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# context '#foo_bar' do
|
11
|
+
# # ...
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# context '.foo_bar' do
|
15
|
+
# # ...
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# describe '#foo_bar' do
|
20
|
+
# # ...
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# describe '.foo_bar' do
|
24
|
+
# # ...
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
class ContextMethod < Base
|
28
|
+
extend AutoCorrector
|
29
|
+
|
30
|
+
MSG = 'Use `describe` for testing methods.'
|
31
|
+
|
32
|
+
# @!method context_method(node)
|
33
|
+
def_node_matcher :context_method, <<~PATTERN
|
34
|
+
(block
|
35
|
+
(send #rspec? :context
|
36
|
+
${(str #method_name?) (dstr (str #method_name?) ...)}
|
37
|
+
...)
|
38
|
+
...)
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
42
|
+
context_method(node) do |context|
|
43
|
+
add_offense(context) do |corrector|
|
44
|
+
corrector.replace(node.send_node.loc.selector, 'describe')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def method_name?(description)
|
52
|
+
description.start_with?('.', '#')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that `context` docstring starts with an allowed prefix.
|
7
|
+
#
|
8
|
+
# The default list of prefixes is minimal. Users are encouraged to tailor
|
9
|
+
# the configuration to meet project needs. Other acceptable prefixes may
|
10
|
+
# include `if`, `unless`, `for`, `before`, `after`, or `during`.
|
11
|
+
# They may consist of multiple words if desired.
|
12
|
+
#
|
13
|
+
# @see http://www.betterspecs.org/#contexts
|
14
|
+
#
|
15
|
+
# @example `Prefixes` configuration
|
16
|
+
# # .rubocop.yml
|
17
|
+
# # RSpec/ContextWording:
|
18
|
+
# # Prefixes:
|
19
|
+
# # - when
|
20
|
+
# # - with
|
21
|
+
# # - without
|
22
|
+
# # - if
|
23
|
+
# # - unless
|
24
|
+
# # - for
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# # bad
|
28
|
+
# context 'the display name not present' do
|
29
|
+
# # ...
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # good
|
33
|
+
# context 'when the display name is not present' do
|
34
|
+
# # ...
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# This cop can be customized allowed context description pattern
|
38
|
+
# with `AllowedPatterns`. By default, there are no checking by pattern.
|
39
|
+
#
|
40
|
+
# @example `AllowedPatterns` configuration
|
41
|
+
#
|
42
|
+
# # .rubocop.yml
|
43
|
+
# # RSpec/ContextWording:
|
44
|
+
# # AllowedPatterns:
|
45
|
+
# # - とき$
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# # bad
|
49
|
+
# context '条件を満たす' do
|
50
|
+
# # ...
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# # good
|
54
|
+
# context '条件を満たすとき' do
|
55
|
+
# # ...
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
class ContextWording < Base
|
59
|
+
include AllowedPattern
|
60
|
+
|
61
|
+
MSG = 'Context description should match %<patterns>s.'
|
62
|
+
|
63
|
+
# @!method context_wording(node)
|
64
|
+
def_node_matcher :context_wording, <<~PATTERN
|
65
|
+
(block (send #rspec? { :context :shared_context } $({str dstr xstr} ...) ...) ...)
|
66
|
+
PATTERN
|
67
|
+
|
68
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
69
|
+
context_wording(node) do |context|
|
70
|
+
if bad_pattern?(context)
|
71
|
+
message = format(MSG, patterns: expect_patterns)
|
72
|
+
add_offense(context, message: message)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def allowed_patterns
|
80
|
+
super + prefix_regexes
|
81
|
+
end
|
82
|
+
|
83
|
+
def prefix_regexes
|
84
|
+
@prefix_regexes ||= prefixes.map { |pre| /^#{Regexp.escape(pre)}\b/ }
|
85
|
+
end
|
86
|
+
|
87
|
+
def bad_pattern?(node)
|
88
|
+
return false if allowed_patterns.empty?
|
89
|
+
|
90
|
+
!matches_allowed_pattern?(description(node))
|
91
|
+
end
|
92
|
+
|
93
|
+
def description(context)
|
94
|
+
if context.xstr_type?
|
95
|
+
context.value.value
|
96
|
+
else
|
97
|
+
context.value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def expect_patterns
|
102
|
+
inspected = allowed_patterns.map do |pattern|
|
103
|
+
pattern.inspect.gsub(/\A"|"\z/, '/')
|
104
|
+
end
|
105
|
+
return inspected.first if inspected.size == 1
|
106
|
+
|
107
|
+
inspected << "or #{inspected.pop}"
|
108
|
+
inspected.join(', ')
|
109
|
+
end
|
110
|
+
|
111
|
+
def prefixes
|
112
|
+
Array(cop_config.fetch('Prefixes', []))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -3,7 +3,19 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
|
-
# Check that the first argument to the top
|
6
|
+
# Check that the first argument to the top-level describe is a constant.
|
7
|
+
#
|
8
|
+
# It can be configured to ignore strings when certain metadata is passed.
|
9
|
+
#
|
10
|
+
# Ignores Rails and Aruba `type` metadata by default.
|
11
|
+
#
|
12
|
+
# @example `IgnoredMetadata` configuration
|
13
|
+
# # .rubocop.yml
|
14
|
+
# # RSpec/DescribeClass:
|
15
|
+
# # IgnoredMetadata:
|
16
|
+
# # type:
|
17
|
+
# # - request
|
18
|
+
# # - controller
|
7
19
|
#
|
8
20
|
# @example
|
9
21
|
# # bad
|
@@ -12,41 +24,60 @@ module RuboCop
|
|
12
24
|
#
|
13
25
|
# # good
|
14
26
|
# describe TestedClass do
|
27
|
+
# subject { described_class }
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# describe 'TestedClass::VERSION' do
|
31
|
+
# subject { Object.const_get(self.class.description) }
|
15
32
|
# end
|
16
33
|
#
|
17
34
|
# describe "A feature example", type: :feature do
|
18
35
|
# end
|
19
|
-
|
20
|
-
|
36
|
+
#
|
37
|
+
class DescribeClass < Base
|
38
|
+
include TopLevelGroup
|
21
39
|
|
22
|
-
MSG = 'The first argument to describe should be '\
|
23
|
-
'the class or module being tested.'
|
40
|
+
MSG = 'The first argument to describe should be ' \
|
41
|
+
'the class or module being tested.'
|
24
42
|
|
25
|
-
|
26
|
-
|
43
|
+
# @!method example_group_with_ignored_metadata?(node)
|
44
|
+
def_node_matcher :example_group_with_ignored_metadata?, <<~PATTERN
|
45
|
+
(send #rspec? :describe ... (hash <#ignored_metadata? ...>))
|
27
46
|
PATTERN
|
28
47
|
|
29
|
-
|
30
|
-
|
31
|
-
!const
|
32
|
-
...
|
33
|
-
(hash $...))
|
48
|
+
# @!method not_a_const_described(node)
|
49
|
+
def_node_matcher :not_a_const_described, <<~PATTERN
|
50
|
+
(send #rspec? :describe $[!const !#string_constant?] ...)
|
34
51
|
PATTERN
|
35
52
|
|
36
|
-
|
37
|
-
|
38
|
-
(sym
|
39
|
-
(sym {:request :feature :routing :view}))
|
53
|
+
# @!method sym_pair(node)
|
54
|
+
def_node_matcher :sym_pair, <<~PATTERN
|
55
|
+
(pair $sym $sym)
|
40
56
|
PATTERN
|
41
57
|
|
42
|
-
def
|
43
|
-
return if
|
58
|
+
def on_top_level_group(node)
|
59
|
+
return if example_group_with_ignored_metadata?(node.send_node)
|
44
60
|
|
45
|
-
|
46
|
-
|
61
|
+
not_a_const_described(node.send_node) do |described|
|
62
|
+
add_offense(described)
|
47
63
|
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def ignored_metadata?(node)
|
69
|
+
sym_pair(node) do |key, value|
|
70
|
+
ignored_metadata[key.value.to_s].to_a.include?(value.value.to_s)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def string_constant?(described)
|
75
|
+
described.str_type? &&
|
76
|
+
described.value.match?(/^(?:(?:::)?[A-Z]\w*)+$/)
|
77
|
+
end
|
48
78
|
|
49
|
-
|
79
|
+
def ignored_metadata
|
80
|
+
cop_config['IgnoredMetadata'] || {}
|
50
81
|
end
|
51
82
|
end
|
52
83
|
end
|
@@ -16,20 +16,35 @@ module RuboCop
|
|
16
16
|
#
|
17
17
|
# describe MyClass, '.my_class_method' do
|
18
18
|
# end
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
#
|
20
|
+
class DescribeMethod < Base
|
21
|
+
include TopLevelGroup
|
22
|
+
|
23
|
+
MSG = 'The second argument to describe should be the method ' \
|
24
|
+
"being tested. '#instance' or '.class'."
|
25
|
+
|
26
|
+
# @!method second_string_literal_argument(node)
|
27
|
+
def_node_matcher :second_string_literal_argument, <<~PATTERN
|
28
|
+
(block
|
29
|
+
(send #rspec? :describe _first_argument ${str dstr} ...)
|
30
|
+
...)
|
31
|
+
PATTERN
|
23
32
|
|
24
|
-
|
25
|
-
|
26
|
-
|
33
|
+
# @!method method_name?(node)
|
34
|
+
def_node_matcher :method_name?, <<~PATTERN
|
35
|
+
{(str #method_name_prefix?) (dstr (str #method_name_prefix?) ...)}
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def on_top_level_group(node)
|
39
|
+
second_string_literal_argument(node) do |argument|
|
40
|
+
add_offense(argument) unless method_name?(argument)
|
41
|
+
end
|
42
|
+
end
|
27
43
|
|
28
|
-
|
29
|
-
return unless second_arg && second_arg.type.equal?(:str)
|
30
|
-
return if METHOD_STRING_MATCHER =~ one(second_arg.children)
|
44
|
+
private
|
31
45
|
|
32
|
-
|
46
|
+
def method_name_prefix?(description)
|
47
|
+
description.start_with?('.', '#')
|
33
48
|
end
|
34
49
|
end
|
35
50
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Avoid describing symbols.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# describe :my_method do
|
11
|
+
# # ...
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# describe '#my_method' do
|
16
|
+
# # ...
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @see https://github.com/rspec/rspec-core/issues/1610
|
20
|
+
class DescribeSymbol < Base
|
21
|
+
MSG = 'Avoid describing symbols.'
|
22
|
+
RESTRICT_ON_SEND = %i[describe].freeze
|
23
|
+
|
24
|
+
# @!method describe_symbol?(node)
|
25
|
+
def_node_matcher :describe_symbol?, <<~PATTERN
|
26
|
+
(send #rspec? :describe $sym ...)
|
27
|
+
PATTERN
|
28
|
+
|
29
|
+
def on_send(node)
|
30
|
+
describe_symbol?(node) do |match|
|
31
|
+
add_offense(match)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|