Module: RuboCop::Cop::RSpec::UnusedLet::Matchers

Extended by:
AST::NodePattern::Macros
Includes:
RSpec::Language
Included in:
RuboCop::Cop::RSpec::UnusedLet, ScopeBuilder, SharedExampleRegistry
Defined in:
lib/rubocop/cop/rspec/unused_let/matchers.rb,
sig/rubocop/cop/rspec/unused_let/matchers.rbs

Overview

The RSpec vocabulary this cop works from, and the node-pattern matchers that recognize it in an AST: example/shared groups, let/subject definitions, and shared-example inclusions.

Constant Summary collapse

DEFINEE_SCOPE_TYPES =

Node types that open a new method-definition scope by keyword.

Returns:

  • (Object)
%i[def defs class module sclass].freeze
BLOCK_TYPES =

Node types for a block literal, whatever form its parameters take.

Returns:

  • (Object)
%i[block numblock itblock].freeze
SUBJECT_HELPERS =

The helpers that declare a subject.

Returns:

  • (Object)
%i[subject subject!].freeze
BANG_HELPERS =

The helpers whose block runs eagerly, so it may exist for its side effects alone.

Returns:

  • (Object)
%i[let! subject!].freeze
ENCLOSING_BODY_TYPES =

Every node type whose body holds code at a remove from the group it is written in, hiding an example selector from #example_of?. Anything that opens a new definee qualifies: code that does not run in the group's own scope cannot be defining one of its examples.

Returns:

  • (Object)
(BLOCK_TYPES + DEFINEE_SCOPE_TYPES).freeze

Instance Method Summary collapse

Instance Method Details

#anonymous_subject_definitionSymbol?

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Symbol, nil)


64
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 64

def anonymous_subject_definition: (RuboCop::AST::Node node) -> Symbol?

#carries_examples?(node) ⇒ Boolean

Whether node carries an example: one it would run itself, its nested example groups included. For a shared block this has to come from the contents, since the keyword that opened it says nothing: RSpec makes shared_examples and shared_context aliases.

RBS:

  • node: RuboCop::AST::Node

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


175
176
177
# File 'lib/rubocop/cop/rspec/unused_let/matchers.rb', line 175

def carries_examples?(node) #: bool
  node.each_descendant(:send).any? { example_send?(_1) && example_of?(_1, node) }
end

#definee_scope?(node) ⇒ Boolean

Whether node opens a method-definition scope (a new self/definee) other than the example group's, so that a def nested inside it defines a method there rather than an instance method on the group's example class: a class/module/def keyword, or a block other than a nested spec group (which collects its own defs separately) or one #rspec_scope_block? recognizes.

RBS:

  • node: RuboCop::AST::Node

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


41
42
43
44
45
46
# File 'lib/rubocop/cop/rspec/unused_let/matchers.rb', line 41

def definee_scope?(node) #: bool
  return true if keyword_definee?(node)
  return false unless BLOCK_TYPES.include?(node.type)

  !spec_group?(node) && !rspec_scope_block?(node)
end

#example_group?Boolean

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


52
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 52

def example_group?: (RuboCop::AST::Node node) -> bool

#example_of?(example_send, node) ⇒ Boolean

Whether node would run example_send as one of its own examples, rather than merely holding the name: only a nested example group and the example's own block may sit between the two.

What tells the two roles of skip/pending apart is position: at a group's level the call defines a pending example, while inside any other body — a hook, an example, a let, a def helper — it acts on the example already running.

RBS:

  • example_send: RuboCop::AST::Node

  • node: RuboCop::AST::Node

Parameters:

  • example_send (RuboCop::AST::Node)
  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


190
191
192
193
194
195
# File 'lib/rubocop/cop/rspec/unused_let/matchers.rb', line 190

def example_of?(example_send, node) #: bool
  example_send
    .each_ancestor(*ENCLOSING_BODY_TYPES)
    .take_while { !_1.equal?(node) }
    .all? { example_group?(_1) || own_block_of?(_1, example_send) }
end

#example_send?Boolean

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


54
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 54

def example_send?: (RuboCop::AST::Node node) -> bool

#inclusion_call?Boolean

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


66
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 66

def inclusion_call?: (RuboCop::AST::Node node) -> bool

#inclusion_nameSymbol, ...

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Symbol, String, nil)


68
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 68

def inclusion_name: (RuboCop::AST::Node node) -> (Symbol | String)?

#inline_inclusion?(node) ⇒ Boolean

An inline inclusion (include_examples/include_context) injects the shared block's definitions into the current context, where a nested one isolates them in its own group. Any inclusion not recognized as nested counts as inline, so an unknown form errs toward suppressing a same-named let rather than risking a false positive.

RBS:

  • node: RuboCop::AST::Node

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


165
166
167
# File 'lib/rubocop/cop/rspec/unused_let/matchers.rb', line 165

def inline_inclusion?(node) #: bool
  inclusion_call?(node) && !nested_inclusion?(node)
end

#keyword_definee?(node) ⇒ Boolean

Whether node opens a definee by keyword, which leaves no doubt as to what a def inside it defines a method on.

RBS:

  • node: RuboCop::AST::Node

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


52
53
54
# File 'lib/rubocop/cop/rspec/unused_let/matchers.rb', line 52

def keyword_definee?(node) #: bool
  DEFINEE_SCOPE_TYPES.include?(node.type)
end

#let_definition[ Symbol, Symbol | String ]?

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • ([ Symbol, Symbol | String ], nil)


60
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 60

def let_definition: (RuboCop::AST::Node node) -> [ Symbol, Symbol | String ]?

#named_subject_definition[ Symbol, Symbol | String ]?

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • ([ Symbol, Symbol | String ], nil)


62
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 62

def named_subject_definition: (RuboCop::AST::Node node) -> [ Symbol, Symbol | String ]?

#nested_inclusion?Boolean

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


70
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 70

def nested_inclusion?: (RuboCop::AST::Node node) -> bool

#own_block_of?(candidate, example_send) ⇒ Boolean

Whether candidate is the example's own block, as in it { ... }.

RBS:

  • candidate: RuboCop::AST::Node

  • example_send: RuboCop::AST::Node

Parameters:

  • candidate (RuboCop::AST::Node)
  • example_send (RuboCop::AST::Node)

Returns:

  • (Boolean)


201
202
203
204
205
206
# File 'lib/rubocop/cop/rspec/unused_let/matchers.rb', line 201

def own_block_of?(candidate, example_send) #: bool
  return false unless candidate.type?(*BLOCK_TYPES)

  node = candidate #: untyped
  node.send_node.equal?(example_send)
end

#rspec_scope_block?Boolean

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


50
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 50

def rspec_scope_block?: (RuboCop::AST::Node node) -> bool

#shared_group_nameSymbol, ...

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Symbol, String, nil)


58
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 58

def shared_group_name: (RuboCop::AST::Node node) -> (Symbol | String)?

#spec_group?Boolean

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


56
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 56

def spec_group?: (RuboCop::AST::Node node) -> bool

#subject_definition(node) ⇒ [ Symbol, Symbol? ]?

[helper, name] for a subject definition, with name nil for an anonymous subject { }; nil when the node defines no subject.

RBS:

  • node: RuboCop::AST::Node

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • ([ Symbol, Symbol? ], nil)


119
120
121
122
123
124
125
# File 'lib/rubocop/cop/rspec/unused_let/matchers.rb', line 119

def subject_definition(node) #: [ Symbol, Symbol? ]?
  if (named = named_subject_definition(node))
    [named[0], named[1].to_sym]
  elsif (helper = anonymous_subject_definition(node))
    [helper, nil]
  end
end

#subject_definition_head?(node) ⇒ Boolean

Whether node is the subject/subject! call declaring a subject: it heads a block of any kind, or takes one as an argument. Such a call names the subject it declares, not one defined further out, so it must not be read as a reference the way a bare subject call is.

Deliberately wider than #subject_definition, which recognizes only the form rubocop-rspec collects as a definition: a declaration the cop cannot check should not silence a subject elsewhere either.

RBS:

  • node: RuboCop::AST::Node

Parameters:

  • node (RuboCop::AST::Node)

Returns:

  • (Boolean)


137
138
139
140
141
142
143
144
# File 'lib/rubocop/cop/rspec/unused_let/matchers.rb', line 137

def subject_definition_head?(node) #: bool
  return false unless node.send_type?

  send = node #: untyped
  return false unless send.receiver.nil? && SUBJECT_HELPERS.include?(send.method_name)

  send.block_literal? || send.last_argument&.block_pass_type? || false
end