Class: RuboCop::Cop::Gusto::DescribedClassConstantReference

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Defined in:
lib/rubocop/cop/gusto/described_class_constant_reference.rb

Overview

Flags constants that are scoped through described_class, e.g. described_class::Worker.

described_class is an RSpec helper method resolved at runtime, so Sorbet's static analysis treats described_class::Worker as a dynamic constant reference and cannot resolve it (Dynamic constant references are unsupported, https://srb.help/5001). Reference the constant by its fully-qualified name instead. A bare described_class (with no :: constant lookup) is an ordinary method call and is left alone.

Autocorrection replaces described_class with the constant that the enclosing example group describes. It is marked unsafe (SafeAutoCorrect: false) because the rewrite relies on the described constant being a statically-written name; review the result before committing. In particular, a constant defined on an ancestor of the described class is qualified against the described class itself, which is correct at runtime but which Sorbet cannot resolve through the inheritance chain -- re-point those to the defining ancestor by hand.

Examples:

# bad
RSpec.describe Payments::Processor do
  describe described_class::Worker do
  end
end

# good
RSpec.describe Payments::Processor do
  describe Payments::Processor::Worker do
  end
end

# good - `RSpec.describe self` resolves to the enclosing namespace
module Payments
  RSpec.describe self do
    it { expect(Payments::TIMEOUT).to eq(5) }
  end
end

# good - a bare `described_class` is not a constant reference
RSpec.describe Payments::Processor do
  subject { described_class.new }
end

Constant Summary collapse

MSG =
"Use the fully-qualified constant name instead of scoping it through " \
"`described_class`, which Sorbet cannot resolve statically."

Instance Method Summary collapse

Instance Method Details

#const_scoped_on_described_class?(node) ⇒ Object

A constant whose scope is a no-receiver described_class, e.g. described_class::Worker.



58
59
60
# File 'lib/rubocop/cop/gusto/described_class_constant_reference.rb', line 58

def_node_matcher :const_scoped_on_described_class?, <<~PATTERN
  (const (send nil? :described_class) _)
PATTERN

#example_group_described_argument(node) ⇒ Object

An example group, capturing its first argument: a constant (RSpec.describe Foo do, context Foo do), self (RSpec.describe self do), and so on.



66
67
68
69
70
71
72
# File 'lib/rubocop/cop/gusto/described_class_constant_reference.rb', line 66

def_node_matcher :example_group_described_argument, <<~PATTERN
  (block
    (send {(const nil? :RSpec) nil?}
      {:describe :xdescribe :fdescribe :context :xcontext :fcontext :feature :example_group}
      $_ ...)
    ...)
PATTERN

#on_const(node) ⇒ Object



80
81
82
83
84
85
86
87
88
# File 'lib/rubocop/cop/gusto/described_class_constant_reference.rb', line 80

def on_const(node)
  return unless const_scoped_on_described_class?(node)

  scope = node.children[0]
  add_offense(scope) do |corrector|
    replacement = described_class_replacement(node)
    corrector.replace(scope, replacement) if replacement
  end
end

#scoped_through_described_class?(node) ⇒ Object

Whether a node routes through a no-receiver described_class.



76
77
78
# File 'lib/rubocop/cop/gusto/described_class_constant_reference.rb', line 76

def_node_search :scoped_through_described_class?, <<~PATTERN
  (send nil? :described_class)
PATTERN