|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
| 3 | +require 'set' |
| 4 | + |
3 | 5 | module RuboCop |
4 | 6 | module Cop |
5 | 7 | module RSpec |
@@ -74,71 +76,58 @@ class SubjectStub < Cop |
74 | 76 | PATTERN |
75 | 77 |
|
76 | 78 | def on_block(node) |
| 79 | + # process only describe/context/... example groups |
77 | 80 | return unless example_group?(node) |
78 | 81 |
|
79 | | - find_subject_stub(node) do |stub| |
| 82 | + # skip already processed example group |
| 83 | + # it's processed if is nested in one of the processed example groups |
| 84 | + return unless (processed_example_groups & node.ancestors).empty? |
| 85 | + |
| 86 | + # add example group to already processed |
| 87 | + processed_example_groups << node |
| 88 | + |
| 89 | + # find all custom subjects e.g. subject(:foo) { ... } |
| 90 | + @named_subjects = find_all_named_subjects(node) |
| 91 | + |
| 92 | + # look for method expectation matcher |
| 93 | + find_subject_expectations(node) do |stub| |
80 | 94 | add_offense(stub) |
81 | 95 | end |
82 | 96 | end |
83 | 97 |
|
84 | 98 | private |
85 | 99 |
|
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 |
| 100 | + def processed_example_groups |
| 101 | + @processed_example_groups ||= Set[] |
95 | 102 | end |
96 | 103 |
|
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) |
| 104 | + def find_all_named_subjects(node) |
| 105 | + named_subjects = {} |
109 | 106 |
|
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) |
| 107 | + node.each_descendant(:block) do |child| |
| 108 | + name = subject(child) |
| 109 | + named_subjects[child.parent.parent] = name if name |
113 | 110 | end |
| 111 | + |
| 112 | + named_subjects |
114 | 113 | end |
115 | 114 |
|
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) |
| 115 | + def find_subject_expectations(node, subject_name = nil, &block) |
| 116 | + # if it's a new example group - check whether new named subject is |
| 117 | + # defined there |
| 118 | + if example_group?(node) |
| 119 | + subject_name = @named_subjects[node] || subject_name |
124 | 120 | end |
125 | | - end |
126 | 121 |
|
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 |
| 122 | + # check default :subject and then named one (if it's present) |
| 123 | + expectation_detected = message_expectation?(node, :subject) || \ |
| 124 | + (subject_name && message_expectation?(node, subject_name)) |
137 | 125 |
|
138 | | - yield(subject_name, parent) if parent |
| 126 | + return yield(node) if expectation_detected |
139 | 127 |
|
| 128 | + # Recurse through node's children looking for a message expectation. |
140 | 129 | node.each_child_node do |child| |
141 | | - find_subject(child, parent: node, &block) |
| 130 | + find_subject_expectations(child, subject_name, &block) |
142 | 131 | end |
143 | 132 | end |
144 | 133 | end |
|
0 commit comments