Class: RuboCop::NodePattern

Inherits:
Object
  • Object
show all
Defined in:
lib/rubocop/node_pattern.rb

Overview

This class performs a pattern-matching operation on an AST node.

Initialize a new NodePattern with NodePattern.new(pattern_string), then pass an AST node to NodePattern#match. Alternatively, use one of the class macros in NodePattern::Macros to define your own pattern-matching method.

If the match fails, nil will be returned. If the match succeeds, the return value depends on whether a block was provided to #match, and whether the pattern contained any "captures" (values which are extracted from a matching AST.)

  • With block: #match yields the captures (if any) and passes the return value of the block through.
  • With no block, but one capture: the capture is returned.
  • With no block, but multiple captures: captures are returned as an array.
  • With no captures: #match returns true.

Pattern string format examples

':sym'              # matches a literal symbol
'1'                 # matches a literal integer
'nil'               # matches a literal nil
'send'              # matches (send ...)
'(send)'            # matches (send)
'(send ...)'        # matches (send ...)
'{send class}'      # matches (send ...) or (class ...)
'({send class})'    # matches (send) or (class)
'(send const)'      # matches (send (const ...))
'(send _ :new)'     # matches (send <anything> :new)
'(send $_ :new)'    # as above, but whatever matches the $_ is captured
'(send $_ $_)'      # you can use as many captures as you want
'(send !const ...)' # ! negates the next part of the pattern
'$(send const ...)' # arbitrary matching can be performed on a capture
'(send _recv _msg)' # wildcards can be named (for readability)
'(send ... :new)'   # you can specifically match against the last child
                   # (this only works for the very last)
'(send $...)'       # capture all the children as an array
'(send $... int)'   # capture all children but the last as an array
'(send _x :+ _x)'   # unification is performed on named wildcards
                   # (like Prolog variables...)
'(int odd?)'        # words which end with a ? are predicate methods,
                   # are are called on the target to see if it matches
                   # any Ruby method which the matched object supports
                   # can be used
'(int [!1 !2])'     # [] contains multiple patterns, ALL of which must
                   # match in that position
                   # ({} is pattern union, [] is intersection)
'(send %1 _)'       # % stands for a parameter which must be supplied to
                   # #match at matching time
                   # it will be compared to the corresponding value in
                   # the AST using #==
                   # a bare '%' is the same as '%1'
'^^send'            # each ^ ascends one level in the AST
                   # so this matches against the grandparent node

You can nest arbitrarily deep:

# matches node parsed from 'Const = Class.new' or 'Const = Module.new':
'(casgn nil const (send (const nil {:Class :Module}) :new)))'
# matches a node parsed from an 'if', with a '==' comparison,
# and no 'else' branch:
'(if (send _ :== _) _ nil)'

Note that patterns like 'send' are implemented by calling #send_type? on the node being matched, 'const' by #const_type?, 'int' by #int_type?, and so on. Therefore, if you add methods which are named like #prefix_type? to the AST node class, then 'prefix' will become usable as a pattern.

Also node that if you need a "guard clause" to protect against possible nils in a certain place in the AST, you can do it like this: [!nil <pattern>]

Defined Under Namespace

Modules: Macros Classes: Compiler

Constant Summary collapse

Invalid =
Class.new(StandardError)

Instance Method Summary collapse

Constructor Details

#initialize(str) ⇒ NodePattern

Returns a new instance of NodePattern.



383
384
385
386
387
388
# File 'lib/rubocop/node_pattern.rb', line 383

def initialize(str)
  compiler = Compiler.new(str)
  src = 'def match(node0' << compiler.emit_trailing_param_list << ');' <<
        compiler.emit_method_code << 'end'
  instance_eval(src)
end