Skip to content

Commit

Permalink
RSpec/SubjectStub. Refactor and decrease complexity
Browse files Browse the repository at this point in the history
  • Loading branch information
andrykonchin committed Jun 7, 2020
1 parent 97e8ebb commit 20e37fb
Showing 1 changed file with 35 additions and 46 deletions.
81 changes: 35 additions & 46 deletions lib/rubocop/cop/rspec/subject_stub.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'set'

module RuboCop
module Cop
module RSpec
Expand Down Expand Up @@ -74,71 +76,58 @@ class SubjectStub < Cop
PATTERN

def on_block(node)
# process only describe/context/... example groups
return unless example_group?(node)

find_subject_stub(node) do |stub|
# skip already processed example group
# it's processed if is nested in one of the processed example groups
return unless (processed_example_groups & node.ancestors).empty?

# add example group to already processed
processed_example_groups << node

# find all custom subjects e.g. subject(:foo) { ... }
@named_subjects = find_all_named_subjects(node)

# look for method expectation matcher
find_subject_expectations(node) do |stub|
add_offense(stub)
end
end

private

# Find subjects within tree and then find (send) nodes for that subject
#
# @param node [RuboCop::Node] example group
#
# @yield [RuboCop::Node] message expectations for subject
def find_subject_stub(node, &block)
find_subject(node) do |subject_name, context|
find_subject_expectation(context, subject_name, &block)
end
def processed_example_groups
@processed_example_groups ||= Set[]
end

# Find a subject message expectation
#
# @param node [RuboCop::Node]
# @param subject_name [Symbol] name of subject
#
# @yield [RuboCop::Node] message expectation
def find_subject_expectation(node, subject_name, &block)
# Do not search node if it is an example group with its own subject.
return if example_group?(node) && redefines_subject?(node)

# Yield the current node if it is a message expectation.
yield(node) if message_expectation?(node, subject_name)
def find_all_named_subjects(node)
named_subjects = {}

# Recurse through node's children looking for a message expectation.
node.each_child_node do |child|
find_subject_expectation(child, subject_name, &block)
node.each_descendant(:block) do |child|
name = subject(child)
named_subjects[child.parent.parent] = name if name
end

named_subjects
end

# Check if node's children contain a subject definition
#
# @param node [RuboCop::Node]
#
# @return [Boolean]
def redefines_subject?(node)
node.each_child_node.any? do |child|
subject(child) || redefines_subject?(child)
def find_subject_expectations(node, subject_name = nil, &block)
# if it's a new example group - check whether new named subject is
# defined there
if example_group?(node)
subject_name = @named_subjects[node] || subject_name
end
end

# Find a subject definition
#
# @param node [RuboCop::Node]
# @param parent [RuboCop::Node,nil]
#
# @yieldparam subject_name [Symbol] name of subject being defined
# @yieldparam parent [RuboCop::Node] parent of subject definition
def find_subject(node, parent: nil, &block)
# An implicit subject is defined by RSpec when no subject is declared
subject_name = subject(node) || :subject
# check default :subject and then named one (if it's present)
expectation_detected = message_expectation?(node, :subject) || \
(subject_name && message_expectation?(node, subject_name))

yield(subject_name, parent) if parent
return yield(node) if expectation_detected

# Recurse through node's children looking for a message expectation.
node.each_child_node do |child|
find_subject(child, parent: node, &block)
find_subject_expectations(child, subject_name, &block)
end
end
end
Expand Down

0 comments on commit 20e37fb

Please sign in to comment.