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.
%i[def defs class module sclass].freeze
- BLOCK_TYPES =
Node types for a block literal, whatever form its parameters take.
%i[block numblock itblock].freeze
- SUBJECT_HELPERS =
The helpers that declare a subject.
%i[subject subject!].freeze
- BANG_HELPERS =
The helpers whose block runs eagerly, so it may exist for its side effects alone.
%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.
(BLOCK_TYPES + DEFINEE_SCOPE_TYPES).freeze
Instance Method Summary collapse
- #anonymous_subject_definition ⇒ Symbol?
-
#carries_examples?(node) ⇒ Boolean
Whether
nodecarries an example: one it would run itself, its nested example groups included. -
#definee_scope?(node) ⇒ Boolean
Whether
nodeopens a method-definition scope (a newself/definee) other than the example group's, so that adefnested inside it defines a method there rather than an instance method on the group's example class: aclass/module/defkeyword, or a block other than a nested spec group (which collects its owndefs separately) or one #rspec_scope_block? recognizes. - #example_group? ⇒ Boolean
-
#example_of?(example_send, node) ⇒ Boolean
Whether
nodewould runexample_sendas 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. - #example_send? ⇒ Boolean
- #inclusion_call? ⇒ Boolean
- #inclusion_name ⇒ Symbol, ...
-
#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. -
#keyword_definee?(node) ⇒ Boolean
Whether
nodeopens a definee by keyword, which leaves no doubt as to what adefinside it defines a method on. - #let_definition ⇒ [ Symbol, Symbol | String ]?
- #named_subject_definition ⇒ [ Symbol, Symbol | String ]?
- #nested_inclusion? ⇒ Boolean
-
#own_block_of?(candidate, example_send) ⇒ Boolean
Whether
candidateis the example's own block, as init { ... }. - #rspec_scope_block? ⇒ Boolean
- #shared_group_name ⇒ Symbol, ...
- #spec_group? ⇒ Boolean
-
#subject_definition(node) ⇒ [ Symbol, Symbol? ]?
[helper, name]for a subject definition, withnamenilfor an anonymoussubject { };nilwhen the node defines no subject. -
#subject_definition_head?(node) ⇒ Boolean
Whether
nodeis thesubject/subject!call declaring a subject: it heads a block of any kind, or takes one as an argument.
Instance Method Details
#anonymous_subject_definition ⇒ Symbol?
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.
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.
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
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.
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
54 |
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 54
def example_send?: (RuboCop::AST::Node node) -> bool
|
#inclusion_call? ⇒ Boolean
66 |
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 66
def inclusion_call?: (RuboCop::AST::Node node) -> bool
|
#inclusion_name ⇒ Symbol, ...
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.
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.
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 ]?
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 ]?
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
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 { ... }.
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
50 |
# File 'sig/rubocop/cop/rspec/unused_let/matchers.rbs', line 50
def rspec_scope_block?: (RuboCop::AST::Node node) -> bool
|
#shared_group_name ⇒ Symbol, ...
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
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.
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.
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 |