Class: RuboCop::Cop::Homebrew::UnreferencedLet Private

Inherits:
RSpec::Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
RangeHelp
Defined in:
rubocops/unreferenced_let.rb,
sorbet/rbi/dsl/rubo_cop/cop/homebrew/unreferenced_let.rbi

Overview

This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.

Flags lazy let declarations whose name is never referenced. A lazy let(:name) { ... } is only evaluated when name is called, so an unreferenced one is dead code -- its block never runs -- and is deleted.

Eager let! is intentionally out of scope: it runs its block before every example for its side effect even when unreferenced, so it cannot simply be deleted. Only plain let is handled here.

Detection is file-scoped: a let referenced only from another file (through a shared example or an included test harness) cannot be seen, so the cop stays conservative and prefers false negatives over false positives:

  • a name defined more than once in the file by let/let!/subject (an override / super chain, including a subject that overrides a let of the same name) is never flagged;
  • a let declared lexically inside a shared_examples / shared_examples_for / shared_context block is skipped (its consumers live in other files);
  • every let in a file that uses it_behaves_like / it_should_behave_like / include_examples / include_context is skipped, because an included shared block may reference the binding by a name we cannot follow statically;
  • let(:cop_config) is skipped: it is a rubocop-rspec contract consumed by the :config shared context, not by a reference in the spec file; and
  • every let in a file that reflectively dispatches through a name we cannot resolve statically (e.g. send("expected_#{type}")) is skipped, since any let could be the target. A name counts as referenced if it is called bare (foo), appears as a symbol (:foo) anywhere but the let's own name argument, or appears as an identifier-shaped token inside any string/heredoc literal -- covering dynamic dispatch, :foo entries in data tables the spec later dispatches on, and bindings named only inside raw SQL/GraphQL text.

Because a bare :foo symbol anywhere counts as a reference, commonly-named lets (let(:formula), let(:cask), let(:id)) are essentially never flagged. This conservative bias means the cop realistically only deletes distinctively-named dead lets; it is not a complete dead-let finder.

Example

# bad (name never referenced -- deleted, the block never runs)
let(:unused) { create(:thing) }

# good
let(:thing) { create(:thing) }
it { expect(thing).to be_present }

Constant Summary collapse

DEFINITION_METHODS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

[:let, :let!, :subject].freeze
FRAMEWORK_RESERVED_NAMES =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

lets consumed by a test framework rather than by a reference in the spec file. RuboCop's own :config shared context (used by every cop spec) reads cop_config, other_cops, cop_options and gem_versions by name from inside the framework, so they are live even though the spec never names them.

[:cop_config, :other_cops, :cop_options, :gem_versions].freeze
DYNAMIC_DISPATCH_METHODS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

Reflective dispatch methods whose target is the first argument. When that argument is not a statically-resolvable name (a sym or plain str) -- e.g. send("expected_#{type}") -- the called name cannot be known, so the whole file is left untouched.

[:send, :public_send, :__send__, :try, :try!, :method, :public_method,
:respond_to?].freeze
IDENTIFIER_IN_STRING =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

Identifier-shaped tokens inside a string/heredoc literal. A let whose name appears only inside string text -- e.g. a binding or column referenced in raw SQL/GraphQL the spec later executes -- counts as referenced, so it is not deleted.

/[A-Za-z_]\w*[!?]?/
MSG =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

"Remove unreferenced `let(:%<name>s)` -- its name is never used, so the block never runs."
RESTRICT_ON_SEND =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

[:let].freeze

Instance Method Summary collapse

Instance Method Details

#definition_name(node) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

The name symbol of any definition (let/let!/subject) in any block form -- used to count how many times a name is defined, so override / super chains (including a subject that overrides a let of the same name) are never flagged.

Parameters:

Returns:

  • (T.untyped)


79
80
81
# File 'rubocops/unreferenced_let.rb', line 79

def_node_matcher :definition_name, <<~PATTERN
  (any_block (send nil? {#{DEFINITION_METHODS.map { |method| ":#{method}" }.join(" ")}} (sym $_) ...) ...)
PATTERN

#on_send(node) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:

  • node (RuboCop::AST::SendNode)


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'rubocops/unreferenced_let.rb', line 84

def on_send(node)
  return unless node.receiver.nil?

  name_argument = node.first_argument
  return unless name_argument&.sym_type?

  block = node.block_node
  return unless block

  name = name_argument.value
  return if exempt_from_deletion?(name, block)

  add_offense(node.loc.selector, message: format(MSG, name:)) do |corrector|
    corrector.remove(removal_range(block))
  end
end