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`, 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