|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
| 3 | +require 'set' |
| 4 | + |
3 | 5 | module RuboCop |
4 | 6 | module Cop |
5 | 7 | module RSpec |
@@ -75,70 +77,46 @@ class SubjectStub < Cop |
75 | 77 |
|
76 | 78 | def on_block(node) |
77 | 79 | return unless example_group?(node) |
| 80 | + return unless (processed_example_groups & node.ancestors).empty? |
| 81 | + |
| 82 | + processed_example_groups << node |
| 83 | + @explicit_subjects = find_all_explicit_subjects(node) |
78 | 84 |
|
79 | | - find_subject_stub(node) do |stub| |
| 85 | + find_subject_expectations(node) do |stub| |
80 | 86 | add_offense(stub) |
81 | 87 | end |
82 | 88 | end |
83 | 89 |
|
84 | 90 | private |
85 | 91 |
|
86 | | - # Find subjects within tree and then find (send) nodes for that subject |
87 | | - # |
88 | | - # @param node [RuboCop::Node] example group |
89 | | - # |
90 | | - # @yield [RuboCop::Node] message expectations for subject |
91 | | - def find_subject_stub(node, &block) |
92 | | - find_subject(node) do |subject_name, context| |
93 | | - find_subject_expectation(context, subject_name, &block) |
94 | | - end |
| 92 | + def processed_example_groups |
| 93 | + @processed_example_groups ||= Set.new |
95 | 94 | end |
96 | 95 |
|
97 | | - # Find a subject message expectation |
98 | | - # |
99 | | - # @param node [RuboCop::Node] |
100 | | - # @param subject_name [Symbol] name of subject |
101 | | - # |
102 | | - # @yield [RuboCop::Node] message expectation |
103 | | - def find_subject_expectation(node, subject_name, &block) |
104 | | - # Do not search node if it is an example group with its own subject. |
105 | | - return if example_group?(node) && redefines_subject?(node) |
106 | | - |
107 | | - # Yield the current node if it is a message expectation. |
108 | | - yield(node) if message_expectation?(node, subject_name) |
| 96 | + def find_all_explicit_subjects(node) |
| 97 | + node.each_descendant(:block).each_with_object({}) do |child, h| |
| 98 | + name = subject(child) |
| 99 | + next unless name |
109 | 100 |
|
110 | | - # Recurse through node's children looking for a message expectation. |
111 | | - node.each_child_node do |child| |
112 | | - find_subject_expectation(child, subject_name, &block) |
113 | | - end |
114 | | - end |
| 101 | + outer_example_group = child.each_ancestor.find do |a| |
| 102 | + example_group?(a) |
| 103 | + end |
115 | 104 |
|
116 | | - # Check if node's children contain a subject definition |
117 | | - # |
118 | | - # @param node [RuboCop::Node] |
119 | | - # |
120 | | - # @return [Boolean] |
121 | | - def redefines_subject?(node) |
122 | | - node.each_child_node.any? do |child| |
123 | | - subject(child) || redefines_subject?(child) |
| 105 | + h[outer_example_group] ||= [] |
| 106 | + h[outer_example_group] << name |
124 | 107 | end |
125 | 108 | end |
126 | 109 |
|
127 | | - # Find a subject definition |
128 | | - # |
129 | | - # @param node [RuboCop::Node] |
130 | | - # @param parent [RuboCop::Node,nil] |
131 | | - # |
132 | | - # @yieldparam subject_name [Symbol] name of subject being defined |
133 | | - # @yieldparam parent [RuboCop::Node] parent of subject definition |
134 | | - def find_subject(node, parent: nil, &block) |
135 | | - # An implicit subject is defined by RSpec when no subject is declared |
136 | | - subject_name = subject(node) || :subject |
| 110 | + def find_subject_expectations(node, subject_names = [], &block) |
| 111 | + subject_names = @explicit_subjects[node] if @explicit_subjects[node] |
137 | 112 |
|
138 | | - yield(subject_name, parent) if parent |
| 113 | + expectation_detected = (subject_names + [:subject]).any? do |name| |
| 114 | + message_expectation?(node, name) |
| 115 | + end |
| 116 | + return yield(node) if expectation_detected |
139 | 117 |
|
140 | 118 | node.each_child_node do |child| |
141 | | - find_subject(child, parent: node, &block) |
| 119 | + find_subject_expectations(child, subject_names, &block) |
142 | 120 | end |
143 | 121 | end |
144 | 122 | end |
|
0 commit comments