rubocop-ast 0.3.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rubocop/ast.rb +21 -0
  3. data/lib/rubocop/ast/builder.rb +3 -0
  4. data/lib/rubocop/ast/ext/set.rb +12 -0
  5. data/lib/rubocop/ast/node.rb +96 -127
  6. data/lib/rubocop/ast/node/array_node.rb +1 -0
  7. data/lib/rubocop/ast/node/block_node.rb +2 -1
  8. data/lib/rubocop/ast/node/const_node.rb +65 -0
  9. data/lib/rubocop/ast/node/def_node.rb +5 -0
  10. data/lib/rubocop/ast/node/keyword_splat_node.rb +1 -0
  11. data/lib/rubocop/ast/node/mixin/collection_node.rb +1 -0
  12. data/lib/rubocop/ast/node/mixin/descendence.rb +116 -0
  13. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +16 -24
  14. data/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +9 -0
  15. data/lib/rubocop/ast/node/mixin/numeric_node.rb +1 -0
  16. data/lib/rubocop/ast/node/mixin/parameterized_node.rb +2 -2
  17. data/lib/rubocop/ast/node/mixin/predicate_operator_node.rb +7 -3
  18. data/lib/rubocop/ast/node/pair_node.rb +4 -0
  19. data/lib/rubocop/ast/node/regexp_node.rb +9 -4
  20. data/lib/rubocop/ast/node/resbody_node.rb +21 -0
  21. data/lib/rubocop/ast/node/rescue_node.rb +49 -0
  22. data/lib/rubocop/ast/node_pattern.rb +44 -870
  23. data/lib/rubocop/ast/node_pattern/builder.rb +72 -0
  24. data/lib/rubocop/ast/node_pattern/comment.rb +45 -0
  25. data/lib/rubocop/ast/node_pattern/compiler.rb +104 -0
  26. data/lib/rubocop/ast/node_pattern/compiler/atom_subcompiler.rb +56 -0
  27. data/lib/rubocop/ast/node_pattern/compiler/binding.rb +78 -0
  28. data/lib/rubocop/ast/node_pattern/compiler/debug.rb +168 -0
  29. data/lib/rubocop/ast/node_pattern/compiler/node_pattern_subcompiler.rb +146 -0
  30. data/lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb +420 -0
  31. data/lib/rubocop/ast/node_pattern/compiler/subcompiler.rb +57 -0
  32. data/lib/rubocop/ast/node_pattern/lexer.rb +70 -0
  33. data/lib/rubocop/ast/node_pattern/lexer.rex +39 -0
  34. data/lib/rubocop/ast/node_pattern/lexer.rex.rb +182 -0
  35. data/lib/rubocop/ast/node_pattern/method_definer.rb +143 -0
  36. data/lib/rubocop/ast/node_pattern/node.rb +275 -0
  37. data/lib/rubocop/ast/node_pattern/parser.racc.rb +470 -0
  38. data/lib/rubocop/ast/node_pattern/parser.rb +66 -0
  39. data/lib/rubocop/ast/node_pattern/parser.y +103 -0
  40. data/lib/rubocop/ast/node_pattern/sets.rb +37 -0
  41. data/lib/rubocop/ast/node_pattern/with_meta.rb +111 -0
  42. data/lib/rubocop/ast/processed_source.rb +44 -3
  43. data/lib/rubocop/ast/rubocop_compatibility.rb +31 -0
  44. data/lib/rubocop/ast/traversal.rb +149 -172
  45. data/lib/rubocop/ast/version.rb +1 -1
  46. metadata +28 -4
@@ -58,6 +58,11 @@ module RuboCop
58
58
  def receiver
59
59
  children[-4]
60
60
  end
61
+
62
+ # @return [Boolean] if the definition is without an `end` or not.
63
+ def endless?
64
+ !loc.end
65
+ end
61
66
  end
62
67
  end
63
68
  end
@@ -9,6 +9,7 @@ module RuboCop
9
9
  include HashElementNode
10
10
 
11
11
  DOUBLE_SPLAT = '**'
12
+ private_constant :DOUBLE_SPLAT
12
13
 
13
14
  # This is used for duck typing with `pair` nodes which also appear as
14
15
  # `hash` elements.
@@ -8,6 +8,7 @@ module RuboCop
8
8
 
9
9
  ARRAY_METHODS =
10
10
  (Array.instance_methods - Object.instance_methods - [:to_a]).freeze
11
+ private_constant :ARRAY_METHODS
11
12
 
12
13
  def_delegators :to_a, *ARRAY_METHODS
13
14
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # Common functionality for primitive literal nodes: `sym`, `str`,
6
+ # `int`, `float`, ...
7
+ module Descendence
8
+ # Calls the given block for each child node.
9
+ # If no block is given, an `Enumerator` is returned.
10
+ #
11
+ # Note that this is different from `node.children.each { |child| ... }`
12
+ # which yields all children including non-node elements.
13
+ #
14
+ # @overload each_child_node
15
+ # Yield all nodes.
16
+ # @overload each_child_node(type)
17
+ # Yield only nodes matching the type.
18
+ # @param [Symbol] type a node type
19
+ # @overload each_child_node(type_a, type_b, ...)
20
+ # Yield only nodes matching any of the types.
21
+ # @param [Symbol] type_a a node type
22
+ # @param [Symbol] type_b a node type
23
+ # @yieldparam [Node] node each child node
24
+ # @return [self] if a block is given
25
+ # @return [Enumerator] if no block is given
26
+ def each_child_node(*types)
27
+ return to_enum(__method__, *types) unless block_given?
28
+
29
+ children.each do |child|
30
+ next unless child.is_a?(::AST::Node)
31
+
32
+ yield child if types.empty? || types.include?(child.type)
33
+ end
34
+
35
+ self
36
+ end
37
+
38
+ # Returns an array of child nodes.
39
+ # This is a shorthand for `node.each_child_node.to_a`.
40
+ #
41
+ # @return [Array<Node>] an array of child nodes
42
+ def child_nodes
43
+ each_child_node.to_a
44
+ end
45
+
46
+ # Calls the given block for each descendant node with depth first order.
47
+ # If no block is given, an `Enumerator` is returned.
48
+ #
49
+ # @overload each_descendant
50
+ # Yield all nodes.
51
+ # @overload each_descendant(type)
52
+ # Yield only nodes matching the type.
53
+ # @param [Symbol] type a node type
54
+ # @overload each_descendant(type_a, type_b, ...)
55
+ # Yield only nodes matching any of the types.
56
+ # @param [Symbol] type_a a node type
57
+ # @param [Symbol] type_b a node type
58
+ # @yieldparam [Node] node each descendant node
59
+ # @return [self] if a block is given
60
+ # @return [Enumerator] if no block is given
61
+ def each_descendant(*types, &block)
62
+ return to_enum(__method__, *types) unless block_given?
63
+
64
+ visit_descendants(types, &block)
65
+
66
+ self
67
+ end
68
+
69
+ # Returns an array of descendant nodes.
70
+ # This is a shorthand for `node.each_descendant.to_a`.
71
+ #
72
+ # @return [Array<Node>] an array of descendant nodes
73
+ def descendants
74
+ each_descendant.to_a
75
+ end
76
+
77
+ # Calls the given block for the receiver and each descendant node in
78
+ # depth-first order.
79
+ # If no block is given, an `Enumerator` is returned.
80
+ #
81
+ # This method would be useful when you treat the receiver node as the root
82
+ # of a tree and want to iterate over all nodes in the tree.
83
+ #
84
+ # @overload each_node
85
+ # Yield all nodes.
86
+ # @overload each_node(type)
87
+ # Yield only nodes matching the type.
88
+ # @param [Symbol] type a node type
89
+ # @overload each_node(type_a, type_b, ...)
90
+ # Yield only nodes matching any of the types.
91
+ # @param [Symbol] type_a a node type
92
+ # @param [Symbol] type_b a node type
93
+ # @yieldparam [Node] node each node
94
+ # @return [self] if a block is given
95
+ # @return [Enumerator] if no block is given
96
+ def each_node(*types, &block)
97
+ return to_enum(__method__, *types) unless block_given?
98
+
99
+ yield self if types.empty? || types.include?(type)
100
+
101
+ visit_descendants(types, &block)
102
+
103
+ self
104
+ end
105
+
106
+ protected
107
+
108
+ def visit_descendants(types, &block)
109
+ each_child_node do |child|
110
+ yield child if types.empty? || types.include?(child.type)
111
+ child.visit_descendants(types, &block)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -10,7 +10,9 @@ module RuboCop
10
10
  include MethodIdentifierPredicates
11
11
 
12
12
  ARITHMETIC_OPERATORS = %i[+ - * / % **].freeze
13
+ private_constant :ARITHMETIC_OPERATORS
13
14
  SPECIAL_MODIFIERS = %w[private protected].freeze
15
+ private_constant :SPECIAL_MODIFIERS
14
16
 
15
17
  # The receiving node of the method dispatch.
16
18
  #
@@ -42,7 +44,7 @@ module RuboCop
42
44
  #
43
45
  # @return [Boolean] whether the dispatched method is a macro method
44
46
  def macro?
45
- !receiver && macro_scope?
47
+ !receiver && in_macro_scope?
46
48
  end
47
49
 
48
50
  # Checks whether the dispatched method is an access modifier.
@@ -222,31 +224,21 @@ module RuboCop
222
224
 
223
225
  private
224
226
 
225
- def_node_matcher :macro_scope?, <<~PATTERN
226
- {^{({sclass class module block} ...) class_constructor?}
227
- ^^{({sclass class module block} ... ({begin if} ...)) class_constructor?}
228
- ^#macro_kwbegin_wrapper?
229
- #root_node?}
227
+ def_node_matcher :in_macro_scope?, <<~PATTERN
228
+ {
229
+ root? # Either a root node,
230
+ ^{ # or the parent is...
231
+ sclass class module class_constructor? # a class-like node
232
+ [ { # or some "wrapper"
233
+ kwbegin begin block
234
+ (if _condition <%0 _>) # note: we're excluding the condition of `if` nodes
235
+ }
236
+ #in_macro_scope? # that is itself in a macro scope
237
+ ]
238
+ }
239
+ }
230
240
  PATTERN
231
241
 
232
- # Check if a node's parent is a kwbegin wrapper within a macro scope
233
- #
234
- # @param parent [Node] parent of the node being checked
235
- #
236
- # @return [Boolean] true if the parent is a kwbegin in a macro scope
237
- def macro_kwbegin_wrapper?(parent)
238
- parent.kwbegin_type? && macro_scope?(parent)
239
- end
240
-
241
- # Check if a node does not have a parent
242
- #
243
- # @param node [Node]
244
- #
245
- # @return [Boolean] if the parent is nil
246
- def root_node?(node)
247
- node.parent.nil?
248
- end
249
-
250
242
  def_node_matcher :adjacent_def_modifier?, <<~PATTERN
251
243
  (send nil? _ ({def defs} ...))
252
244
  PATTERN
@@ -11,17 +11,23 @@ module RuboCop
11
11
  find find_all find_index inject loop map!
12
12
  map reduce reject reject! reverse_each select
13
13
  select! times upto].to_set.freeze
14
+ private_constant :ENUMERATOR_METHODS
14
15
 
15
16
  ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).to_set.freeze
17
+ private_constant :ENUMERABLE_METHODS
16
18
 
17
19
  # http://phrogz.net/programmingruby/language.html#table_18.4
18
20
  OPERATOR_METHODS = %i[| ^ & <=> == === =~ > >= < <= << >> + - * /
19
21
  % ** ~ +@ -@ !@ ~@ [] []= ! != !~ `].to_set.freeze
22
+ private_constant :OPERATOR_METHODS
20
23
 
21
24
  NONMUTATING_BINARY_OPERATOR_METHODS = %i[* / % + - == === != < > <= >= <=>].to_set.freeze
25
+ private_constant :NONMUTATING_BINARY_OPERATOR_METHODS
22
26
  NONMUTATING_UNARY_OPERATOR_METHODS = %i[+@ -@ ~ !].to_set.freeze
27
+ private_constant :NONMUTATING_UNARY_OPERATOR_METHODS
23
28
  NONMUTATING_OPERATOR_METHODS = (NONMUTATING_BINARY_OPERATOR_METHODS +
24
29
  NONMUTATING_UNARY_OPERATOR_METHODS).freeze
30
+ private_constant :NONMUTATING_OPERATOR_METHODS
25
31
 
26
32
  NONMUTATING_ARRAY_METHODS = %i[
27
33
  all? any? assoc at bsearch bsearch_index collect
@@ -37,6 +43,7 @@ module RuboCop
37
43
  to_a to_ary to_h to_s transpose union uniq
38
44
  values_at zip |
39
45
  ].to_set.freeze
46
+ private_constant :NONMUTATING_ARRAY_METHODS
40
47
 
41
48
  NONMUTATING_HASH_METHODS = %i[
42
49
  any? assoc compact dig each each_key each_pair
@@ -47,6 +54,7 @@ module RuboCop
47
54
  to_proc to_s transform_keys transform_values value?
48
55
  values values_at
49
56
  ].to_set.freeze
57
+ private_constant :NONMUTATING_HASH_METHODS
50
58
 
51
59
  NONMUTATING_STRING_METHODS = %i[
52
60
  ascii_only? b bytes bytesize byteslice capitalize
@@ -62,6 +70,7 @@ module RuboCop
62
70
  to_str to_sym tr tr_s unicode_normalize unicode_normalized?
63
71
  unpack unpack1 upcase upto valid_encoding?
64
72
  ].to_set.freeze
73
+ private_constant :NONMUTATING_STRING_METHODS
65
74
 
66
75
  # Checks whether the method name matches the argument.
67
76
  #
@@ -5,6 +5,7 @@ module RuboCop
5
5
  # Common functionality for primitive numeric nodes: `int`, `float`, ...
6
6
  module NumericNode
7
7
  SIGN_REGEX = /\A[+-]/.freeze
8
+ private_constant :SIGN_REGEX
8
9
 
9
10
  # Checks whether this is literal has a sign.
10
11
  #
@@ -82,9 +82,9 @@ module RuboCop
82
82
  # and optimizes other calls
83
83
  module RestArguments
84
84
  include ParameterizedNode
85
- # @return [Array] arguments, if any
85
+ # @return [Array<Node>] arguments, if any
86
86
  def arguments
87
- children[first_argument_index..-1]
87
+ children[first_argument_index..-1].freeze
88
88
  end
89
89
 
90
90
  # A shorthand for getting the first argument of the node.
@@ -5,10 +5,14 @@ module RuboCop
5
5
  # Common functionality for nodes that are predicates:
6
6
  # `or`, `and` ...
7
7
  module PredicateOperatorNode
8
- LOGICAL_AND = '&&'
8
+ LOGICAL_AND = '&&'
9
+ private_constant :LOGICAL_AND
9
10
  SEMANTIC_AND = 'and'
10
- LOGICAL_OR = '||'
11
- SEMANTIC_OR = 'or'
11
+ private_constant :SEMANTIC_AND
12
+ LOGICAL_OR = '||'
13
+ private_constant :LOGICAL_OR
14
+ SEMANTIC_OR = 'or'
15
+ private_constant :SEMANTIC_OR
12
16
 
13
17
  # Returns the operator as a string.
14
18
  #
@@ -9,9 +9,13 @@ module RuboCop
9
9
  include HashElementNode
10
10
 
11
11
  HASH_ROCKET = '=>'
12
+ private_constant :HASH_ROCKET
12
13
  SPACED_HASH_ROCKET = ' => '
14
+ private_constant :SPACED_HASH_ROCKET
13
15
  COLON = ':'
16
+ private_constant :COLON
14
17
  SPACED_COLON = ': '
18
+ private_constant :SPACED_COLON
15
19
 
16
20
  # Checks whether the `pair` uses a hash rocket delimiter.
17
21
  #
@@ -13,13 +13,11 @@ module RuboCop
13
13
  n: Regexp::NOENCODING,
14
14
  o: 0
15
15
  }.freeze
16
+ private_constant :OPTIONS
16
17
 
17
- # Note: The 'o' option is ignored.
18
- #
19
18
  # @return [Regexp] a regexp of this node
20
19
  def to_regexp
21
- option = regopt.children.map { |opt| OPTIONS.fetch(opt) }.inject(:|)
22
- Regexp.new(content, option)
20
+ Regexp.new(content, options)
23
21
  end
24
22
 
25
23
  # @return [RuboCop::AST::Node] a regopt node
@@ -27,6 +25,13 @@ module RuboCop
27
25
  children.last
28
26
  end
29
27
 
28
+ # Note: The 'o' option is ignored.
29
+ #
30
+ # @return [Integer] the Regexp option bits as returned by Regexp#options
31
+ def options
32
+ regopt.children.map { |opt| OPTIONS.fetch(opt) }.inject(0, :|)
33
+ end
34
+
30
35
  # @return [String] a string of regexp content
31
36
  def content
32
37
  children.select(&:str_type?).map(&:str_content).join
@@ -13,12 +13,33 @@ module RuboCop
13
13
  node_parts[2]
14
14
  end
15
15
 
16
+ # Returns an array of all the exceptions in the `rescue` clause.
17
+ #
18
+ # @return [Array<Node>] an array of exception nodes
19
+ def exceptions
20
+ exceptions_node = node_parts[0]
21
+ if exceptions_node.nil?
22
+ []
23
+ elsif exceptions_node.array_type?
24
+ exceptions_node.values
25
+ else
26
+ [exceptions_node]
27
+ end
28
+ end
29
+
16
30
  # Returns the exception variable of the `rescue` clause.
17
31
  #
18
32
  # @return [Node, nil] The exception variable of the `resbody`.
19
33
  def exception_variable
20
34
  node_parts[1]
21
35
  end
36
+
37
+ # Returns the index of the `resbody` branch within the exception handling statement.
38
+ #
39
+ # @return [Integer] the index of the `resbody` branch
40
+ def branch_index
41
+ parent.resbody_branches.index(self)
42
+ end
22
43
  end
23
44
  end
24
45
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # A node extension for `rescue` nodes. This will be used in place of a
6
+ # plain node when the builder constructs the AST, making its methods
7
+ # available to all `rescue` nodes within RuboCop.
8
+ class RescueNode < Node
9
+ # Returns the body of the rescue node.
10
+ #
11
+ # @return [Node, nil] The body of the rescue node.
12
+ def body
13
+ node_parts[0]
14
+ end
15
+
16
+ # Returns an array of all the rescue branches in the exception handling statement.
17
+ #
18
+ # @return [Array<ResbodyNode>] an array of `resbody` nodes
19
+ def resbody_branches
20
+ node_parts[1...-1]
21
+ end
22
+
23
+ # Returns an array of all the rescue branches in the exception handling statement.
24
+ #
25
+ # @return [Array<Node, nil>] an array of the bodies of the rescue branches
26
+ # and the else (if any). Note that these bodies could be nil.
27
+ def branches
28
+ bodies = resbody_branches.map(&:body)
29
+ bodies.push(else_branch) if else?
30
+ bodies
31
+ end
32
+
33
+ # Returns the else branch of the exception handling statement, if any.
34
+ #
35
+ # @return [Node] the else branch node of the exception handling statement
36
+ # @return [nil] if the exception handling statement does not have an else branch.
37
+ def else_branch
38
+ node_parts[-1]
39
+ end
40
+
41
+ # Checks whether this exception handling statement has an `else` branch.
42
+ #
43
+ # @return [Boolean] whether the exception handling statement has an `else` branch
44
+ def else?
45
+ loc.else
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'delegate'
4
- require 'erb'
5
4
 
6
- # rubocop:disable Metrics/ClassLength, Metrics/CyclomaticComplexity
7
5
  module RuboCop
8
6
  module AST
9
7
  # This class performs a pattern-matching operation on an AST node.
10
8
  #
9
+ # Detailed syntax: /doc/modules/ROOT/pages/node_pattern.md
10
+ #
11
11
  # Initialize a new `NodePattern` with `NodePattern.new(pattern_string)`, then
12
12
  # pass an AST node to `NodePattern#match`. Alternatively, use one of the class
13
13
  # macros in `NodePattern::Macros` to define your own pattern-matching method.
@@ -23,838 +23,7 @@ module RuboCop
23
23
  # - With no block, but multiple captures: captures are returned as an array.
24
24
  # - With no block and no captures: #match returns `true`.
25
25
  #
26
- # ## Pattern string format examples
27
- #
28
- # ':sym' # matches a literal symbol
29
- # '1' # matches a literal integer
30
- # 'nil' # matches a literal nil
31
- # 'send' # matches (send ...)
32
- # '(send)' # matches (send)
33
- # '(send ...)' # matches (send ...)
34
- # '(op-asgn)' # node types with hyphenated names also work
35
- # '{send class}' # matches (send ...) or (class ...)
36
- # '({send class})' # matches (send) or (class)
37
- # '(send const)' # matches (send (const ...))
38
- # '(send _ :new)' # matches (send <anything> :new)
39
- # '(send $_ :new)' # as above, but whatever matches the $_ is captured
40
- # '(send $_ $_)' # you can use as many captures as you want
41
- # '(send !const ...)' # ! negates the next part of the pattern
42
- # '$(send const ...)' # arbitrary matching can be performed on a capture
43
- # '(send _recv _msg)' # wildcards can be named (for readability)
44
- # '(send ... :new)' # you can match against the last children
45
- # '(array <str sym>)' # you can match children in any order. This
46
- # # would match `['x', :y]` as well as `[:y, 'x']
47
- # '(_ <str sym ...>)' # will match if arguments have at least a `str` and
48
- # # a `sym` node, but can have more.
49
- # '(array <$str $_>)' # captures are in the order of the pattern,
50
- # # irrespective of the actual order of the children
51
- # '(array int*)' # will match an array of 0 or more integers
52
- # '(array int ?)' # will match 0 or 1 integer.
53
- # # Note: Space needed to distinguish from int?
54
- # '(array int+)' # will match an array of 1 or more integers
55
- # '(array (int $_)+)' # as above and will capture the numbers in an array
56
- # '(send $...)' # capture all the children as an array
57
- # '(send $... int)' # capture all children but the last as an array
58
- # '(send _x :+ _x)' # unification is performed on named wildcards
59
- # # (like Prolog variables...)
60
- # # (#== is used to see if values unify)
61
- # '(int odd?)' # words which end with a ? are predicate methods,
62
- # # are are called on the target to see if it matches
63
- # # any Ruby method which the matched object supports
64
- # # can be used
65
- # # if a truthy value is returned, the match succeeds
66
- # '(int [!1 !2])' # [] contains multiple patterns, ALL of which must
67
- # # match in that position
68
- # # in other words, while {} is pattern union (logical
69
- # # OR), [] is intersection (logical AND)
70
- # '(send %1 _)' # % stands for a parameter which must be supplied to
71
- # # #match at matching time
72
- # # it will be compared to the corresponding value in
73
- # # the AST using #=== so you can pass Procs, Regexp,
74
- # # etc. in addition to Nodes or literals.
75
- # # `Array#===` will never match a node element, but
76
- # # `Set#===` is an alias to `Set#include?` (Ruby 2.5+
77
- # # only), and so can be very useful to match within
78
- # # many possible literals / Nodes.
79
- # # a bare '%' is the same as '%1'
80
- # # the number of extra parameters passed to #match
81
- # # must equal the highest % value in the pattern
82
- # # for consistency, %0 is the 'root node' which is
83
- # # passed as the 1st argument to #match, where the
84
- # # matching process starts
85
- # '(send _ %named)' # arguments can also be passed as named
86
- # # parameters (see `%1`)
87
- # # Note that the macros `def_node_matcher` and
88
- # # `def_node_search` accept default values for these.
89
- # '(send _ %CONST)' # the named constant will act like `%1` and `%named`.
90
- # '^^send' # each ^ ascends one level in the AST
91
- # # so this matches against the grandparent node
92
- # '`send' # descends any number of level in the AST
93
- # # so this matches against any descendant node
94
- # '#method' # we call this a 'funcall'; it calls a method in the
95
- # # context where a pattern-matching method is defined
96
- # # if that returns a truthy value, the match succeeds
97
- # 'equal?(%1)' # predicates can be given 1 or more extra args
98
- # '#method(%0, 1)' # funcalls can also be given 1 or more extra args
99
- # # These arguments can be patterns themselves, in
100
- # # which case a matcher responding to === will be
101
- # # passed.
102
- # '# comment' # comments are accepted at the end of lines
103
- #
104
- # You can nest arbitrarily deep:
105
- #
106
- # # matches node parsed from 'Const = Class.new' or 'Const = Module.new':
107
- # '(casgn nil? :Const (send (const nil? {:Class :Module}) :new))'
108
- # # matches a node parsed from an 'if', with a '==' comparison,
109
- # # and no 'else' branch:
110
- # '(if (send _ :== _) _ nil?)'
111
- #
112
- # Note that patterns like 'send' are implemented by calling `#send_type?` on
113
- # the node being matched, 'const' by `#const_type?`, 'int' by `#int_type?`,
114
- # and so on. Therefore, if you add methods which are named like
115
- # `#prefix_type?` to the AST node class, then 'prefix' will become usable as
116
- # a pattern.
117
26
  class NodePattern
118
- # @private
119
- Invalid = Class.new(StandardError)
120
-
121
- # @private
122
- # Builds Ruby code which implements a pattern
123
- class Compiler
124
- SYMBOL = %r{:(?:[\w+@*/?!<>=~|%^-]+|\[\]=?)}.freeze
125
- IDENTIFIER = /[a-zA-Z_][a-zA-Z0-9_-]*/.freeze
126
- COMMENT = /#\s.*$/.freeze
127
-
128
- META = Regexp.union(
129
- %w"( ) { } [ ] $< < > $... $ ! ^ ` ... + * ?"
130
- ).freeze
131
- NUMBER = /-?\d+(?:\.\d+)?/.freeze
132
- STRING = /".+?"/.freeze
133
- METHOD_NAME = /\#?#{IDENTIFIER}[!?]?\(?/.freeze
134
- PARAM_CONST = /%[A-Z:][a-zA-Z_:]+/.freeze
135
- KEYWORD_NAME = /%[a-z_]+/.freeze
136
- PARAM_NUMBER = /%\d*/.freeze
137
-
138
- SEPARATORS = /\s+/.freeze
139
- ONLY_SEPARATOR = /\A#{SEPARATORS}\Z/.freeze
140
-
141
- TOKENS = Regexp.union(META, PARAM_CONST, KEYWORD_NAME, PARAM_NUMBER, NUMBER,
142
- METHOD_NAME, SYMBOL, STRING)
143
-
144
- TOKEN = /\G(?:#{SEPARATORS}|#{TOKENS}|.)/.freeze
145
-
146
- NODE = /\A#{IDENTIFIER}\Z/.freeze
147
- PREDICATE = /\A#{IDENTIFIER}\?\(?\Z/.freeze
148
- WILDCARD = /\A_(?:#{IDENTIFIER})?\Z/.freeze
149
-
150
- FUNCALL = /\A\##{METHOD_NAME}/.freeze
151
- LITERAL = /\A(?:#{SYMBOL}|#{NUMBER}|#{STRING})\Z/.freeze
152
- PARAM = /\A#{PARAM_NUMBER}\Z/.freeze
153
- CONST = /\A#{PARAM_CONST}\Z/.freeze
154
- KEYWORD = /\A#{KEYWORD_NAME}\Z/.freeze
155
- CLOSING = /\A(?:\)|\}|\])\Z/.freeze
156
-
157
- REST = '...'
158
- CAPTURED_REST = '$...'
159
-
160
- attr_reader :match_code, :tokens, :captures
161
-
162
- SEQ_HEAD_INDEX = -1
163
-
164
- # Placeholders while compiling, see with_..._context methods
165
- CUR_PLACEHOLDER = '@@@cur'
166
- CUR_NODE = "#{CUR_PLACEHOLDER} node@@@"
167
- CUR_ELEMENT = "#{CUR_PLACEHOLDER} element@@@"
168
- SEQ_HEAD_GUARD = '@@@seq guard head@@@'
169
- MULTIPLE_CUR_PLACEHOLDER = /#{CUR_PLACEHOLDER}.*#{CUR_PLACEHOLDER}/.freeze
170
-
171
- line = __LINE__
172
- ANY_ORDER_TEMPLATE = ERB.new <<~RUBY.gsub("-%>\n", '%>')
173
- <% if capture_rest %>(<%= capture_rest %> = []) && <% end -%>
174
- <% if capture_all %>(<%= capture_all %> = <% end -%>
175
- <%= CUR_NODE %>.children[<%= range %>]<% if capture_all %>)<% end -%>
176
- .each_with_object({}) { |<%= child %>, <%= matched %>|
177
- case
178
- <% patterns.each_with_index do |pattern, i| -%>
179
- when !<%= matched %>[<%= i %>] && <%=
180
- with_context(pattern, child, use_temp_node: false)
181
- %> then <%= matched %>[<%= i %>] = true
182
- <% end -%>
183
- <% if !rest %> else break({})
184
- <% elsif capture_rest %> else <%= capture_rest %> << <%= child %>
185
- <% end -%>
186
- end
187
- }.size == <%= patterns.size -%>
188
- RUBY
189
- ANY_ORDER_TEMPLATE.location = [__FILE__, line + 1]
190
-
191
- line = __LINE__
192
- REPEATED_TEMPLATE = ERB.new <<~RUBY.gsub("-%>\n", '%>')
193
- <% if captured %>(<%= accumulate %> = Array.new) && <% end %>
194
- <%= CUR_NODE %>.children[<%= range %>].all? do |<%= child %>|
195
- <%= with_context(expr, child, use_temp_node: false) %><% if captured %>&&
196
- <%= accumulate %>.push(<%= captured %>)<% end %>
197
- end <% if captured %>&&
198
- (<%= captured %> = if <%= accumulate %>.empty?
199
- <%= captured %>.map{[]} # Transpose hack won't work for empty case
200
- else
201
- <%= accumulate %>.transpose
202
- end) <% end -%>
203
- RUBY
204
- REPEATED_TEMPLATE.location = [__FILE__, line + 1]
205
-
206
- def initialize(str, root = 'node0', node_var = root)
207
- @string = str
208
- # For def_node_pattern, root == node_var
209
- # For def_node_search, root is the root node to search on,
210
- # and node_var is the current descendant being searched.
211
- @root = root
212
- @node_var = node_var
213
-
214
- @temps = 0 # avoid name clashes between temp variables
215
- @captures = 0 # number of captures seen
216
- @unify = {} # named wildcard -> temp variable
217
- @params = 0 # highest % (param) number seen
218
- @keywords = Set[] # keyword parameters seen
219
- run
220
- end
221
-
222
- def run
223
- @tokens = Compiler.tokens(@string)
224
-
225
- @match_code = with_context(compile_expr, @node_var, use_temp_node: false)
226
- @match_code.prepend("(captures = Array.new(#{@captures})) && ") \
227
- if @captures.positive?
228
-
229
- fail_due_to('unbalanced pattern') unless tokens.empty?
230
- end
231
-
232
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
233
- def compile_expr(token = tokens.shift)
234
- # read a single pattern-matching expression from the token stream,
235
- # return Ruby code which performs the corresponding matching operation
236
- #
237
- # the 'pattern-matching' expression may be a composite which
238
- # contains an arbitrary number of sub-expressions, but that composite
239
- # must all have precedence higher or equal to that of `&&`
240
- #
241
- # Expressions may use placeholders like:
242
- # CUR_NODE: Ruby code that evaluates to an AST node
243
- # CUR_ELEMENT: Either the node or the type if in first element of
244
- # a sequence (aka seq_head, e.g. "(seq_head first_node_arg ...")
245
- if (atom = compile_atom(token))
246
- return atom_to_expr(atom)
247
- end
248
-
249
- case token
250
- when '(' then compile_seq
251
- when '{' then compile_union
252
- when '[' then compile_intersect
253
- when '!' then compile_negation
254
- when '$' then compile_capture
255
- when '^' then compile_ascend
256
- when '`' then compile_descend
257
- when WILDCARD then compile_new_wildcard(token[1..-1])
258
- when FUNCALL then compile_funcall(token)
259
- when PREDICATE then compile_predicate(token)
260
- when NODE then compile_nodetype(token)
261
- else fail_due_to("invalid token #{token.inspect}")
262
- end
263
- end
264
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
265
-
266
- def tokens_until(stop, what)
267
- return to_enum __method__, stop, what unless block_given?
268
-
269
- fail_due_to("empty #{what}") if tokens.first == stop
270
- yield until tokens.first == stop
271
- tokens.shift
272
- end
273
-
274
- def compile_seq
275
- terms = tokens_until(')', 'sequence').map { variadic_seq_term }
276
- Sequence.new(self, *terms).compile
277
- end
278
-
279
- def compile_guard_clause
280
- "#{CUR_NODE}.is_a?(RuboCop::AST::Node)"
281
- end
282
-
283
- def variadic_seq_term
284
- token = tokens.shift
285
- case token
286
- when CAPTURED_REST then compile_captured_ellipsis
287
- when REST then compile_ellipsis
288
- when '$<' then compile_any_order(next_capture)
289
- when '<' then compile_any_order
290
- else compile_repeated_expr(token)
291
- end
292
- end
293
-
294
- def compile_repeated_expr(token)
295
- before = @captures
296
- expr = compile_expr(token)
297
- min, max = parse_repetition_token
298
- return [1, expr] if min.nil?
299
-
300
- if @captures != before
301
- captured = "captures[#{before}...#{@captures}]"
302
- accumulate = next_temp_variable(:accumulate)
303
- end
304
- arity = min..max || Float::INFINITY
305
-
306
- [arity, repeated_generator(expr, captured, accumulate)]
307
- end
308
-
309
- def repeated_generator(expr, captured, accumulate)
310
- with_temp_variables do |child|
311
- lambda do |range|
312
- fail_due_to 'repeated pattern at beginning of sequence' if range.begin == SEQ_HEAD_INDEX
313
- REPEATED_TEMPLATE.result(binding)
314
- end
315
- end
316
- end
317
-
318
- def parse_repetition_token
319
- case tokens.first
320
- when '*' then min = 0
321
- when '+' then min = 1
322
- when '?' then min = 0
323
- max = 1
324
- else return
325
- end
326
- tokens.shift
327
- [min, max]
328
- end
329
-
330
- # @private
331
- # Builds Ruby code for a sequence
332
- # (head *first_terms variadic_term *last_terms)
333
- class Sequence
334
- extend Forwardable
335
- def_delegators :@compiler, :compile_guard_clause, :with_seq_head_context,
336
- :with_child_context, :fail_due_to
337
-
338
- def initialize(compiler, *arity_term_list)
339
- @arities, @terms = arity_term_list.transpose
340
-
341
- @compiler = compiler
342
- @variadic_index = @arities.find_index { |a| a.is_a?(Range) }
343
- fail_due_to 'multiple variable patterns in same sequence' \
344
- if @variadic_index && !@arities.one? { |a| a.is_a?(Range) }
345
- end
346
-
347
- def compile
348
- [
349
- compile_guard_clause,
350
- compile_child_nb_guard,
351
- compile_seq_head,
352
- *compile_first_terms,
353
- compile_variadic_term,
354
- *compile_last_terms
355
- ].compact.join(" &&\n") << SEQ_HEAD_GUARD
356
- end
357
-
358
- private
359
-
360
- def first_terms_arity
361
- first_terms_range { |r| @arities[r].inject(0, :+) } || 0
362
- end
363
-
364
- def last_terms_arity
365
- last_terms_range { |r| @arities[r].inject(0, :+) } || 0
366
- end
367
-
368
- def variadic_term_min_arity
369
- @variadic_index ? @arities[@variadic_index].begin : 0
370
- end
371
-
372
- def first_terms_range
373
- yield 1..(@variadic_index || @terms.size) - 1 if seq_head?
374
- end
375
-
376
- def last_terms_range
377
- yield @variadic_index + 1...@terms.size if @variadic_index
378
- end
379
-
380
- def seq_head?
381
- @variadic_index != 0
382
- end
383
-
384
- def compile_child_nb_guard
385
- fixed = first_terms_arity + last_terms_arity
386
- min = fixed + variadic_term_min_arity
387
- op = if @variadic_index
388
- max_variadic = @arities[@variadic_index].end
389
- if max_variadic != Float::INFINITY
390
- range = min..fixed + max_variadic
391
- return "(#{range}).cover?(#{CUR_NODE}.children.size)"
392
- end
393
- '>='
394
- else
395
- '=='
396
- end
397
- "#{CUR_NODE}.children.size #{op} #{min}"
398
- end
399
-
400
- def term(index, range)
401
- t = @terms[index]
402
- if t.respond_to? :call
403
- t.call(range)
404
- else
405
- with_child_context(t, range.begin)
406
- end
407
- end
408
-
409
- def compile_seq_head
410
- return unless seq_head?
411
-
412
- fail_due_to 'sequences cannot start with <' \
413
- if @terms[0].respond_to? :call
414
-
415
- with_seq_head_context(@terms[0])
416
- end
417
-
418
- def compile_first_terms
419
- first_terms_range { |range| compile_terms(range, 0) }
420
- end
421
-
422
- def compile_last_terms
423
- last_terms_range { |r| compile_terms(r, -last_terms_arity) }
424
- end
425
-
426
- def compile_terms(index_range, start)
427
- index_range.map do |i|
428
- current = start
429
- start += @arities.fetch(i)
430
- term(i, current..start - 1)
431
- end
432
- end
433
-
434
- def compile_variadic_term
435
- variadic_arity { |arity| term(@variadic_index, arity) }
436
- end
437
-
438
- def variadic_arity
439
- return unless @variadic_index
440
-
441
- first = @variadic_index.positive? ? first_terms_arity : SEQ_HEAD_INDEX
442
- yield first..-last_terms_arity - 1
443
- end
444
- end
445
- private_constant :Sequence
446
-
447
- def compile_captured_ellipsis
448
- capture = next_capture
449
- block = lambda { |range|
450
- # Consider ($...) like (_ $...):
451
- range = 0..range.end if range.begin == SEQ_HEAD_INDEX
452
- "(#{capture} = #{CUR_NODE}.children[#{range}])"
453
- }
454
- [0..Float::INFINITY, block]
455
- end
456
-
457
- def compile_ellipsis
458
- [0..Float::INFINITY, 'true']
459
- end
460
-
461
- # rubocop:disable Metrics/MethodLength
462
- def compile_any_order(capture_all = nil)
463
- rest = capture_rest = nil
464
- patterns = []
465
- with_temp_variables do |child, matched|
466
- tokens_until('>', 'any child') do
467
- fail_due_to 'ellipsis must be at the end of <>' if rest
468
- token = tokens.shift
469
- case token
470
- when CAPTURED_REST then rest = capture_rest = next_capture
471
- when REST then rest = true
472
- else patterns << compile_expr(token)
473
- end
474
- end
475
- [rest ? patterns.size..Float::INFINITY : patterns.size,
476
- ->(range) { ANY_ORDER_TEMPLATE.result(binding) }]
477
- end
478
- end
479
- # rubocop:enable Metrics/MethodLength
480
-
481
- def insure_same_captures(enum, what)
482
- return to_enum __method__, enum, what unless block_given?
483
-
484
- captures_before = captures_after = nil
485
- enum.each do
486
- captures_before ||= @captures
487
- @captures = captures_before
488
- yield
489
- captures_after ||= @captures
490
- fail_due_to("each #{what} must have same # of captures") if captures_after != @captures
491
- end
492
- end
493
-
494
- def access_unify(name)
495
- var = @unify[name]
496
-
497
- if var == :forbidden_unification
498
- fail_due_to "Wildcard #{name} was first seen in a subset of a" \
499
- " union and can't be used outside that union"
500
- end
501
- var
502
- end
503
-
504
- def forbid_unification(*names)
505
- names.each do |name|
506
- @unify[name] = :forbidden_unification
507
- end
508
- end
509
-
510
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
511
- def unify_in_union(enum)
512
- # We need to reset @unify before each branch is processed.
513
- # Moreover we need to keep track of newly encountered wildcards.
514
- # Var `new_unify_intersection` will hold those that are encountered
515
- # in all branches; these are not a problem.
516
- # Var `partial_unify` will hold those encountered in only a subset
517
- # of the branches; these can't be used outside of the union.
518
-
519
- return to_enum __method__, enum unless block_given?
520
-
521
- new_unify_intersection = nil
522
- partial_unify = []
523
- unify_before = @unify.dup
524
-
525
- result = enum.each do |e|
526
- @unify = unify_before.dup if new_unify_intersection
527
- yield e
528
- new_unify = @unify.keys - unify_before.keys
529
- if new_unify_intersection.nil?
530
- # First iteration
531
- new_unify_intersection = new_unify
532
- else
533
- union = new_unify_intersection | new_unify
534
- new_unify_intersection &= new_unify
535
- partial_unify |= union - new_unify_intersection
536
- end
537
- end
538
-
539
- # At this point, all members of `new_unify_intersection` can be used
540
- # for unification outside of the union, but partial_unify may not
541
-
542
- forbid_unification(*partial_unify)
543
-
544
- result
545
- end
546
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
547
-
548
- def compile_union
549
- # we need to ensure that each branch of the {} contains the same
550
- # number of captures (since only one branch of the {} can actually
551
- # match, the same variables are used to hold the captures for each
552
- # branch)
553
- enum = tokens_until('}', 'union')
554
- enum = unify_in_union(enum)
555
- terms = insure_same_captures(enum, 'branch of {}')
556
- .map { compile_expr }
557
-
558
- "(#{terms.join(' || ')})"
559
- end
560
-
561
- def compile_intersect
562
- tokens_until(']', 'intersection')
563
- .map { compile_expr }
564
- .join(' && ')
565
- end
566
-
567
- def compile_capture
568
- "(#{next_capture} = #{CUR_ELEMENT}; #{compile_expr})"
569
- end
570
-
571
- def compile_negation
572
- "!(#{compile_expr})"
573
- end
574
-
575
- def compile_ascend
576
- with_context("#{CUR_NODE} && #{compile_expr}", "#{CUR_NODE}.parent")
577
- end
578
-
579
- def compile_descend
580
- with_temp_variables do |descendant|
581
- pattern = with_context(compile_expr, descendant,
582
- use_temp_node: false)
583
- [
584
- "RuboCop::AST::NodePattern.descend(#{CUR_ELEMENT}).",
585
- "any? do |#{descendant}|",
586
- " #{pattern}",
587
- 'end'
588
- ].join("\n")
589
- end
590
- end
591
-
592
- # Known wildcards are considered atoms, see `compile_atom`
593
- def compile_new_wildcard(name)
594
- return 'true' if name.empty?
595
-
596
- n = @unify[name] = "unify_#{name.gsub('-', '__')}"
597
- # double assign to avoid "assigned but unused variable"
598
- "(#{n} = #{CUR_ELEMENT}; #{n} = #{n}; true)"
599
- end
600
-
601
- def compile_predicate(predicate)
602
- if predicate.end_with?('(') # is there an arglist?
603
- args = compile_args
604
- predicate = predicate[0..-2] # drop the trailing (
605
- "#{CUR_ELEMENT}.#{predicate}(#{args.join(',')})"
606
- else
607
- "#{CUR_ELEMENT}.#{predicate}"
608
- end
609
- end
610
-
611
- def compile_funcall(method)
612
- # call a method in the context which this pattern-matching
613
- # code is used in. pass target value as an argument
614
- method = method[1..-1] # drop the leading #
615
- if method.end_with?('(') # is there an arglist?
616
- args = compile_args
617
- method = method[0..-2] # drop the trailing (
618
- "#{method}(#{CUR_ELEMENT},#{args.join(',')})"
619
- else
620
- "#{method}(#{CUR_ELEMENT})"
621
- end
622
- end
623
-
624
- def compile_nodetype(type)
625
- "#{compile_guard_clause} && #{CUR_NODE}.#{type.tr('-', '_')}_type?"
626
- end
627
-
628
- def compile_args
629
- tokens_until(')', 'call arguments').map do
630
- arg = compile_arg
631
- tokens.shift if tokens.first == ','
632
- arg
633
- end
634
- end
635
-
636
- def atom_to_expr(atom)
637
- "#{atom} === #{CUR_ELEMENT}"
638
- end
639
-
640
- def expr_to_atom(expr)
641
- with_temp_variables do |compare|
642
- in_context = with_context(expr, compare, use_temp_node: false)
643
- "::RuboCop::AST::NodePattern::Matcher.new{|#{compare}| #{in_context}}"
644
- end
645
- end
646
-
647
- # @return compiled atom (e.g. ":literal" or "SOME_CONST")
648
- # or nil if not a simple atom (unknown wildcard, other tokens)
649
- def compile_atom(token)
650
- case token
651
- when WILDCARD then access_unify(token[1..-1]) # could be nil
652
- when LITERAL then token
653
- when KEYWORD then get_keyword(token[1..-1])
654
- when CONST then get_const(token[1..-1])
655
- when PARAM then get_param(token[1..-1])
656
- when CLOSING then fail_due_to("#{token} in invalid position")
657
- when nil then fail_due_to('pattern ended prematurely')
658
- end
659
- end
660
-
661
- def compile_arg
662
- token = tokens.shift
663
- compile_atom(token) || expr_to_atom(compile_expr(token))
664
- end
665
-
666
- def next_capture
667
- index = @captures
668
- @captures += 1
669
- "captures[#{index}]"
670
- end
671
-
672
- def get_param(number)
673
- number = number.empty? ? 1 : Integer(number)
674
- @params = number if number > @params
675
- number.zero? ? @root : "param#{number}"
676
- end
677
-
678
- def get_keyword(name)
679
- @keywords << name
680
- name
681
- end
682
-
683
- def get_const(const)
684
- const # Output the constant exactly as given
685
- end
686
-
687
- def emit_yield_capture(when_no_capture = '')
688
- yield_val = if @captures.zero?
689
- when_no_capture
690
- elsif @captures == 1
691
- 'captures[0]' # Circumvent https://github.com/jruby/jruby/issues/5710
692
- else
693
- '*captures'
694
- end
695
- "yield(#{yield_val})"
696
- end
697
-
698
- def emit_retval
699
- if @captures.zero?
700
- 'true'
701
- elsif @captures == 1
702
- 'captures[0]'
703
- else
704
- 'captures'
705
- end
706
- end
707
-
708
- def emit_param_list
709
- (1..@params).map { |n| "param#{n}" }.join(',')
710
- end
711
-
712
- def emit_keyword_list(forwarding: false)
713
- pattern = "%<keyword>s: #{'%<keyword>s' if forwarding}"
714
- @keywords.map { |k| format(pattern, keyword: k) }.join(',')
715
- end
716
-
717
- def emit_params(*first, forwarding: false)
718
- params = emit_param_list
719
- keywords = emit_keyword_list(forwarding: forwarding)
720
- [*first, params, keywords].reject(&:empty?).join(',')
721
- end
722
-
723
- def emit_method_code
724
- <<~RUBY
725
- return unless #{@match_code}
726
- block_given? ? #{emit_yield_capture} : (return #{emit_retval})
727
- RUBY
728
- end
729
-
730
- def fail_due_to(message)
731
- raise Invalid, "Couldn't compile due to #{message}. Pattern: #{@string}"
732
- end
733
-
734
- def with_temp_node(cur_node)
735
- with_temp_variables do |node|
736
- yield "(#{node} = #{cur_node})", node
737
- end
738
- .gsub("\n", "\n ") # Nicer indent for debugging
739
- end
740
-
741
- def with_temp_variables(&block)
742
- names = block.parameters.map { |_, name| next_temp_variable(name) }
743
- yield(*names)
744
- end
745
-
746
- def next_temp_variable(name)
747
- "#{name}#{next_temp_value}"
748
- end
749
-
750
- def next_temp_value
751
- @temps += 1
752
- end
753
-
754
- def auto_use_temp_node?(code)
755
- code.match?(MULTIPLE_CUR_PLACEHOLDER)
756
- end
757
-
758
- # with_<...>_context methods are used whenever the context,
759
- # i.e the current node or the current element can be determined.
760
-
761
- def with_child_context(code, child_index)
762
- with_context(code, "#{CUR_NODE}.children[#{child_index}]")
763
- end
764
-
765
- def with_context(code, cur_node,
766
- use_temp_node: auto_use_temp_node?(code))
767
- if use_temp_node
768
- with_temp_node(cur_node) do |init, temp_var|
769
- substitute_cur_node(code, temp_var, first_cur_node: init)
770
- end
771
- else
772
- substitute_cur_node(code, cur_node)
773
- end
774
- end
775
-
776
- def with_seq_head_context(code)
777
- fail_due_to('parentheses at sequence head') if code.include?(SEQ_HEAD_GUARD)
778
-
779
- code.gsub CUR_ELEMENT, "#{CUR_NODE}.type"
780
- end
781
-
782
- def substitute_cur_node(code, cur_node, first_cur_node: cur_node)
783
- iter = 0
784
- code
785
- .gsub(CUR_ELEMENT, CUR_NODE)
786
- .gsub(CUR_NODE) do
787
- iter += 1
788
- iter == 1 ? first_cur_node : cur_node
789
- end
790
- .gsub(SEQ_HEAD_GUARD, '')
791
- end
792
-
793
- def self.tokens(pattern)
794
- pattern.gsub(COMMENT, '').scan(TOKEN).grep_v(ONLY_SEPARATOR)
795
- end
796
-
797
- # This method minimizes the closure for our method
798
- def wrapping_block(method_name, **defaults)
799
- proc do |*args, **values|
800
- send method_name, *args, **defaults, **values
801
- end
802
- end
803
-
804
- def def_helper(base, method_name, **defaults)
805
- location = caller_locations(3, 1).first
806
- unless defaults.empty?
807
- call = :"without_defaults_#{method_name}"
808
- base.send :define_method, method_name, &wrapping_block(call, **defaults)
809
- method_name = call
810
- end
811
- src = yield method_name
812
- base.class_eval(src, location.path, location.lineno)
813
- end
814
-
815
- def def_node_matcher(base, method_name, **defaults)
816
- def_helper(base, method_name, **defaults) do |name|
817
- <<~RUBY
818
- def #{name}(#{emit_params('node = self')})
819
- #{emit_method_code}
820
- end
821
- RUBY
822
- end
823
- end
824
-
825
- def def_node_search(base, method_name, **defaults)
826
- def_helper(base, method_name, **defaults) do |name|
827
- emit_node_search(name)
828
- end
829
- end
830
-
831
- def emit_node_search(method_name)
832
- if method_name.to_s.end_with?('?')
833
- on_match = 'return true'
834
- else
835
- args = emit_params(":#{method_name}", @root, forwarding: true)
836
- prelude = "return enum_for(#{args}) unless block_given?\n"
837
- on_match = emit_yield_capture(@node_var)
838
- end
839
- emit_node_search_body(method_name, prelude: prelude, on_match: on_match)
840
- end
841
-
842
- def emit_node_search_body(method_name, prelude:, on_match:)
843
- <<~RUBY
844
- def #{method_name}(#{emit_params(@root)})
845
- #{prelude}
846
- #{@root}.each_node do |#{@node_var}|
847
- if #{match_code}
848
- #{on_match}
849
- end
850
- end
851
- nil
852
- end
853
- RUBY
854
- end
855
- end
856
- private_constant :Compiler
857
-
858
27
  # Helpers for defining methods based on a pattern string
859
28
  module Macros
860
29
  # Define a method which applies a pattern to an AST node
@@ -865,8 +34,7 @@ module RuboCop
865
34
  # If the node matches, and no block is provided, the new method will
866
35
  # return the captures, or `true` if there were none.
867
36
  def def_node_matcher(method_name, pattern_str, **keyword_defaults)
868
- Compiler.new(pattern_str, 'node')
869
- .def_node_matcher(self, method_name, **keyword_defaults)
37
+ NodePattern.new(pattern_str).def_node_matcher(self, method_name, **keyword_defaults)
870
38
  end
871
39
 
872
40
  # Define a method which recurses over the descendants of an AST node,
@@ -876,48 +44,60 @@ module RuboCop
876
44
  # as soon as it finds a descendant which matches. Otherwise, it will
877
45
  # yield all descendants which match.
878
46
  def def_node_search(method_name, pattern_str, **keyword_defaults)
879
- Compiler.new(pattern_str, 'node0', 'node')
880
- .def_node_search(self, method_name, **keyword_defaults)
47
+ NodePattern.new(pattern_str).def_node_search(self, method_name, **keyword_defaults)
881
48
  end
882
49
  end
883
50
 
884
- attr_reader :pattern
51
+ extend Forwardable
52
+ include MethodDefiner
53
+ Invalid = Class.new(StandardError)
885
54
 
886
- def initialize(str)
55
+ VAR = 'node'
56
+
57
+ attr_reader :pattern, :ast, :match_code
58
+
59
+ def_delegators :@compiler, :captures, :named_parameters, :positional_parameters
60
+
61
+ def initialize(str, compiler: Compiler.new)
887
62
  @pattern = str
888
- compiler = Compiler.new(str, 'node0')
889
- src = "def match(#{compiler.emit_params('node0')});" \
890
- "#{compiler.emit_method_code}end"
891
- instance_eval(src, __FILE__, __LINE__ + 1)
63
+ @ast = compiler.parser.parse(str)
64
+ @compiler = compiler
65
+ @match_code = @compiler.compile_as_node_pattern(@ast, var: VAR)
66
+ @cache = {}
892
67
  end
893
68
 
894
- def match(*args, **rest)
895
- # If we're here, it's because the singleton method has not been defined,
896
- # either because we've been dup'ed or serialized through YAML
897
- initialize(pattern)
898
- if rest.empty?
899
- match(*args)
900
- else
901
- match(*args, **rest)
902
- end
69
+ def match(*args, **rest, &block)
70
+ @cache[:lambda] ||= as_lambda
71
+ @cache[:lambda].call(*args, block: block, **rest)
72
+ end
73
+
74
+ def ==(other)
75
+ other.is_a?(NodePattern) && other.ast == ast
76
+ end
77
+ alias eql? ==
78
+
79
+ def to_s
80
+ "#<#{self.class} #{pattern}>"
903
81
  end
904
82
 
905
- def marshal_load(pattern)
83
+ def marshal_load(pattern) #:nodoc:
906
84
  initialize pattern
907
85
  end
908
86
 
909
- def marshal_dump
87
+ def marshal_dump #:nodoc:
910
88
  pattern
911
89
  end
912
90
 
913
- def ==(other)
914
- other.is_a?(NodePattern) &&
915
- Compiler.tokens(other.pattern) == Compiler.tokens(pattern)
91
+ def as_json(_options = nil) #:nodoc:
92
+ pattern
916
93
  end
917
- alias eql? ==
918
94
 
919
- def to_s
920
- "#<#{self.class} #{pattern}>"
95
+ def encode_with(coder) #:nodoc:
96
+ coder['pattern'] = pattern
97
+ end
98
+
99
+ def init_with(coder) #:nodoc:
100
+ initialize(coder['pattern'])
921
101
  end
922
102
 
923
103
  # Yields its argument and any descendants, depth-first.
@@ -936,17 +116,11 @@ module RuboCop
936
116
  nil
937
117
  end
938
118
 
939
- # @api private
940
- class Matcher
941
- def initialize(&block)
942
- @block = block
943
- end
944
-
945
- def ===(compare)
946
- @block.call(compare)
947
- end
119
+ def freeze
120
+ @match_code.freeze
121
+ @compiler.freeze
122
+ super
948
123
  end
949
124
  end
950
125
  end
951
126
  end
952
- # rubocop:enable Metrics/ClassLength, Metrics/CyclomaticComplexity