rubocop-rspec 1.7.0 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (193) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +955 -79
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/MIT-LICENSE.md +1 -2
  5. data/README.md +35 -35
  6. data/config/default.yml +940 -52
  7. data/config/obsoletion.yml +30 -0
  8. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +49 -0
  9. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +49 -0
  10. data/lib/rubocop/cop/rspec/any_instance.rb +10 -13
  11. data/lib/rubocop/cop/rspec/around_block.rb +97 -0
  12. data/lib/rubocop/cop/rspec/base.rb +26 -0
  13. data/lib/rubocop/cop/rspec/be.rb +39 -0
  14. data/lib/rubocop/cop/rspec/be_empty.rb +45 -0
  15. data/lib/rubocop/cop/rspec/be_eq.rb +47 -0
  16. data/lib/rubocop/cop/rspec/be_eql.rb +18 -15
  17. data/lib/rubocop/cop/rspec/be_nil.rb +74 -0
  18. data/lib/rubocop/cop/rspec/before_after_all.rb +45 -0
  19. data/lib/rubocop/cop/rspec/change_by_zero.rb +184 -0
  20. data/lib/rubocop/cop/rspec/class_check.rb +101 -0
  21. data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
  22. data/lib/rubocop/cop/rspec/context_method.rb +57 -0
  23. data/lib/rubocop/cop/rspec/context_wording.rb +117 -0
  24. data/lib/rubocop/cop/rspec/describe_class.rb +52 -21
  25. data/lib/rubocop/cop/rspec/describe_method.rb +26 -11
  26. data/lib/rubocop/cop/rspec/describe_symbol.rb +37 -0
  27. data/lib/rubocop/cop/rspec/described_class.rb +181 -34
  28. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +38 -0
  29. data/lib/rubocop/cop/rspec/dialect.rb +84 -0
  30. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +58 -0
  31. data/lib/rubocop/cop/rspec/empty_example_group.rb +134 -47
  32. data/lib/rubocop/cop/rspec/empty_hook.rb +49 -0
  33. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +82 -0
  34. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +42 -0
  35. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +40 -0
  36. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +82 -0
  37. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +36 -0
  38. data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
  39. data/lib/rubocop/cop/rspec/empty_output.rb +47 -0
  40. data/lib/rubocop/cop/rspec/eq.rb +47 -0
  41. data/lib/rubocop/cop/rspec/example_length.rb +38 -20
  42. data/lib/rubocop/cop/rspec/example_without_description.rb +98 -0
  43. data/lib/rubocop/cop/rspec/example_wording.rb +117 -27
  44. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +110 -0
  45. data/lib/rubocop/cop/rspec/expect_actual.rb +46 -20
  46. data/lib/rubocop/cop/rspec/expect_change.rb +86 -0
  47. data/lib/rubocop/cop/rspec/expect_in_hook.rb +50 -0
  48. data/lib/rubocop/cop/rspec/expect_in_let.rb +42 -0
  49. data/lib/rubocop/cop/rspec/expect_output.rb +50 -0
  50. data/lib/rubocop/cop/rspec/focus.rb +79 -25
  51. data/lib/rubocop/cop/rspec/hook_argument.rb +48 -36
  52. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +81 -0
  53. data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +37 -0
  54. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +68 -0
  55. data/lib/rubocop/cop/rspec/implicit_expect.rb +100 -0
  56. data/lib/rubocop/cop/rspec/implicit_subject.rb +167 -0
  57. data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
  58. data/lib/rubocop/cop/rspec/instance_spy.rb +74 -0
  59. data/lib/rubocop/cop/rspec/instance_variable.rb +28 -14
  60. data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
  61. data/lib/rubocop/cop/rspec/it_behaves_like.rb +49 -0
  62. data/lib/rubocop/cop/rspec/iterated_expectation.rb +74 -0
  63. data/lib/rubocop/cop/rspec/leading_subject.rb +57 -29
  64. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +127 -0
  65. data/lib/rubocop/cop/rspec/let_before_examples.rb +101 -0
  66. data/lib/rubocop/cop/rspec/let_setup.rb +32 -16
  67. data/lib/rubocop/cop/rspec/match_array.rb +59 -0
  68. data/lib/rubocop/cop/rspec/message_chain.rb +10 -15
  69. data/lib/rubocop/cop/rspec/message_expectation.rb +12 -9
  70. data/lib/rubocop/cop/rspec/message_spies.rb +88 -0
  71. data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
  72. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +35 -0
  73. data/lib/rubocop/cop/rspec/missing_expectation_target_method.rb +54 -0
  74. data/lib/rubocop/cop/rspec/mixin/comments_help.rb +38 -0
  75. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +59 -0
  76. data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
  77. data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
  78. data/lib/rubocop/cop/rspec/mixin/inside_example_group.rb +29 -0
  79. data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
  80. data/lib/rubocop/cop/rspec/mixin/metadata.rb +63 -0
  81. data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
  82. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +39 -0
  83. data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
  84. data/lib/rubocop/cop/rspec/mixin/variable.rb +21 -0
  85. data/lib/rubocop/cop/rspec/multiple_describes.rb +14 -12
  86. data/lib/rubocop/cop/rspec/multiple_expectations.rb +86 -26
  87. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +146 -0
  88. data/lib/rubocop/cop/rspec/multiple_subjects.rb +97 -0
  89. data/lib/rubocop/cop/rspec/named_subject.rb +107 -27
  90. data/lib/rubocop/cop/rspec/nested_groups.rb +84 -47
  91. data/lib/rubocop/cop/rspec/no_expectation_example.rb +102 -0
  92. data/lib/rubocop/cop/rspec/not_to_not.rb +30 -27
  93. data/lib/rubocop/cop/rspec/overwriting_setup.rb +74 -0
  94. data/lib/rubocop/cop/rspec/pending.rb +80 -0
  95. data/lib/rubocop/cop/rspec/pending_without_reason.rb +159 -0
  96. data/lib/rubocop/cop/rspec/predicate_matcher.rb +341 -0
  97. data/lib/rubocop/cop/rspec/receive_counts.rb +89 -0
  98. data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
  99. data/lib/rubocop/cop/rspec/receive_never.rb +41 -0
  100. data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
  101. data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
  102. data/lib/rubocop/cop/rspec/remove_const.rb +39 -0
  103. data/lib/rubocop/cop/rspec/repeated_description.rb +98 -0
  104. data/lib/rubocop/cop/rspec/repeated_example.rb +53 -0
  105. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +100 -0
  106. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
  107. data/lib/rubocop/cop/rspec/repeated_include_example.rb +105 -0
  108. data/lib/rubocop/cop/rspec/repeated_subject_call.rb +125 -0
  109. data/lib/rubocop/cop/rspec/return_from_stub.rb +169 -0
  110. data/lib/rubocop/cop/rspec/scattered_let.rb +59 -0
  111. data/lib/rubocop/cop/rspec/scattered_setup.rb +92 -0
  112. data/lib/rubocop/cop/rspec/shared_context.rb +107 -0
  113. data/lib/rubocop/cop/rspec/shared_examples.rb +125 -0
  114. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +93 -0
  115. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
  116. data/lib/rubocop/cop/rspec/sort_metadata.rb +71 -0
  117. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
  118. data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
  119. data/lib/rubocop/cop/rspec/stubbed_mock.rb +176 -0
  120. data/lib/rubocop/cop/rspec/subject_declaration.rb +46 -0
  121. data/lib/rubocop/cop/rspec/subject_stub.rb +93 -74
  122. data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +69 -0
  123. data/lib/rubocop/cop/rspec/unspecified_exception.rb +67 -0
  124. data/lib/rubocop/cop/rspec/variable_definition.rb +77 -0
  125. data/lib/rubocop/cop/rspec/variable_name.rb +68 -0
  126. data/lib/rubocop/cop/rspec/verified_double_reference.rb +111 -0
  127. data/lib/rubocop/cop/rspec/verified_doubles.rb +28 -14
  128. data/lib/rubocop/cop/rspec/void_expect.rb +60 -0
  129. data/lib/rubocop/cop/rspec/yield.rb +82 -0
  130. data/lib/rubocop/cop/rspec_cops.rb +112 -0
  131. data/lib/rubocop/rspec/align_let_brace.rb +63 -0
  132. data/lib/rubocop/rspec/concept.rb +33 -0
  133. data/lib/rubocop/rspec/config_formatter.rb +27 -4
  134. data/lib/rubocop/rspec/cop/generator.rb +25 -0
  135. data/lib/rubocop/rspec/corrector/move_node.rb +51 -0
  136. data/lib/rubocop/rspec/description_extractor.rb +60 -18
  137. data/lib/rubocop/rspec/example.rb +37 -0
  138. data/lib/rubocop/rspec/example_group.rb +67 -0
  139. data/lib/rubocop/rspec/hook.rb +79 -0
  140. data/lib/rubocop/rspec/inject.rb +3 -1
  141. data/lib/rubocop/rspec/language.rb +184 -41
  142. data/lib/rubocop/rspec/node.rb +19 -0
  143. data/lib/rubocop/rspec/shared_contexts/default_rspec_language_config_context.rb +29 -0
  144. data/lib/rubocop/rspec/version.rb +1 -1
  145. data/lib/rubocop/rspec/wording.rb +61 -19
  146. data/lib/rubocop/rspec.rb +6 -2
  147. data/lib/rubocop-rspec.rb +45 -34
  148. metadata +130 -195
  149. data/Gemfile +0 -13
  150. data/Rakefile +0 -48
  151. data/lib/rubocop/cop/rspec/file_path.rb +0 -83
  152. data/lib/rubocop/rspec/language/node_pattern.rb +0 -16
  153. data/lib/rubocop/rspec/spec_only.rb +0 -61
  154. data/lib/rubocop/rspec/top_level_describe.rb +0 -61
  155. data/lib/rubocop/rspec/util.rb +0 -19
  156. data/rubocop-rspec.gemspec +0 -42
  157. data/spec/expect_violation/expectation_spec.rb +0 -85
  158. data/spec/project/changelog_spec.rb +0 -81
  159. data/spec/project/default_config_spec.rb +0 -52
  160. data/spec/project/project_requires_spec.rb +0 -8
  161. data/spec/rubocop/cop/rspec/any_instance_spec.rb +0 -30
  162. data/spec/rubocop/cop/rspec/be_eql_spec.rb +0 -59
  163. data/spec/rubocop/cop/rspec/describe_class_spec.rb +0 -113
  164. data/spec/rubocop/cop/rspec/describe_method_spec.rb +0 -32
  165. data/spec/rubocop/cop/rspec/described_class_spec.rb +0 -219
  166. data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +0 -79
  167. data/spec/rubocop/cop/rspec/example_length_spec.rb +0 -117
  168. data/spec/rubocop/cop/rspec/example_wording_spec.rb +0 -82
  169. data/spec/rubocop/cop/rspec/expect_actual_spec.rb +0 -136
  170. data/spec/rubocop/cop/rspec/file_path_spec.rb +0 -236
  171. data/spec/rubocop/cop/rspec/focus_spec.rb +0 -130
  172. data/spec/rubocop/cop/rspec/hook_argument_spec.rb +0 -189
  173. data/spec/rubocop/cop/rspec/instance_variable_spec.rb +0 -75
  174. data/spec/rubocop/cop/rspec/leading_subject_spec.rb +0 -54
  175. data/spec/rubocop/cop/rspec/let_setup_spec.rb +0 -66
  176. data/spec/rubocop/cop/rspec/message_chain_spec.rb +0 -21
  177. data/spec/rubocop/cop/rspec/message_expectation_spec.rb +0 -63
  178. data/spec/rubocop/cop/rspec/multiple_describes_spec.rb +0 -28
  179. data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +0 -84
  180. data/spec/rubocop/cop/rspec/named_subject_spec.rb +0 -62
  181. data/spec/rubocop/cop/rspec/nested_groups_spec.rb +0 -55
  182. data/spec/rubocop/cop/rspec/not_to_not_spec.rb +0 -57
  183. data/spec/rubocop/cop/rspec/subject_stub_spec.rb +0 -183
  184. data/spec/rubocop/cop/rspec/verified_doubles_spec.rb +0 -71
  185. data/spec/rubocop/rspec/config_formatter_spec.rb +0 -48
  186. data/spec/rubocop/rspec/description_extractor_spec.rb +0 -35
  187. data/spec/rubocop/rspec/language/selector_set_spec.rb +0 -29
  188. data/spec/rubocop/rspec/spec_only_spec.rb +0 -97
  189. data/spec/rubocop/rspec/util/one_spec.rb +0 -21
  190. data/spec/rubocop/rspec/wording_spec.rb +0 -44
  191. data/spec/shared/rspec_only_cop_behavior.rb +0 -68
  192. data/spec/spec_helper.rb +0 -41
  193. 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 level describe is a constant.
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
- class DescribeClass < Cop
20
- include RuboCop::RSpec::SpecOnly, RuboCop::RSpec::TopLevelDescribe
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.'.freeze
40
+ MSG = 'The first argument to describe should be ' \
41
+ 'the class or module being tested.'
24
42
 
25
- def_node_matcher :valid_describe?, <<-PATTERN
26
- {(send {(const nil :RSpec) nil} :describe const ...) (send nil :describe)}
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
- def_node_matcher :describe_with_metadata, <<-PATTERN
30
- (send {(const nil :RSpec) nil} :describe
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
- def_node_matcher :rails_metadata?, <<-PATTERN
37
- (pair
38
- (sym :type)
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 on_top_level_describe(node, args)
43
- return if valid_describe?(node)
58
+ def on_top_level_group(node)
59
+ return if example_group_with_ignored_metadata?(node.send_node)
44
60
 
45
- describe_with_metadata(node) do |pairs|
46
- return if pairs.any?(&method(:rails_metadata?))
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
- add_offense(args.first, :expression)
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
- class DescribeMethod < Cop
20
- include RuboCop::RSpec::SpecOnly,
21
- RuboCop::RSpec::TopLevelDescribe,
22
- RuboCop::RSpec::Util
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
- MESSAGE = 'The second argument to describe should be the method ' \
25
- "being tested. '#instance' or '.class'".freeze
26
- METHOD_STRING_MATCHER = /^[\#\.].+/
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
- def on_top_level_describe(_node, (_, second_arg))
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
- add_offense(second_arg, :expression, MESSAGE)
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