rubocop-rspec 1.38.1 → 1.43.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -0
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/README.md +1 -61
  5. data/config/default.yml +159 -19
  6. data/lib/rubocop-rspec.rb +5 -2
  7. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +12 -19
  8. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +12 -19
  9. data/lib/rubocop/cop/rspec/any_instance.rb +1 -1
  10. data/lib/rubocop/cop/rspec/around_block.rb +1 -1
  11. data/lib/rubocop/cop/rspec/base.rb +74 -0
  12. data/lib/rubocop/cop/rspec/be.rb +2 -2
  13. data/lib/rubocop/cop/rspec/be_eql.rb +6 -6
  14. data/lib/rubocop/cop/rspec/before_after_all.rb +1 -1
  15. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +19 -17
  16. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +14 -12
  17. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
  18. data/lib/rubocop/cop/rspec/context_method.rb +7 -9
  19. data/lib/rubocop/cop/rspec/context_wording.rb +3 -3
  20. data/lib/rubocop/cop/rspec/cop.rb +3 -87
  21. data/lib/rubocop/cop/rspec/describe_class.rb +29 -23
  22. data/lib/rubocop/cop/rspec/describe_method.rb +14 -7
  23. data/lib/rubocop/cop/rspec/describe_symbol.rb +2 -2
  24. data/lib/rubocop/cop/rspec/described_class.rb +12 -9
  25. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +1 -1
  26. data/lib/rubocop/cop/rspec/dialect.rb +5 -12
  27. data/lib/rubocop/cop/rspec/empty_example_group.rb +91 -7
  28. data/lib/rubocop/cop/rspec/empty_hook.rb +46 -0
  29. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +5 -7
  30. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +5 -9
  31. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +8 -8
  32. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +5 -9
  33. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +6 -6
  34. data/lib/rubocop/cop/rspec/example_length.rb +1 -1
  35. data/lib/rubocop/cop/rspec/example_without_description.rb +1 -1
  36. data/lib/rubocop/cop/rspec/example_wording.rb +10 -11
  37. data/lib/rubocop/cop/rspec/expect_actual.rb +8 -11
  38. data/lib/rubocop/cop/rspec/expect_change.rb +10 -35
  39. data/lib/rubocop/cop/rspec/expect_in_hook.rb +3 -3
  40. data/lib/rubocop/cop/rspec/expect_output.rb +2 -2
  41. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +24 -21
  42. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +20 -22
  43. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +7 -8
  44. data/lib/rubocop/cop/rspec/file_path.rb +57 -21
  45. data/lib/rubocop/cop/rspec/focus.rb +7 -11
  46. data/lib/rubocop/cop/rspec/hook_argument.rb +16 -23
  47. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +10 -29
  48. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +1 -1
  49. data/lib/rubocop/cop/rspec/implicit_expect.rb +7 -15
  50. data/lib/rubocop/cop/rspec/implicit_subject.rb +16 -11
  51. data/lib/rubocop/cop/rspec/instance_spy.rb +18 -12
  52. data/lib/rubocop/cop/rspec/instance_variable.rb +4 -8
  53. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +3 -6
  54. data/lib/rubocop/cop/rspec/it_behaves_like.rb +5 -6
  55. data/lib/rubocop/cop/rspec/iterated_expectation.rb +1 -1
  56. data/lib/rubocop/cop/rspec/leading_subject.rb +22 -26
  57. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +2 -5
  58. data/lib/rubocop/cop/rspec/let_before_examples.rb +10 -26
  59. data/lib/rubocop/cop/rspec/let_setup.rb +21 -6
  60. data/lib/rubocop/cop/rspec/message_chain.rb +7 -6
  61. data/lib/rubocop/cop/rspec/message_expectation.rb +2 -2
  62. data/lib/rubocop/cop/rspec/message_spies.rb +2 -3
  63. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +1 -1
  64. data/lib/rubocop/cop/rspec/multiple_describes.rb +11 -8
  65. data/lib/rubocop/cop/rspec/multiple_expectations.rb +7 -11
  66. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +148 -0
  67. data/lib/rubocop/cop/rspec/multiple_subjects.rb +18 -19
  68. data/lib/rubocop/cop/rspec/named_subject.rb +8 -8
  69. data/lib/rubocop/cop/rspec/nested_groups.rb +12 -13
  70. data/lib/rubocop/cop/rspec/not_to_not.rb +5 -6
  71. data/lib/rubocop/cop/rspec/overwriting_setup.rb +1 -1
  72. data/lib/rubocop/cop/rspec/pending.rb +1 -1
  73. data/lib/rubocop/cop/rspec/predicate_matcher.rb +32 -69
  74. data/lib/rubocop/cop/rspec/rails/http_status.rb +7 -9
  75. data/lib/rubocop/cop/rspec/receive_counts.rb +15 -17
  76. data/lib/rubocop/cop/rspec/receive_never.rb +12 -12
  77. data/lib/rubocop/cop/rspec/repeated_description.rb +1 -1
  78. data/lib/rubocop/cop/rspec/repeated_example.rb +2 -2
  79. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +12 -2
  80. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +1 -1
  81. data/lib/rubocop/cop/rspec/return_from_stub.rb +12 -22
  82. data/lib/rubocop/cop/rspec/scattered_let.rb +12 -2
  83. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  84. data/lib/rubocop/cop/rspec/shared_context.rb +8 -21
  85. data/lib/rubocop/cop/rspec/shared_examples.rb +7 -9
  86. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +15 -18
  87. data/lib/rubocop/cop/rspec/subject_stub.rb +25 -53
  88. data/lib/rubocop/cop/rspec/unspecified_exception.rb +1 -1
  89. data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
  90. data/lib/rubocop/cop/rspec/variable_name.rb +66 -0
  91. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
  92. data/lib/rubocop/cop/rspec/void_expect.rb +1 -1
  93. data/lib/rubocop/cop/rspec/yield.rb +14 -11
  94. data/lib/rubocop/cop/rspec_cops.rb +6 -1
  95. data/lib/rubocop/rspec/corrector/move_node.rb +54 -0
  96. data/lib/rubocop/rspec/description_extractor.rb +2 -6
  97. data/lib/rubocop/rspec/{blank_line_separation.rb → empty_line_separation.rb} +13 -10
  98. data/lib/rubocop/rspec/example_group.rb +21 -49
  99. data/lib/rubocop/rspec/factory_bot.rb +7 -1
  100. data/lib/rubocop/rspec/language.rb +13 -3
  101. data/lib/rubocop/rspec/language/node_pattern.rb +11 -2
  102. data/lib/rubocop/rspec/top_level_describe.rb +2 -2
  103. data/lib/rubocop/rspec/top_level_group.rb +55 -0
  104. data/lib/rubocop/rspec/variable.rb +16 -0
  105. data/lib/rubocop/rspec/version.rb +1 -1
  106. metadata +36 -13
  107. data/lib/rubocop/rspec/util.rb +0 -19
@@ -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
@@ -1,94 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop # rubocop:disable Style/Documentation
5
- WorkaroundCop = Cop.dup
6
-
7
- # Clone of the the normal RuboCop::Cop::Cop class so we can rewrite
8
- # the inherited method without breaking functionality
9
- class WorkaroundCop
10
- # Remove the Cop.inherited method to be a noop. Our RSpec::Cop
11
- # class will invoke the inherited hook instead
12
- class << self
13
- undef inherited
14
- def inherited(*) end
15
- end
16
-
17
- # Special case `Module#<` so that the rspec support rubocop exports
18
- # is compatible with our subclass
19
- def self.<(other)
20
- other.equal?(RuboCop::Cop::Cop) || super
21
- end
22
- end
23
- private_constant(:WorkaroundCop)
24
-
4
+ module Cop
25
5
  module RSpec
26
- # @abstract parent class to rspec cops
27
- #
28
- # The criteria for whether rubocop-rspec analyzes a certain ruby file
29
- # is configured via `AllCops/RSpec`. For example, if you want to
30
- # customize your project to scan all files within a `test/` directory
31
- # then you could add this to your configuration:
32
- #
33
- # @example configuring analyzed paths
34
- #
35
- # AllCops:
36
- # RSpec:
37
- # Patterns:
38
- # - '_test.rb$'
39
- # - '(?:^|/)test/'
40
- class Cop < WorkaroundCop
41
- include RuboCop::RSpec::Language
42
- include RuboCop::RSpec::Language::NodePattern
43
-
44
- DEFAULT_CONFIGURATION =
45
- RuboCop::RSpec::CONFIG.fetch('AllCops').fetch('RSpec')
46
-
47
- DEFAULT_PATTERN_RE = Regexp.union(
48
- DEFAULT_CONFIGURATION.fetch('Patterns')
49
- .map(&Regexp.public_method(:new))
50
- )
51
-
52
- # Invoke the original inherited hook so our cops are recognized
53
- def self.inherited(subclass)
54
- RuboCop::Cop::Cop.inherited(subclass)
55
- end
56
-
57
- def relevant_file?(file)
58
- relevant_rubocop_rspec_file?(file) && super
59
- end
60
-
61
- private
62
-
63
- def relevant_rubocop_rspec_file?(file)
64
- rspec_pattern =~ file
65
- end
66
-
67
- def rspec_pattern
68
- if rspec_pattern_config?
69
- Regexp.union(rspec_pattern_config.map(&Regexp.public_method(:new)))
70
- else
71
- DEFAULT_PATTERN_RE
72
- end
73
- end
74
-
75
- def all_cops_config
76
- config
77
- .for_all_cops
78
- end
79
-
80
- def rspec_pattern_config?
81
- return unless all_cops_config.key?('RSpec')
82
-
83
- all_cops_config.fetch('RSpec').key?('Patterns')
84
- end
85
-
86
- def rspec_pattern_config
87
- all_cops_config
88
- .fetch('RSpec', DEFAULT_CONFIGURATION)
89
- .fetch('Patterns')
90
- end
91
- end
6
+ # @deprecated Use ::RuboCop::Cop::RSpec::Base instead
7
+ Cop = Base
92
8
  end
93
9
  end
94
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
@@ -12,44 +12,50 @@ module RuboCop
12
12
  #
13
13
  # # good
14
14
  # describe TestedClass do
15
+ # subject { described_class }
16
+ # end
17
+ #
18
+ # describe 'TestedClass::VERSION' do
19
+ # subject { Object.const_get(self.class.description) }
15
20
  # end
16
21
  #
17
22
  # describe "A feature example", type: :feature do
18
23
  # end
19
- class DescribeClass < Cop
20
- include RuboCop::RSpec::TopLevelDescribe
24
+ class DescribeClass < Base
25
+ include RuboCop::RSpec::TopLevelGroup
21
26
 
22
27
  MSG = 'The first argument to describe should be '\
23
28
  'the class or module being tested.'
24
29
 
25
- def_node_matcher :valid_describe?, <<-PATTERN
26
- {
27
- (send #{RSPEC} :describe const ...)
28
- (send #{RSPEC} :describe)
29
- }
30
- PATTERN
31
-
32
- def_node_matcher :describe_with_rails_metadata?, <<-PATTERN
33
- (send #{RSPEC} :describe !const ...
34
- (hash <#rails_metadata? ...>)
35
- )
36
- PATTERN
37
-
38
30
  def_node_matcher :rails_metadata?, <<-PATTERN
39
31
  (pair
40
32
  (sym :type)
41
- (sym {:request :feature :system :routing :view})
33
+ (sym { :channel :controller :helper :job :mailer :model :request
34
+ :routing :view :feature :system :mailbox })
42
35
  )
43
36
  PATTERN
44
37
 
45
- 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
45
+
46
+ def on_top_level_group(top_level_node)
47
+ return if example_group_with_rails_metadata?(top_level_node.send_node)
48
+
49
+ not_a_const_described(top_level_node.send_node) do |described|
50
+ add_offense(described)
51
+ end
52
+ end
46
53
 
47
- def on_top_level_describe(node, args)
48
- return if shared_group?(root_node)
49
- return if valid_describe?(node)
50
- return if describe_with_rails_metadata?(node)
54
+ private
51
55
 
52
- add_offense(args.first)
56
+ def string_constant?(described)
57
+ described.str_type? &&
58
+ described.value.match?(/^(?:(?:::)?[A-Z]\w*)+$/)
53
59
  end
54
60
  end
55
61
  end
@@ -16,18 +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
21
- include RuboCop::RSpec::Util
19
+ class DescribeMethod < Base
20
+ include RuboCop::RSpec::TopLevelGroup
22
21
 
23
22
  MSG = 'The second argument to describe should be the method '\
24
23
  "being tested. '#instance' or '.class'."
25
24
 
26
- def on_top_level_describe(_node, (_, second_arg))
27
- return unless second_arg&.str_type?
28
- 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
29
30
 
30
- 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)
31
38
  end
32
39
  end
33
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