rubocop-rspec 1.38.1 → 1.43.0

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 (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
@@ -22,7 +22,7 @@ module RuboCop
22
22
  # let(:foo) do
23
23
  # instance_double("ClassName", method_name: 'returned value')
24
24
  # end
25
- class VerifiedDoubles < Cop
25
+ class VerifiedDoubles < Base
26
26
  MSG = 'Prefer using verifying doubles over normal doubles.'
27
27
 
28
28
  def_node_matcher :unverified_double, <<-PATTERN
@@ -11,7 +11,7 @@ module RuboCop
11
11
  #
12
12
  # # good
13
13
  # expect(something).to be(1)
14
- class VoidExpect < Cop
14
+ class VoidExpect < Base
15
15
  MSG = 'Do not use `expect()` without `.to` or `.not_to`. ' \
16
16
  'Chain the methods or remove it.'
17
17
 
@@ -11,7 +11,8 @@ module RuboCop
11
11
  #
12
12
  # # good
13
13
  # expect(foo).to be(:bar).and_yield(1)
14
- class Yield < Cop
14
+ class Yield < Base
15
+ extend AutoCorrector
15
16
  include RangeHelp
16
17
 
17
18
  MSG = 'Use `.and_yield`.'
@@ -27,22 +28,24 @@ module RuboCop
27
28
 
28
29
  block_arg(node.arguments) do |block|
29
30
  if calling_block?(node.body, block)
30
- add_offense(node, location: block_range(node))
31
- end
32
- end
33
- end
31
+ range = block_range(node)
34
32
 
35
- def autocorrect(node)
36
- lambda do |corrector|
37
- node_range = range_with_surrounding_space(
38
- range: block_range(node), side: :left
39
- )
40
- corrector.replace(node_range, generate_replacement(node.body))
33
+ add_offense(range) do |corrector|
34
+ autocorrect(corrector, node, range)
35
+ end
36
+ end
41
37
  end
42
38
  end
43
39
 
44
40
  private
45
41
 
42
+ def autocorrect(corrector, node, range)
43
+ corrector.replace(
44
+ range_with_surrounding_space(range: range, side: :left),
45
+ generate_replacement(node.body)
46
+ )
47
+ end
48
+
46
49
  def calling_block?(node, block)
47
50
  if node.begin_type?
48
51
  node.each_child_node.all? { |child| block_call?(child, block) }
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'rspec/capybara/current_path_expectation'
4
4
  require_relative 'rspec/capybara/feature_methods'
5
+ require_relative 'rspec/capybara/visibility_matcher'
5
6
 
6
7
  require_relative 'rspec/factory_bot/attribute_defined_statically'
7
8
  require_relative 'rspec/factory_bot/create_list'
@@ -9,7 +10,7 @@ require_relative 'rspec/factory_bot/factory_class_name'
9
10
 
10
11
  begin
11
12
  require_relative 'rspec/rails/http_status'
12
- rescue LoadError # rubocop:disable Lint/SuppressedException
13
+ rescue LoadError
13
14
  # Rails/HttpStatus cannot be loaded if rack/utils is unavailable.
14
15
  end
15
16
 
@@ -29,6 +30,7 @@ require_relative 'rspec/described_class'
29
30
  require_relative 'rspec/described_class_module_wrapping'
30
31
  require_relative 'rspec/dialect'
31
32
  require_relative 'rspec/empty_example_group'
33
+ require_relative 'rspec/empty_hook'
32
34
  require_relative 'rspec/empty_line_after_example'
33
35
  require_relative 'rspec/empty_line_after_example_group'
34
36
  require_relative 'rspec/empty_line_after_final_let'
@@ -63,6 +65,7 @@ require_relative 'rspec/message_spies'
63
65
  require_relative 'rspec/missing_example_group_argument'
64
66
  require_relative 'rspec/multiple_describes'
65
67
  require_relative 'rspec/multiple_expectations'
68
+ require_relative 'rspec/multiple_memoized_helpers'
66
69
  require_relative 'rspec/multiple_subjects'
67
70
  require_relative 'rspec/named_subject'
68
71
  require_relative 'rspec/nested_groups'
@@ -84,6 +87,8 @@ require_relative 'rspec/shared_examples'
84
87
  require_relative 'rspec/single_argument_message_chain'
85
88
  require_relative 'rspec/subject_stub'
86
89
  require_relative 'rspec/unspecified_exception'
90
+ require_relative 'rspec/variable_definition'
91
+ require_relative 'rspec/variable_name'
87
92
  require_relative 'rspec/verified_doubles'
88
93
  require_relative 'rspec/void_expect'
89
94
  require_relative 'rspec/yield'
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module RSpec
5
+ module Corrector
6
+ # Helper methods to move a node
7
+ class MoveNode
8
+ include RuboCop::Cop::RangeHelp
9
+ include RuboCop::RSpec::FinalEndLocation
10
+
11
+ attr_reader :original, :corrector, :processed_source
12
+
13
+ def initialize(node, corrector, processed_source)
14
+ @original = node
15
+ @corrector = corrector
16
+ @processed_source = processed_source # used by RangeHelp
17
+ end
18
+
19
+ def move_before(other)
20
+ position = other.loc.expression
21
+ indent = ' ' * other.loc.column
22
+ newline_indent = "\n#{indent}"
23
+
24
+ corrector.insert_before(position, source(original) + newline_indent)
25
+ corrector.remove(node_range_with_surrounding_space(original))
26
+ end
27
+
28
+ def move_after(other)
29
+ position = final_end_location(other)
30
+ indent = ' ' * other.loc.column
31
+ newline_indent = "\n#{indent}"
32
+
33
+ corrector.insert_after(position, newline_indent + source(original))
34
+ corrector.remove(node_range_with_surrounding_space(original))
35
+ end
36
+
37
+ private
38
+
39
+ def source(node)
40
+ node_range(node).source
41
+ end
42
+
43
+ def node_range(node)
44
+ node.loc.expression.with(end_pos: final_end_location(node).end_pos)
45
+ end
46
+
47
+ def node_range_with_surrounding_space(node)
48
+ range = node_range(node)
49
+ range_by_whole_lines(range, include_final_newline: true)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -21,7 +21,7 @@ module RuboCop
21
21
 
22
22
  # Decorator of a YARD code object for working with documented rspec cops
23
23
  class CodeObject
24
- COP_CLASS_NAMES = %w[RuboCop::Cop RuboCop::Cop::RSpec::Cop].freeze
24
+ COP_CLASS_NAME = 'RuboCop::Cop::RSpec::Base'
25
25
  RSPEC_NAMESPACE = 'RuboCop::Cop::RSpec'
26
26
 
27
27
  def initialize(yardoc)
@@ -68,11 +68,7 @@ module RuboCop
68
68
  end
69
69
 
70
70
  def cop_subclass?
71
- # YARD superclass resolution is a bit flaky: All classes loaded before
72
- # RuboCop::Cop::WorkaroundCop are shown as having RuboCop::Cop as
73
- # superclass, while all the following classes are listed as having
74
- # RuboCop::Cop::RSpec::Cop as their superclass.
75
- COP_CLASS_NAMES.include?(yardoc.superclass.path)
71
+ yardoc.superclass.path == COP_CLASS_NAME
76
72
  end
77
73
 
78
74
  def abstract?
@@ -2,12 +2,23 @@
2
2
 
3
3
  module RuboCop
4
4
  module RSpec
5
- # Helps determine the offending location if there is not a blank line
5
+ # Helps determine the offending location if there is not an empty line
6
6
  # following the node. Allows comments to follow directly after.
7
- module BlankLineSeparation
7
+ module EmptyLineSeparation
8
8
  include FinalEndLocation
9
9
  include RuboCop::Cop::RangeHelp
10
10
 
11
+ def missing_separating_line_offense(node)
12
+ return if last_child?(node)
13
+
14
+ missing_separating_line(node) do |location|
15
+ msg = yield(node.method_name)
16
+ add_offense(location, message: msg) do |corrector|
17
+ corrector.insert_after(location.end, "\n")
18
+ end
19
+ end
20
+ end
21
+
11
22
  def missing_separating_line(node)
12
23
  line = final_end_location(node).line
13
24
 
@@ -32,14 +43,6 @@ module RuboCop
32
43
 
33
44
  node.equal?(node.parent.children.last)
34
45
  end
35
-
36
- def autocorrect(node)
37
- lambda do |corrector|
38
- missing_separating_line(node) do |location|
39
- corrector.insert_after(location.end, "\n")
40
- end
41
- end
42
- end
43
46
  end
44
47
  end
45
48
  end
@@ -14,72 +14,44 @@ module RuboCop
14
14
  ExampleGroups::ALL + SharedGroups::ALL + Includes::ALL
15
15
  ).block_pattern
16
16
 
17
+ def lets
18
+ find_all_in_scope(node, :let?)
19
+ end
20
+
17
21
  def subjects
18
- subjects_in_scope(node)
22
+ find_all_in_scope(node, :subject?)
19
23
  end
20
24
 
21
25
  def examples
22
- examples_in_scope(node).map(&Example.public_method(:new))
26
+ find_all_in_scope(node, :example?).map(&Example.public_method(:new))
23
27
  end
24
28
 
25
29
  def hooks
26
- hooks_in_scope(node).map(&Hook.public_method(:new))
30
+ find_all_in_scope(node, :hook?).map(&Hook.public_method(:new))
27
31
  end
28
32
 
29
33
  private
30
34
 
31
- def subjects_in_scope(node)
32
- node.each_child_node.flat_map do |child|
33
- find_subjects(child)
34
- end
35
- end
36
-
37
- def find_subjects(node)
38
- return [] if scope_change?(node)
39
-
40
- if subject?(node)
41
- [node]
42
- else
43
- subjects_in_scope(node)
44
- end
45
- end
46
-
47
- def hooks_in_scope(node)
48
- node.each_child_node.flat_map do |child|
49
- find_hooks(child)
50
- end
51
- end
52
-
53
- def find_hooks(node)
54
- return [] if scope_change?(node) || example?(node)
55
-
56
- if hook?(node)
57
- [node]
58
- else
59
- hooks_in_scope(node)
60
- end
61
- end
62
-
63
- def examples_in_scope(node, &blk)
64
- node.each_child_node.flat_map do |child|
65
- find_examples(child, &blk)
66
- end
67
- end
68
-
69
- # Recursively search for examples within the current scope
35
+ # Recursively search for predicate within the current scope
70
36
  #
71
- # Searches node for examples and halts when a scope change is detected
37
+ # Searches node and halts when a scope change is detected
72
38
  #
73
- # @param node [RuboCop::Node] node to recursively search for examples
39
+ # @param node [RuboCop::AST::Node] node to recursively search
74
40
  #
75
- # @return [Array<RuboCop::Node>] discovered example nodes
76
- def find_examples(node)
77
- return [] if scope_change?(node)
41
+ # @return [Array<RuboCop::AST::Node>] discovered nodes
42
+ def find_all_in_scope(node, predicate)
43
+ node.each_child_node.flat_map do |child|
44
+ find_all(child, predicate)
45
+ end
46
+ end
78
47
 
79
- if example?(node)
48
+ def find_all(node, predicate)
49
+ if public_send(predicate, node)
80
50
  [node]
51
+ elsif scope_change?(node) || example?(node)
52
+ []
81
53
  else
82
- examples_in_scope(node)
54
+ find_all_in_scope(node, predicate)
83
55
  end
84
56
  end
85
57
  end
@@ -4,7 +4,13 @@ module RuboCop
4
4
  module RSpec
5
5
  # RuboCop FactoryBot project namespace
6
6
  module FactoryBot
7
- ATTRIBUTE_DEFINING_METHODS = %i[factory trait transient ignore].freeze
7
+ ATTRIBUTE_DEFINING_METHODS = %i[
8
+ factory
9
+ ignore
10
+ trait
11
+ traits_for_enum
12
+ transient
13
+ ].freeze
8
14
 
9
15
  UNPROXIED_METHODS = %i[
10
16
  __send__
@@ -4,8 +4,6 @@ module RuboCop
4
4
  module RSpec
5
5
  # RSpec public API methods that are commonly used in cops
6
6
  module Language
7
- RSPEC = '{(const {nil? cbase} :RSpec) nil?}'
8
-
9
7
  # Set of method selectors
10
8
  class SelectorSet
11
9
  def initialize(selectors)
@@ -28,8 +26,20 @@ module RuboCop
28
26
  "(block #{send_pattern} ...)"
29
27
  end
30
28
 
29
+ def block_pass_pattern
30
+ "(send #rspec? #{node_pattern_union} _ block_pass)"
31
+ end
32
+
33
+ def block_or_block_pass_pattern
34
+ "{#{block_pattern} #{block_pass_pattern}}"
35
+ end
36
+
31
37
  def send_pattern
32
- "(send #{RSPEC} #{node_pattern_union} ...)"
38
+ "(send #rspec? #{node_pattern_union} ...)"
39
+ end
40
+
41
+ def send_or_block_or_block_pass_pattern
42
+ "{#{send_pattern} #{block_pattern} #{block_pass_pattern}}"
33
43
  end
34
44
 
35
45
  def node_pattern_union
@@ -7,17 +7,26 @@ module RuboCop
7
7
  module NodePattern
8
8
  extend RuboCop::NodePattern::Macros
9
9
 
10
+ def_node_matcher :rspec?, '{(const {nil? cbase} :RSpec) nil?}'
11
+
10
12
  def_node_matcher :example_group?, ExampleGroups::ALL.block_pattern
13
+ def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern
14
+
15
+ spec_groups = ExampleGroups::ALL + SharedGroups::ALL
16
+ def_node_matcher :spec_group?, spec_groups.block_pattern
11
17
 
12
18
  def_node_matcher :example_group_with_body?, <<-PATTERN
13
- (block #{ExampleGroups::ALL.send_pattern} args [!nil?])
19
+ (block #{ExampleGroups::ALL.send_pattern} args !nil?)
14
20
  PATTERN
15
21
 
16
22
  def_node_matcher :example?, Examples::ALL.block_pattern
17
23
 
18
24
  def_node_matcher :hook?, Hooks::ALL.block_pattern
19
25
 
20
- def_node_matcher :let?, Helpers::ALL.block_pattern
26
+ def_node_matcher :let?, Helpers::ALL.block_or_block_pass_pattern
27
+
28
+ def_node_matcher :include?,
29
+ Includes::ALL.send_or_block_or_block_pass_pattern
21
30
 
22
31
  def_node_matcher :subject?, Subject::ALL.block_pattern
23
32
  end
@@ -16,7 +16,7 @@ module RuboCop
16
16
  private
17
17
 
18
18
  def top_level_describe?(node)
19
- return false unless node.method_name == :describe
19
+ return false unless node.method?(:describe)
20
20
 
21
21
  top_level_nodes.include?(node)
22
22
  end
@@ -44,7 +44,7 @@ module RuboCop
44
44
 
45
45
  def describe_statement_children(node)
46
46
  node.each_child_node(:send).select do |element|
47
- element.method_name == :describe
47
+ element.method?(:describe)
48
48
  end
49
49
  end
50
50
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module RSpec
5
+ # Helper methods for top level example group cops
6
+ module TopLevelGroup
7
+ extend RuboCop::NodePattern::Macros
8
+ include RuboCop::RSpec::Language
9
+
10
+ def_node_matcher :example_or_shared_group?,
11
+ (ExampleGroups::ALL + SharedGroups::ALL).block_pattern
12
+
13
+ def on_new_investigation
14
+ super
15
+
16
+ return unless root_node
17
+
18
+ top_level_groups.each do |node|
19
+ example_group?(node, &method(:on_top_level_example_group))
20
+ on_top_level_group(node)
21
+ end
22
+ end
23
+
24
+ def top_level_groups
25
+ @top_level_groups ||=
26
+ top_level_nodes(root_node).select { |n| example_or_shared_group?(n) }
27
+ end
28
+
29
+ private
30
+
31
+ # Dummy methods to be overridden in the consumer
32
+ def on_top_level_example_group; end
33
+
34
+ def on_top_level_group; end
35
+
36
+ def top_level_group?(node)
37
+ top_level_groups.include?(node)
38
+ end
39
+
40
+ def top_level_nodes(node)
41
+ if node.begin_type?
42
+ node.children
43
+ elsif node.module_type? || node.class_type?
44
+ top_level_nodes(node.body)
45
+ else
46
+ [node]
47
+ end
48
+ end
49
+
50
+ def root_node
51
+ processed_source.ast
52
+ end
53
+ end
54
+ end
55
+ end