rubocop-rspec 1.40.0 → 1.43.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/config/default.yml +12 -2
  5. data/lib/rubocop-rspec.rb +3 -1
  6. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +12 -19
  7. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +12 -19
  8. data/lib/rubocop/cop/rspec/any_instance.rb +1 -1
  9. data/lib/rubocop/cop/rspec/around_block.rb +1 -1
  10. data/lib/rubocop/cop/rspec/base.rb +74 -0
  11. data/lib/rubocop/cop/rspec/be.rb +2 -2
  12. data/lib/rubocop/cop/rspec/be_eql.rb +6 -6
  13. data/lib/rubocop/cop/rspec/before_after_all.rb +1 -1
  14. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +19 -17
  15. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +14 -12
  16. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +1 -1
  17. data/lib/rubocop/cop/rspec/context_method.rb +7 -9
  18. data/lib/rubocop/cop/rspec/context_wording.rb +3 -3
  19. data/lib/rubocop/cop/rspec/cop.rb +2 -66
  20. data/lib/rubocop/cop/rspec/describe_class.rb +20 -27
  21. data/lib/rubocop/cop/rspec/describe_method.rb +14 -6
  22. data/lib/rubocop/cop/rspec/describe_symbol.rb +2 -2
  23. data/lib/rubocop/cop/rspec/described_class.rb +12 -9
  24. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +1 -1
  25. data/lib/rubocop/cop/rspec/dialect.rb +5 -12
  26. data/lib/rubocop/cop/rspec/empty_example_group.rb +91 -7
  27. data/lib/rubocop/cop/rspec/empty_hook.rb +6 -10
  28. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +5 -7
  29. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +5 -9
  30. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +8 -8
  31. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +5 -9
  32. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +6 -6
  33. data/lib/rubocop/cop/rspec/example_length.rb +1 -1
  34. data/lib/rubocop/cop/rspec/example_without_description.rb +1 -1
  35. data/lib/rubocop/cop/rspec/example_wording.rb +10 -11
  36. data/lib/rubocop/cop/rspec/expect_actual.rb +8 -11
  37. data/lib/rubocop/cop/rspec/expect_change.rb +10 -35
  38. data/lib/rubocop/cop/rspec/expect_in_hook.rb +3 -3
  39. data/lib/rubocop/cop/rspec/expect_output.rb +2 -2
  40. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +24 -21
  41. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +20 -22
  42. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +7 -8
  43. data/lib/rubocop/cop/rspec/file_path.rb +25 -17
  44. data/lib/rubocop/cop/rspec/focus.rb +7 -11
  45. data/lib/rubocop/cop/rspec/hook_argument.rb +16 -23
  46. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +13 -14
  47. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +1 -1
  48. data/lib/rubocop/cop/rspec/implicit_expect.rb +7 -15
  49. data/lib/rubocop/cop/rspec/implicit_subject.rb +16 -11
  50. data/lib/rubocop/cop/rspec/instance_spy.rb +18 -12
  51. data/lib/rubocop/cop/rspec/instance_variable.rb +4 -8
  52. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +3 -6
  53. data/lib/rubocop/cop/rspec/it_behaves_like.rb +5 -6
  54. data/lib/rubocop/cop/rspec/iterated_expectation.rb +1 -1
  55. data/lib/rubocop/cop/rspec/leading_subject.rb +27 -20
  56. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +2 -5
  57. data/lib/rubocop/cop/rspec/let_before_examples.rb +13 -11
  58. data/lib/rubocop/cop/rspec/let_setup.rb +21 -6
  59. data/lib/rubocop/cop/rspec/message_chain.rb +7 -6
  60. data/lib/rubocop/cop/rspec/message_expectation.rb +2 -2
  61. data/lib/rubocop/cop/rspec/message_spies.rb +2 -3
  62. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +1 -1
  63. data/lib/rubocop/cop/rspec/multiple_describes.rb +11 -8
  64. data/lib/rubocop/cop/rspec/multiple_expectations.rb +7 -11
  65. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +148 -0
  66. data/lib/rubocop/cop/rspec/multiple_subjects.rb +18 -19
  67. data/lib/rubocop/cop/rspec/named_subject.rb +2 -2
  68. data/lib/rubocop/cop/rspec/nested_groups.rb +12 -13
  69. data/lib/rubocop/cop/rspec/not_to_not.rb +5 -6
  70. data/lib/rubocop/cop/rspec/overwriting_setup.rb +1 -1
  71. data/lib/rubocop/cop/rspec/pending.rb +1 -1
  72. data/lib/rubocop/cop/rspec/predicate_matcher.rb +32 -69
  73. data/lib/rubocop/cop/rspec/rails/http_status.rb +5 -9
  74. data/lib/rubocop/cop/rspec/receive_counts.rb +15 -17
  75. data/lib/rubocop/cop/rspec/receive_never.rb +12 -12
  76. data/lib/rubocop/cop/rspec/repeated_description.rb +1 -1
  77. data/lib/rubocop/cop/rspec/repeated_example.rb +2 -2
  78. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +1 -1
  79. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +1 -1
  80. data/lib/rubocop/cop/rspec/return_from_stub.rb +12 -22
  81. data/lib/rubocop/cop/rspec/scattered_let.rb +8 -11
  82. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  83. data/lib/rubocop/cop/rspec/shared_context.rb +8 -21
  84. data/lib/rubocop/cop/rspec/shared_examples.rb +6 -9
  85. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +15 -18
  86. data/lib/rubocop/cop/rspec/subject_stub.rb +5 -11
  87. data/lib/rubocop/cop/rspec/unspecified_exception.rb +1 -1
  88. data/lib/rubocop/cop/rspec/variable_definition.rb +6 -6
  89. data/lib/rubocop/cop/rspec/variable_name.rb +28 -9
  90. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
  91. data/lib/rubocop/cop/rspec/void_expect.rb +1 -1
  92. data/lib/rubocop/cop/rspec/yield.rb +14 -11
  93. data/lib/rubocop/cop/rspec_cops.rb +1 -0
  94. data/lib/rubocop/rspec/corrector/move_node.rb +7 -5
  95. data/lib/rubocop/rspec/description_extractor.rb +1 -1
  96. data/lib/rubocop/rspec/{blank_line_separation.rb → empty_line_separation.rb} +13 -10
  97. data/lib/rubocop/rspec/example_group.rb +21 -49
  98. data/lib/rubocop/rspec/factory_bot.rb +7 -1
  99. data/lib/rubocop/rspec/language.rb +6 -4
  100. data/lib/rubocop/rspec/language/node_pattern.rb +10 -1
  101. data/lib/rubocop/rspec/top_level_describe.rb +2 -2
  102. data/lib/rubocop/rspec/top_level_group.rb +57 -0
  103. data/lib/rubocop/rspec/variable.rb +1 -1
  104. data/lib/rubocop/rspec/version.rb +1 -1
  105. metadata +29 -11
@@ -40,7 +40,9 @@ module RuboCop
40
40
  # # ...
41
41
  # end
42
42
  # end
43
- class FeatureMethods < Cop
43
+ class FeatureMethods < Base
44
+ extend AutoCorrector
45
+
44
46
  MSG = 'Use `%<replacement>s` instead of `%<method>s`.'
45
47
 
46
48
  # https://git.io/v7Kwr
@@ -53,15 +55,18 @@ module RuboCop
53
55
  feature: :describe
54
56
  }.freeze
55
57
 
58
+ def_node_matcher :capybara_speak,
59
+ SelectorSet.new(MAP.keys).node_pattern_union
60
+
56
61
  def_node_matcher :spec?, <<-PATTERN
57
62
  (block
58
- (send #{RSPEC} {:describe :feature} ...)
63
+ (send #rspec? {:describe :feature} ...)
59
64
  ...)
60
65
  PATTERN
61
66
 
62
67
  def_node_matcher :feature_method, <<-PATTERN
63
68
  (block
64
- $(send #{RSPEC} ${#{MAP.keys.map(&:inspect).join(' ')}} ...)
69
+ $(send #rspec? $#capybara_speak ...)
65
70
  ...)
66
71
  PATTERN
67
72
 
@@ -71,18 +76,15 @@ module RuboCop
71
76
  feature_method(node) do |send_node, match|
72
77
  next if enabled?(match)
73
78
 
74
- add_offense(
75
- send_node,
76
- location: :selector,
77
- message: format(MSG, method: match, replacement: MAP[match])
78
- )
79
+ add_offense(send_node.loc.selector) do |corrector|
80
+ corrector.replace(send_node.loc.selector, MAP[match].to_s)
81
+ end
79
82
  end
80
83
  end
81
84
 
82
- def autocorrect(node)
83
- lambda do |corrector|
84
- corrector.replace(node.loc.selector, MAP[node.method_name].to_s)
85
- end
85
+ def message(range)
86
+ name = range.source.to_sym
87
+ format(MSG, method: name, replacement: MAP[name])
86
88
  end
87
89
 
88
90
  private
@@ -26,7 +26,7 @@ module RuboCop
26
26
  # expect(page).to have_css('.foo', visible: :all)
27
27
  # expect(page).to have_link('my link', visible: :hidden)
28
28
  #
29
- class VisibilityMatcher < Cop
29
+ class VisibilityMatcher < Base
30
30
  MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
31
31
  MSG_TRUE = 'Use `:visible` instead of `true`.'
32
32
  CAPYBARA_MATCHER_METHODS = %i[
@@ -23,22 +23,20 @@ module RuboCop
23
23
  # describe '.foo_bar' do
24
24
  # # ...
25
25
  # end
26
- class ContextMethod < Cop
26
+ class ContextMethod < Base
27
+ extend AutoCorrector
28
+
27
29
  MSG = 'Use `describe` for testing methods.'
28
30
 
29
31
  def_node_matcher :context_method, <<-PATTERN
30
- (block (send #{RSPEC} :context $(str #method_name?) ...) ...)
32
+ (block (send #rspec? :context $(str #method_name?) ...) ...)
31
33
  PATTERN
32
34
 
33
35
  def on_block(node)
34
36
  context_method(node) do |context|
35
- add_offense(context)
36
- end
37
- end
38
-
39
- def autocorrect(node)
40
- lambda do |corrector|
41
- corrector.replace(node.parent.loc.selector, 'describe')
37
+ add_offense(context) do |corrector|
38
+ corrector.replace(node.send_node.loc.selector, 'describe')
39
+ end
42
40
  end
43
41
  end
44
42
 
@@ -34,11 +34,11 @@ module RuboCop
34
34
  # context 'when the display name is not present' do
35
35
  # # ...
36
36
  # end
37
- class ContextWording < Cop
37
+ class ContextWording < Base
38
38
  MSG = 'Start context description with %<prefixes>s.'
39
39
 
40
40
  def_node_matcher :context_wording, <<-PATTERN
41
- (block (send #{RSPEC} { :context :shared_context } $(str #bad_prefix?) ...) ...)
41
+ (block (send #rspec? { :context :shared_context } $(str #bad_prefix?) ...) ...)
42
42
  PATTERN
43
43
 
44
44
  def on_block(node)
@@ -51,7 +51,7 @@ module RuboCop
51
51
  private
52
52
 
53
53
  def bad_prefix?(description)
54
- !prefixes.include?(description.split.first)
54
+ !prefixes.include?(description.split(/\b/).first)
55
55
  end
56
56
 
57
57
  def joined_prefixes
@@ -3,72 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # @abstract parent class to RSpec cops
7
- #
8
- # The criteria for whether rubocop-rspec analyzes a certain ruby file
9
- # is configured via `AllCops/RSpec`. For example, if you want to
10
- # customize your project to scan all files within a `test/` directory
11
- # then you could add this to your configuration:
12
- #
13
- # @example configuring analyzed paths
14
- # # .rubocop.yml
15
- # # AllCops:
16
- # # RSpec:
17
- # # Patterns:
18
- # # - '_test.rb$'
19
- # # - '(?:^|/)test/'
20
- class Cop < ::RuboCop::Cop::Cop
21
- include RuboCop::RSpec::Language
22
- include RuboCop::RSpec::Language::NodePattern
23
-
24
- DEFAULT_CONFIGURATION =
25
- RuboCop::RSpec::CONFIG.fetch('AllCops').fetch('RSpec')
26
-
27
- DEFAULT_PATTERN_RE = Regexp.union(
28
- DEFAULT_CONFIGURATION.fetch('Patterns')
29
- .map(&Regexp.public_method(:new))
30
- )
31
-
32
- # Invoke the original inherited hook so our cops are recognized
33
- def self.inherited(subclass)
34
- RuboCop::Cop::Cop.inherited(subclass)
35
- end
36
-
37
- def relevant_file?(file)
38
- relevant_rubocop_rspec_file?(file) && super
39
- end
40
-
41
- private
42
-
43
- def relevant_rubocop_rspec_file?(file)
44
- rspec_pattern =~ file
45
- end
46
-
47
- def rspec_pattern
48
- if rspec_pattern_config?
49
- Regexp.union(rspec_pattern_config.map(&Regexp.public_method(:new)))
50
- else
51
- DEFAULT_PATTERN_RE
52
- end
53
- end
54
-
55
- def all_cops_config
56
- config
57
- .for_all_cops
58
- end
59
-
60
- def rspec_pattern_config?
61
- return unless all_cops_config.key?('RSpec')
62
-
63
- all_cops_config.fetch('RSpec').key?('Patterns')
64
- end
65
-
66
- def rspec_pattern_config
67
- all_cops_config
68
- .fetch('RSpec', DEFAULT_CONFIGURATION)
69
- .fetch('Patterns')
70
- end
71
- end
6
+ # @deprecated Use ::RuboCop::Cop::RSpec::Base instead
7
+ Cop = Base
72
8
  end
73
9
  end
74
10
  end
@@ -3,7 +3,7 @@
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
7
  #
8
8
  # @example
9
9
  # # bad
@@ -21,48 +21,41 @@ module RuboCop
21
21
  #
22
22
  # describe "A feature example", type: :feature do
23
23
  # end
24
- class DescribeClass < Cop
25
- include RuboCop::RSpec::TopLevelDescribe
24
+ class DescribeClass < Base
25
+ include RuboCop::RSpec::TopLevelGroup
26
26
 
27
27
  MSG = 'The first argument to describe should be '\
28
28
  'the class or module being tested.'
29
29
 
30
- def_node_matcher :valid_describe?, <<-PATTERN
31
- {
32
- (send #{RSPEC} :describe const ...)
33
- (send #{RSPEC} :describe)
34
- }
35
- PATTERN
36
-
37
- def_node_matcher :describe_with_rails_metadata?, <<-PATTERN
38
- (send #{RSPEC} :describe !const ...
39
- (hash <#rails_metadata? ...>)
40
- )
41
- PATTERN
42
-
43
30
  def_node_matcher :rails_metadata?, <<-PATTERN
44
31
  (pair
45
32
  (sym :type)
46
- (sym {:request :feature :system :routing :view})
33
+ (sym { :channel :controller :helper :job :mailer :model :request
34
+ :routing :view :feature :system :mailbox })
47
35
  )
48
36
  PATTERN
49
37
 
50
- def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern
38
+ def_node_matcher :example_group_with_rails_metadata?, <<~PATTERN
39
+ (send #rspec? :describe ... (hash <#rails_metadata? ...>))
40
+ PATTERN
41
+
42
+ def_node_matcher :not_a_const_described, <<~PATTERN
43
+ (send #rspec? :describe $[!const !#string_constant?] ...)
44
+ PATTERN
51
45
 
52
- def on_top_level_describe(node, (described_value, _))
53
- return if shared_group?(root_node)
54
- return if valid_describe?(node)
55
- return if describe_with_rails_metadata?(node)
56
- return if string_constant_describe?(described_value)
46
+ def on_top_level_group(top_level_node)
47
+ return if example_group_with_rails_metadata?(top_level_node.send_node)
57
48
 
58
- add_offense(described_value)
49
+ not_a_const_described(top_level_node.send_node) do |described|
50
+ add_offense(described)
51
+ end
59
52
  end
60
53
 
61
54
  private
62
55
 
63
- def string_constant_describe?(described_value)
64
- described_value.str_type? &&
65
- described_value.value =~ /^((::)?[A-Z]\w*)+$/
56
+ def string_constant?(described)
57
+ described.str_type? &&
58
+ described.value.match?(/^(?:(?:::)?[A-Z]\w*)+$/)
66
59
  end
67
60
  end
68
61
  end
@@ -16,17 +16,25 @@ module RuboCop
16
16
  #
17
17
  # describe MyClass, '.my_class_method' do
18
18
  # end
19
- class DescribeMethod < Cop
20
- include RuboCop::RSpec::TopLevelDescribe
19
+ class DescribeMethod < Base
20
+ include RuboCop::RSpec::TopLevelGroup
21
21
 
22
22
  MSG = 'The second argument to describe should be the method '\
23
23
  "being tested. '#instance' or '.class'."
24
24
 
25
- def on_top_level_describe(_node, (_, second_arg))
26
- return unless second_arg&.str_type?
27
- return if second_arg.str_content.start_with?('#', '.')
25
+ def_node_matcher :second_argument, <<~PATTERN
26
+ (block
27
+ (send #rspec? :describe _first_argument $(str _) ...) ...
28
+ )
29
+ PATTERN
28
30
 
29
- add_offense(second_arg)
31
+ def on_top_level_group(node)
32
+ second_argument = second_argument(node)
33
+
34
+ return unless second_argument
35
+ return if second_argument.str_content.start_with?('#', '.')
36
+
37
+ add_offense(second_argument)
30
38
  end
31
39
  end
32
40
  end
@@ -17,11 +17,11 @@ module RuboCop
17
17
  # end
18
18
  #
19
19
  # @see https://github.com/rspec/rspec-core/issues/1610
20
- class DescribeSymbol < Cop
20
+ class DescribeSymbol < Base
21
21
  MSG = 'Avoid describing symbols.'
22
22
 
23
23
  def_node_matcher :describe_symbol?, <<-PATTERN
24
- (send #{RSPEC} :describe $sym ...)
24
+ (send #rspec? :describe $sym ...)
25
25
  PATTERN
26
26
 
27
27
  def on_send(node)
@@ -54,7 +54,8 @@ module RuboCop
54
54
  # end
55
55
  # end
56
56
  #
57
- class DescribedClass < Cop
57
+ class DescribedClass < Base
58
+ extend AutoCorrector
58
59
  include ConfigurableEnforcedStyle
59
60
 
60
61
  DESCRIBED_CLASS = 'described_class'
@@ -85,22 +86,24 @@ module RuboCop
85
86
  return unless body
86
87
 
87
88
  find_usage(body) do |match|
88
- add_offense(match, message: message(match.const_name))
89
+ msg = message(match.const_name)
90
+ add_offense(match, message: msg) do |corrector|
91
+ autocorrect(corrector, match)
92
+ end
89
93
  end
90
94
  end
91
95
 
92
- def autocorrect(node)
96
+ private
97
+
98
+ def autocorrect(corrector, match)
93
99
  replacement = if style == :described_class
94
100
  DESCRIBED_CLASS
95
101
  else
96
102
  @described_class.const_name
97
103
  end
98
- lambda do |corrector|
99
- corrector.replace(node.loc.expression, replacement)
100
- end
101
- end
102
104
 
103
- private
105
+ corrector.replace(match, replacement)
106
+ end
104
107
 
105
108
  def find_usage(node, &block)
106
109
  yield(node) if offensive?(node)
@@ -139,7 +142,7 @@ module RuboCop
139
142
  if style == :described_class
140
143
  offensive_described_class?(node)
141
144
  else
142
- node.send_type? && node.method_name == :described_class
145
+ node.send_type? && node.method?(:described_class)
143
146
  end
144
147
  end
145
148
 
@@ -19,7 +19,7 @@ module RuboCop
19
19
  # end
20
20
  #
21
21
  # @see https://github.com/rubocop-hq/rubocop-rspec/issues/735
22
- class DescribedClassModuleWrapping < Cop
22
+ class DescribedClassModuleWrapping < Base
23
23
  MSG = 'Avoid opening modules and defining specs within them.'
24
24
 
25
25
  def_node_search :find_rspec_blocks,
@@ -41,7 +41,8 @@ module RuboCop
41
41
  # describe 'display name presence' do
42
42
  # # ...
43
43
  # end
44
- class Dialect < Cop
44
+ class Dialect < Base
45
+ extend AutoCorrector
45
46
  include MethodPreference
46
47
 
47
48
  MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
@@ -52,24 +53,16 @@ module RuboCop
52
53
  return unless rspec_method?(node)
53
54
  return unless preferred_methods[node.method_name]
54
55
 
55
- add_offense(node)
56
- end
56
+ msg = format(MSG, prefer: preferred_method(node.method_name),
57
+ current: node.method_name)
57
58
 
58
- def autocorrect(node)
59
- lambda do |corrector|
59
+ add_offense(node, message: msg) do |corrector|
60
60
  current = node.loc.selector
61
61
  preferred = preferred_method(current.source)
62
62
 
63
63
  corrector.replace(current, preferred)
64
64
  end
65
65
  end
66
-
67
- private
68
-
69
- def message(node)
70
- format(MSG, prefer: preferred_method(node.method_name),
71
- current: node.method_name)
72
- end
73
66
  end
74
67
  end
75
68
  end
@@ -57,20 +57,104 @@ module RuboCop
57
57
  # end
58
58
  # end
59
59
  #
60
- class EmptyExampleGroup < Cop
60
+ class EmptyExampleGroup < Base
61
61
  MSG = 'Empty example group detected.'
62
62
 
63
- def_node_search :contains_example?, <<-PATTERN
63
+ # @!method example_group_body(node)
64
+ # Match example group blocks and yield their body
65
+ #
66
+ # @example source that matches
67
+ # describe 'example group' do
68
+ # it { is_expected.to be }
69
+ # end
70
+ #
71
+ # @param node [RuboCop::AST::Node]
72
+ # @yield [RuboCop::AST::Node] example group body
73
+ def_node_matcher :example_group_body, <<~PATTERN
74
+ (block #{ExampleGroups::ALL.send_pattern} args $_)
75
+ PATTERN
76
+
77
+ # @!method example_or_group_or_include?(node)
78
+ # Match examples, example groups and includes
79
+ #
80
+ # @example source that matches
81
+ # it { is_expected.to fly }
82
+ # describe('non-empty example groups too') { }
83
+ # it_behaves_like 'an animal'
84
+ # it_behaves_like('a cat') { let(:food) { 'milk' } }
85
+ # it_has_root_access
86
+ #
87
+ # @param node [RuboCop::AST::Node]
88
+ # @return [Array<RuboCop::AST::Node>] matching nodes
89
+ def_node_matcher :example_or_group_or_include?, <<~PATTERN
64
90
  {
65
- #{(Examples::ALL + Includes::ALL).send_pattern}
66
- (send _ #custom_include? ...)
91
+ #{Examples::ALL.block_pattern}
92
+ #{ExampleGroups::ALL.block_pattern}
93
+ #{Includes::ALL.send_pattern}
94
+ #{Includes::ALL.block_pattern}
95
+ (send nil? #custom_include? ...)
67
96
  }
68
97
  PATTERN
69
98
 
70
- def on_block(node)
71
- return unless example_group?(node) && !contains_example?(node)
99
+ # @!method examples_inside_block?(node)
100
+ # Match examples defined inside a block which is not a hook
101
+ #
102
+ # @example source that matches
103
+ # %w(r g b).each do |color|
104
+ # it { is_expected.to have_color(color) }
105
+ # end
106
+ #
107
+ # @example source that does not match
108
+ # before do
109
+ # it { is_expected.to fall_into_oblivion }
110
+ # end
111
+ #
112
+ # @param node [RuboCop::AST::Node]
113
+ # @return [Array<RuboCop::AST::Node>] matching nodes
114
+ def_node_matcher :examples_inside_block?, <<~PATTERN
115
+ (block !#{Hooks::ALL.send_pattern} _ #examples?)
116
+ PATTERN
72
117
 
73
- add_offense(node.send_node)
118
+ # @!method examples_directly_or_in_block?(node)
119
+ # Match examples or examples inside blocks
120
+ #
121
+ # @example source that matches
122
+ # it { expect(drink).to be_cold }
123
+ # context('when winter') { it { expect(drink).to be_hot } }
124
+ # (1..5).each { |divisor| it { is_expected.to divide_by(divisor) } }
125
+ #
126
+ # @param node [RuboCop::AST::Node]
127
+ # @return [Array<RuboCop::AST::Node>] matching nodes
128
+ def_node_matcher :examples_directly_or_in_block?, <<~PATTERN
129
+ {
130
+ #example_or_group_or_include?
131
+ #examples_inside_block?
132
+ }
133
+ PATTERN
134
+
135
+ # @!method examples?(node)
136
+ # Matches examples defined in scopes where they could run
137
+ #
138
+ # @example source that matches
139
+ # it { expect(myself).to be_run }
140
+ # describe { it { i_run_as_well } }
141
+ #
142
+ # @example source that does not match
143
+ # before { it { whatever here wont run anyway } }
144
+ #
145
+ # @param node [RuboCop::AST::Node]
146
+ # @return [Array<RuboCop::AST::Node>] matching nodes
147
+ def_node_matcher :examples?, <<~PATTERN
148
+ {
149
+ #examples_directly_or_in_block?
150
+ (begin <#examples_directly_or_in_block? ...>)
151
+ }
152
+ PATTERN
153
+
154
+ def on_block(node)
155
+ example_group_body(node) do |body|
156
+ add_offense(node.send_node) unless examples?(body)
157
+ end
74
158
  end
75
159
 
76
160
  private