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.
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
@@ -6,10 +6,14 @@ module RuboCop
6
6
  # Checks that tests use `described_class`.
7
7
  #
8
8
  # If the first argument of describe is a class, the class is exposed to
9
- # each example via described_class - this should be used instead of
10
- # repeating the class.
9
+ # each example via described_class.
11
10
  #
12
- # @example
11
+ # This cop can be configured using the `EnforcedStyle`, `SkipBlocks`
12
+ # and `OnlyStaticConstants` options.
13
+ # `OnlyStaticConstants` is only relevant when `EnforcedStyle` is
14
+ # `described_class`.
15
+ #
16
+ # @example `EnforcedStyle: described_class` (default)
13
17
  # # bad
14
18
  # describe MyClass do
15
19
  # subject { MyClass.do_something }
@@ -19,64 +23,207 @@ module RuboCop
19
23
  # describe MyClass do
20
24
  # subject { described_class.do_something }
21
25
  # end
22
- class DescribedClass < Cop
23
- include RuboCop::RSpec::SpecOnly, RuboCop::RSpec::TopLevelDescribe
24
-
25
- DESCRIBED_CLASS = 'described_class'.freeze
26
- MSG = "Use `#{DESCRIBED_CLASS}` instead of `%s`".freeze
26
+ #
27
+ # @example `OnlyStaticConstants: true` (default)
28
+ # # good
29
+ # describe MyClass do
30
+ # subject { MyClass::CONSTANT }
31
+ # end
32
+ #
33
+ # @example `OnlyStaticConstants: false`
34
+ # # bad
35
+ # describe MyClass do
36
+ # subject { MyClass::CONSTANT }
37
+ # end
38
+ #
39
+ # @example `EnforcedStyle: explicit`
40
+ # # bad
41
+ # describe MyClass do
42
+ # subject { described_class.do_something }
43
+ # end
44
+ #
45
+ # # good
46
+ # describe MyClass do
47
+ # subject { MyClass.do_something }
48
+ # end
49
+ #
50
+ # There's a known caveat with rspec-rails's `controller` helper that
51
+ # runs its block in a different context, and `described_class` is not
52
+ # available to it. `SkipBlocks` option excludes detection in all
53
+ # non-RSpec related blocks.
54
+ #
55
+ # To narrow down this setting to only a specific directory, it is
56
+ # possible to use an overriding configuration file local to that
57
+ # directory.
58
+ #
59
+ # @example `SkipBlocks: true`
60
+ # # spec/controllers/.rubocop.yml
61
+ # # RSpec/DescribedClass:
62
+ # # SkipBlocks: true
63
+ #
64
+ # # acceptable
65
+ # describe MyConcern do
66
+ # controller(ApplicationController) do
67
+ # include MyConcern
68
+ # end
69
+ # end
70
+ #
71
+ class DescribedClass < Base # rubocop:disable Metrics/ClassLength
72
+ extend AutoCorrector
73
+ include ConfigurableEnforcedStyle
74
+ include Namespace
27
75
 
28
- RSPEC_BLOCK_METHODS = RuboCop::RSpec::Language::ALL.to_node_pattern
76
+ DESCRIBED_CLASS = 'described_class'
77
+ MSG = 'Use `%<replacement>s` instead of `%<src>s`.'
29
78
 
30
- def_node_matcher :common_instance_exec_closure?, <<-PATTERN
31
- (block (send (const nil {:Class :Module}) :new ...) ...)
79
+ # @!method common_instance_exec_closure?(node)
80
+ def_node_matcher :common_instance_exec_closure?, <<~PATTERN
81
+ (block (send (const nil? {:Class :Module :Struct}) :new ...) ...)
32
82
  PATTERN
33
83
 
34
- def_node_matcher :rspec_block?, <<-PATTERN
35
- (block (send nil {#{RSPEC_BLOCK_METHODS}} ...) ...)
36
- PATTERN
84
+ # @!method rspec_block?(node)
85
+ def_node_matcher :rspec_block?,
86
+ '({block numblock} (send #rspec? #ALL.all ...) ...)'
37
87
 
88
+ # @!method scope_changing_syntax?(node)
38
89
  def_node_matcher :scope_changing_syntax?, '{def class module}'
39
90
 
40
- def on_block(node)
41
- describe, described_class, body = described_constant(node)
42
- return unless top_level_describe?(describe)
91
+ # @!method described_constant(node)
92
+ def_node_matcher :described_constant, <<~PATTERN
93
+ (block (send _ :describe $(const ...) ...) (args) $_)
94
+ PATTERN
95
+
96
+ # @!method contains_described_class?(node)
97
+ def_node_search :contains_described_class?,
98
+ '(send nil? :described_class)'
43
99
 
44
- find_constant_usage(body, described_class) do |match|
45
- add_offense(match, :expression, format(MSG, match.const_name))
46
- end
47
- end
100
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
101
+ # In case the explicit style is used, we need to remember what's
102
+ # being described.
103
+ @described_class, body = described_constant(node)
104
+
105
+ return unless body
48
106
 
49
- def autocorrect(node)
50
- lambda do |corrector|
51
- corrector.replace(node.loc.expression, DESCRIBED_CLASS)
107
+ find_usage(body) do |match|
108
+ msg = message(match.const_name)
109
+ add_offense(match, message: msg) do |corrector|
110
+ autocorrect(corrector, match)
111
+ end
52
112
  end
53
113
  end
54
114
 
55
115
  private
56
116
 
57
- def find_constant_usage(node, described_class, &block)
58
- yield(node) if node.eql?(described_class)
117
+ def autocorrect(corrector, match)
118
+ replacement = if style == :described_class
119
+ DESCRIBED_CLASS
120
+ else
121
+ @described_class.const_name
122
+ end
123
+
124
+ corrector.replace(match, replacement)
125
+ end
126
+
127
+ def find_usage(node, &block)
128
+ yield(node) if offensive?(node)
129
+ return if scope_change?(node) || allowed?(node)
59
130
 
60
- return unless node.instance_of?(Node)
61
- return if scope_change?(node) || node.const_type?
131
+ node.each_child_node do |child|
132
+ find_usage(child, &block)
133
+ end
134
+ end
135
+
136
+ def allowed?(node)
137
+ node.const_type? && only_static_constants?
138
+ end
62
139
 
63
- node.children.each do |child|
64
- find_constant_usage(child, described_class, &block)
140
+ def message(offense)
141
+ if style == :described_class
142
+ format(MSG, replacement: DESCRIBED_CLASS, src: offense)
143
+ else
144
+ format(MSG, replacement: @described_class.const_name,
145
+ src: DESCRIBED_CLASS)
65
146
  end
66
147
  end
67
148
 
68
149
  def scope_change?(node)
69
- scope_changing_syntax?(node) ||
150
+ scope_changing_syntax?(node) ||
70
151
  common_instance_exec_closure?(node) ||
71
152
  skippable_block?(node)
72
153
  end
73
154
 
74
155
  def skippable_block?(node)
75
- node.block_type? && !rspec_block?(node) && skip_blocks?
156
+ node.block_type? && !rspec_block?(node) && cop_config['SkipBlocks']
157
+ end
158
+
159
+ def only_static_constants?
160
+ cop_config.fetch('OnlyStaticConstants', true)
161
+ end
162
+
163
+ def offensive?(node)
164
+ if style == :described_class
165
+ offensive_described_class?(node)
166
+ else
167
+ node.send_type? && node.method?(:described_class)
168
+ end
169
+ end
170
+
171
+ def offensive_described_class?(node)
172
+ return false unless node.const_type?
173
+
174
+ # E.g. `described_class::CONSTANT`
175
+ return false if contains_described_class?(node)
176
+
177
+ nearest_described_class, = node.each_ancestor(:block)
178
+ .map { |ancestor| described_constant(ancestor) }.find(&:itself)
179
+
180
+ return false if nearest_described_class.equal?(node)
181
+
182
+ full_const_name(nearest_described_class) == full_const_name(node)
183
+ end
184
+
185
+ def full_const_name(node)
186
+ symbolized_namespace = namespace(node).map(&:to_sym)
187
+ collapse_namespace(symbolized_namespace, const_name(node))
76
188
  end
77
189
 
78
- def skip_blocks?
79
- cop_config['SkipBlocks'].equal?(true)
190
+ # @param namespace [Array<Symbol>]
191
+ # @param const [Array<Symbol>]
192
+ # @return [Array<Symbol>]
193
+ # @example
194
+ # # nil represents base constant
195
+ # collapse_namespace([], [:C]) # => [:C]
196
+ # collapse_namespace([:A, :B], [:C]) # => [:A, :B, :C]
197
+ # collapse_namespace([:A, :B], [:B, :C]) # => [:A, :B, :C]
198
+ # collapse_namespace([:A, :B], [nil, :C]) # => [nil, :C]
199
+ # collapse_namespace([:A, :B], [nil, :B, :C]) # => [nil, :B, :C]
200
+ def collapse_namespace(namespace, const)
201
+ return const if namespace.empty? || const.first.nil?
202
+
203
+ start = [0, (namespace.length - const.length)].max
204
+ max = namespace.length
205
+ intersection = (start..max).find do |shift|
206
+ namespace[shift, max - shift] == const[0, max - shift]
207
+ end
208
+ [*namespace[0, intersection], *const]
209
+ end
210
+
211
+ # @param node [RuboCop::AST::Node]
212
+ # @return [Array<Symbol>]
213
+ # @example
214
+ # const_name(s(:const, nil, :C)) # => [:C]
215
+ # const_name(s(:const, s(:const, nil, :M), :C)) # => [:M, :C]
216
+ # const_name(s(:const, s(:cbase), :C)) # => [nil, :C]
217
+ def const_name(node)
218
+ namespace = node.namespace
219
+ name = node.short_name
220
+ if !namespace
221
+ [name]
222
+ elsif namespace.const_type?
223
+ [*const_name(namespace), name]
224
+ elsif %i[lvar cbase send].include?(namespace.type)
225
+ [nil, name]
226
+ end
80
227
  end
81
228
  end
82
229
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Avoid opening modules and defining specs within them.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # module MyModule
11
+ # RSpec.describe MyClass do
12
+ # # ...
13
+ # end
14
+ # end
15
+ #
16
+ # # good
17
+ # RSpec.describe MyModule::MyClass do
18
+ # # ...
19
+ # end
20
+ #
21
+ # @see https://github.com/rubocop/rubocop-rspec/issues/735
22
+ class DescribedClassModuleWrapping < Base
23
+ MSG = 'Avoid opening modules and defining specs within them.'
24
+
25
+ # @!method include_rspec_blocks?(node)
26
+ def_node_search :include_rspec_blocks?, <<~PATTERN
27
+ ({block numblock} (send #explicit_rspec? #ExampleGroups.all ...) ...)
28
+ PATTERN
29
+
30
+ def on_module(node)
31
+ return unless include_rspec_blocks?(node)
32
+
33
+ add_offense(node)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Enforces custom RSpec dialects.
7
+ #
8
+ # A dialect can be based on the following RSpec methods:
9
+ #
10
+ # - describe, context, feature, example_group
11
+ # - xdescribe, xcontext, xfeature
12
+ # - fdescribe, fcontext, ffeature
13
+ # - shared_examples, shared_examples_for, shared_context
14
+ # - it, specify, example, scenario, its
15
+ # - fit, fspecify, fexample, fscenario, focus
16
+ # - xit, xspecify, xexample, xscenario, skip
17
+ # - pending
18
+ # - prepend_before, before, append_before,
19
+ # - around
20
+ # - prepend_after, after, append_after
21
+ # - let, let!
22
+ # - subject, subject!
23
+ # - expect, is_expected, expect_any_instance_of
24
+ #
25
+ # By default all of the RSpec methods and aliases are allowed. By setting
26
+ # a config like:
27
+ #
28
+ # RSpec/Dialect:
29
+ # PreferredMethods:
30
+ # context: describe
31
+ #
32
+ # If you were previously using the `RSpec/Capybara/FeatureMethods` cop and
33
+ # want to keep disabling all Capybara-specific methods that have the same
34
+ # native RSpec method (e.g. are just aliases), use the following config:
35
+ #
36
+ # RSpec/Dialect:
37
+ # PreferredMethods:
38
+ # background: :before
39
+ # scenario: :it
40
+ # xscenario: :xit
41
+ # given: :let
42
+ # given!: :let!
43
+ # feature: :describe
44
+ #
45
+ # You can expect the following behavior:
46
+ #
47
+ # @example
48
+ # # bad
49
+ # context 'display name presence' do
50
+ # # ...
51
+ # end
52
+ #
53
+ # # good
54
+ # describe 'display name presence' do
55
+ # # ...
56
+ # end
57
+ #
58
+ class Dialect < Base
59
+ extend AutoCorrector
60
+ include MethodPreference
61
+
62
+ MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
63
+
64
+ # @!method rspec_method?(node)
65
+ def_node_matcher :rspec_method?, '(send #rspec? #ALL.all ...)'
66
+
67
+ def on_send(node)
68
+ return unless rspec_method?(node)
69
+ return unless preferred_methods[node.method_name]
70
+
71
+ msg = format(MSG, prefer: preferred_method(node.method_name),
72
+ current: node.method_name)
73
+
74
+ add_offense(node, message: msg) do |corrector|
75
+ current = node.loc.selector
76
+ preferred = preferred_method(current.source)
77
+
78
+ corrector.replace(current, preferred)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Avoid duplicated metadata.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe 'Something', :a, :a
11
+ #
12
+ # # good
13
+ # describe 'Something', :a
14
+ class DuplicatedMetadata < Base
15
+ extend AutoCorrector
16
+
17
+ include Metadata
18
+ include RangeHelp
19
+
20
+ MSG = 'Avoid duplicated metadata.'
21
+
22
+ def on_metadata(symbols, _hash)
23
+ symbols.each do |symbol|
24
+ on_metadata_symbol(symbol)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def on_metadata_symbol(node)
31
+ return unless duplicated?(node)
32
+
33
+ add_offense(node) do |corrector|
34
+ autocorrect(corrector, node)
35
+ end
36
+ end
37
+
38
+ def autocorrect(corrector, node)
39
+ corrector.remove(
40
+ range_with_surrounding_comma(
41
+ range_with_surrounding_space(
42
+ node.source_range,
43
+ side: :left
44
+ ),
45
+ :left
46
+ )
47
+ )
48
+ end
49
+
50
+ def duplicated?(node)
51
+ node.left_siblings.any? do |sibling|
52
+ sibling.eql?(node)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -5,10 +5,7 @@ module RuboCop
5
5
  module RSpec
6
6
  # Checks if an example group does not include any tests.
7
7
  #
8
- # This cop is configurable using the `CustomIncludeMethods` option
9
- #
10
8
  # @example usage
11
- #
12
9
  # # bad
13
10
  # describe Bacon do
14
11
  # let(:bacon) { Bacon.new(chunkiness) }
@@ -33,66 +30,156 @@ module RuboCop
33
30
  # end
34
31
  # end
35
32
  #
36
- # @example configuration
37
- #
38
- # # .rubocop.yml
39
- # RSpec/EmptyExampleGroup:
40
- # CustomIncludeMethods:
41
- # - include_tests
42
- #
43
- # # spec_helper.rb
44
- # RSpec.configure do |config|
45
- # config.alias_it_behaves_like_to(:include_tests)
46
- # end
47
- #
48
- # # bacon_spec.rb
33
+ # # good
49
34
  # describe Bacon do
50
- # let(:bacon) { Bacon.new(chunkiness) }
51
- # let(:chunkiness) { false }
52
- #
53
- # context 'extra chunky' do # not flagged by rubocop
54
- # let(:chunkiness) { true }
55
- #
56
- # include_tests 'shared tests'
57
- # end
35
+ # pending 'will add tests later'
58
36
  # end
59
37
  #
60
- class EmptyExampleGroup < Cop
61
- include RuboCop::RSpec::SpecOnly,
62
- RuboCop::RSpec::Language,
63
- RuboCop::RSpec::Language::NodePattern
38
+ class EmptyExampleGroup < Base
39
+ extend AutoCorrector
40
+
41
+ include RangeHelp
42
+
43
+ MSG = 'Empty example group detected.'
44
+
45
+ # @!method example_group_body(node)
46
+ # Match example group blocks and yield their body
47
+ #
48
+ # @example source that matches
49
+ # describe 'example group' do
50
+ # it { is_expected.to be }
51
+ # end
52
+ #
53
+ # @param node [RuboCop::AST::Node]
54
+ # @yield [RuboCop::AST::Node] example group body
55
+ def_node_matcher :example_group_body, <<~PATTERN
56
+ (block (send #rspec? #ExampleGroups.all ...) args $_)
57
+ PATTERN
64
58
 
65
- MSG = 'Empty example group detected.'.freeze
59
+ # @!method example_or_group_or_include?(node)
60
+ # Match examples, example groups and includes
61
+ #
62
+ # @example source that matches
63
+ # it { is_expected.to fly }
64
+ # describe('non-empty example groups too') { }
65
+ # it_behaves_like 'an animal'
66
+ # it_behaves_like('a cat') { let(:food) { 'milk' } }
67
+ # it_has_root_access
68
+ # skip
69
+ # it 'will be implemented later'
70
+ #
71
+ # @param node [RuboCop::AST::Node]
72
+ # @return [Array<RuboCop::AST::Node>] matching nodes
73
+ def_node_matcher :example_or_group_or_include?, <<~PATTERN
74
+ {
75
+ (block
76
+ (send #rspec? {#Examples.all #ExampleGroups.all #Includes.all} ...)
77
+ ...)
78
+ (send nil? {#Examples.all #Includes.all} ...)
79
+ }
80
+ PATTERN
81
+
82
+ # @!method examples_inside_block?(node)
83
+ # Match examples defined inside a block which is not a hook
84
+ #
85
+ # @example source that matches
86
+ # %w(r g b).each do |color|
87
+ # it { is_expected.to have_color(color) }
88
+ # end
89
+ #
90
+ # @example source that does not match
91
+ # before do
92
+ # it { is_expected.to fall_into_oblivion }
93
+ # end
94
+ #
95
+ # @param node [RuboCop::AST::Node]
96
+ # @return [Array<RuboCop::AST::Node>] matching nodes
97
+ def_node_matcher :examples_inside_block?, <<~PATTERN
98
+ (block !(send nil? #Hooks.all ...) _ #examples?)
99
+ PATTERN
100
+
101
+ # @!method examples_directly_or_in_block?(node)
102
+ # Match examples or examples inside blocks
103
+ #
104
+ # @example source that matches
105
+ # it { expect(drink).to be_cold }
106
+ # context('when winter') { it { expect(drink).to be_hot } }
107
+ # (1..5).each { |divisor| it { is_expected.to divide_by(divisor) } }
108
+ #
109
+ # @param node [RuboCop::AST::Node]
110
+ # @return [Array<RuboCop::AST::Node>] matching nodes
111
+ def_node_matcher :examples_directly_or_in_block?, <<~PATTERN
112
+ {
113
+ #example_or_group_or_include?
114
+ #examples_inside_block?
115
+ }
116
+ PATTERN
66
117
 
67
- def_node_search :contains_example?, <<-PATTERN
118
+ # @!method examples?(node)
119
+ # Matches examples defined in scopes where they could run
120
+ #
121
+ # @example source that matches
122
+ # it { expect(myself).to be_run }
123
+ # describe { it { i_run_as_well } }
124
+ #
125
+ # @example source that does not match
126
+ # before { it { whatever here won't run anyway } }
127
+ #
128
+ # @param node [RuboCop::AST::Node]
129
+ # @return [Array<RuboCop::AST::Node>] matching nodes
130
+ def_node_matcher :examples?, <<~PATTERN
68
131
  {
69
- (send _ {
70
- #{Examples::ALL.to_node_pattern}
71
- :it_behaves_like
72
- :it_should_behave_like
73
- :include_context
74
- :include_examples
75
- } ...)
76
- (send _ #custom_include? ...)
132
+ #examples_directly_or_in_block?
133
+ (begin <#examples_directly_or_in_block? ...>)
134
+ (begin <#examples_in_branches? ...>)
77
135
  }
78
136
  PATTERN
79
137
 
80
- def on_block(node)
81
- return unless example_group?(node) && !contains_example?(node)
138
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
139
+ return if node.each_ancestor(:def, :defs).any?
140
+ return if node.each_ancestor(:block).any? { |block| example?(block) }
82
141
 
83
- add_offense(node.children.first, :expression)
142
+ example_group_body(node) do |body|
143
+ next unless offensive?(body)
144
+
145
+ add_offense(node.send_node) do |corrector|
146
+ corrector.remove(removed_range(node))
147
+ end
148
+ end
84
149
  end
85
150
 
86
151
  private
87
152
 
88
- def custom_include?(method_name)
89
- custom_include_methods.include?(method_name)
153
+ def offensive?(body)
154
+ return true unless body
155
+ return false if conditionals_with_examples?(body)
156
+
157
+ if body.if_type? || body.case_type?
158
+ !examples_in_branches?(body)
159
+ else
160
+ !examples?(body)
161
+ end
162
+ end
163
+
164
+ def conditionals_with_examples?(body)
165
+ return false unless body.begin_type? || body.case_type?
166
+
167
+ body.each_descendant(:if, :case).any? do |condition_node|
168
+ examples_in_branches?(condition_node)
169
+ end
170
+ end
171
+
172
+ def examples_in_branches?(condition_node)
173
+ return false if !condition_node.if_type? && !condition_node.case_type?
174
+
175
+ condition_node.branches.any? { |branch| examples?(branch) }
90
176
  end
91
177
 
92
- def custom_include_methods
93
- cop_config
94
- .fetch('CustomIncludeMethods', [])
95
- .map(&:to_sym)
178
+ def removed_range(node)
179
+ range_by_whole_lines(
180
+ node.source_range,
181
+ include_final_newline: true
182
+ )
96
183
  end
97
184
  end
98
185
  end