1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
# frozen_string_literal: true
module RuboCop
module AST
# Provides methods for traversing an AST.
# Does not transform an AST; for that, use Parser::AST::Processor.
# Override methods to perform custom processing. Remember to call `super`
# if you want to recursively process descendant nodes.
module Traversal
# Only for debugging.
# @api private
class DebugError < RuntimeError
end
TYPE_TO_METHOD = Hash.new { |h, type| h[type] = :"on_#{type}" }
def walk(node)
return if node.nil?
send(TYPE_TO_METHOD[node.type], node)
nil
end
# @api private
module CallbackCompiler
SEND = 'send(TYPE_TO_METHOD[child.type], child)'
assign_code = 'child = node.children[%<index>i]'
code = "#{assign_code}\n#{SEND}"
TEMPLATE = {
skip: '',
always: code,
nil?: "#{code} if child"
}.freeze
def def_callback(type, *signature,
arity: signature.size..signature.size,
arity_check: ENV.fetch('RUBOCOP_DEBUG', nil) && self.arity_check(arity),
body: self.body(signature, arity_check))
type, *aliases = type
lineno = caller_locations(1, 1).first.lineno
module_eval(<<~RUBY, __FILE__, lineno) # rubocop:disable Style/EvalWithLocation
def on_#{type}(node) # def on_send(node)
#{body} # # body ...
nil # nil
end # end
RUBY
aliases.each do |m|
alias_method "on_#{m}", "on_#{type}"
end
end
def body(signature, prelude)
signature
.map.with_index do |arg, i|
TEMPLATE[arg].gsub('%<index>i', i.to_s)
end
.unshift(prelude)
.join("\n")
end
def arity_check(range)
<<~RUBY
n = node.children.size
raise DebugError, [
'Expected #{range} children, got',
n, 'for', node.inspect
].join(' ') unless (#{range}).cover?(node.children.size)
RUBY
end
end
private_constant :CallbackCompiler
extend CallbackCompiler
send_code = CallbackCompiler::SEND
### arity == 0
no_children = %i[true false nil self cbase zsuper redo retry
forward_args forwarded_args match_nil_pattern
forward_arg forwarded_restarg forwarded_kwrestarg
lambda empty_else kwnilarg
__FILE__ __LINE__ __ENCODING__]
### arity == 0..1
opt_symbol_child = %i[restarg kwrestarg]
opt_node_child = %i[splat kwsplat match_rest]
### arity == 1
literal_child = %i[int float complex
rational str sym lvar
ivar cvar gvar nth_ref back_ref
arg blockarg shadowarg
kwarg match_var]
many_symbol_children = %i[regopt]
node_child = %i[not match_current_line defined?
arg_expr pin if_guard unless_guard
match_with_trailing_comma]
node_or_nil_child = %i[block_pass preexe postexe]
NO_CHILD_NODES = (no_children + opt_symbol_child + literal_child).to_set.freeze
private_constant :NO_CHILD_NODES # Used by Commissioner
### arity > 1
symbol_then_opt_node = %i[lvasgn ivasgn cvasgn gvasgn]
symbol_then_node_or_nil = %i[optarg kwoptarg]
node_then_opt_node = %i[while until module sclass]
### variable arity
many_node_children = %i[dstr dsym xstr regexp array hash pair
mlhs masgn or_asgn and_asgn rasgn mrasgn
undef alias args super yield or and
while_post until_post iflipflop eflipflop
match_with_lvasgn begin kwbegin return
in_match match_alt break next
match_as array_pattern array_pattern_with_tail
hash_pattern const_pattern find_pattern
index indexasgn procarg0 kwargs]
many_opt_node_children = %i[case rescue resbody ensure for when
case_match in_pattern irange erange
match_pattern match_pattern_p]
### Callbacks for above
def_callback no_children
def_callback opt_symbol_child, :skip, arity: 0..1
def_callback opt_node_child, :nil?, arity: 0..1
def_callback literal_child, :skip
def_callback node_child, :always
def_callback node_or_nil_child, :nil?
def_callback symbol_then_opt_node, :skip, :nil?, arity: 1..2
def_callback symbol_then_node_or_nil, :skip, :nil?
def_callback node_then_opt_node, :always, :nil?
def_callback many_symbol_children, :skip, arity_check: nil
def_callback many_node_children, body: <<~RUBY
node.children.each { |child| #{send_code} }
RUBY
def_callback many_opt_node_children,
body: <<~RUBY
node.children.each { |child| #{send_code} if child }
RUBY
### Other particular cases
def_callback :const, :nil?, :skip
def_callback :casgn, :nil?, :skip, :nil?, arity: 2..3
def_callback :class, :always, :nil?, :nil?
def_callback :def, :skip, :always, :nil?
def_callback :op_asgn, :always, :skip, :always
def_callback :if, :always, :nil?, :nil?
def_callback :block, :always, :always, :nil?
def_callback :numblock, :always, :skip, :nil?
def_callback :defs, :always, :skip, :always, :nil?
def_callback %i[send csend], body: <<~RUBY
node.children.each_with_index do |child, i|
next if i == 1
#{send_code} if child
end
RUBY
### generic processing of any other node (forward compatibility)
defined = instance_methods(false)
.grep(/^on_/)
.map { |s| s.to_s[3..].to_sym } # :on_foo => :foo
to_define = ::Parser::Meta::NODE_TYPES.to_a
to_define -= defined
to_define -= %i[numargs ident] # transient
to_define -= %i[blockarg_expr restarg_expr] # obsolete
to_define -= %i[objc_kwarg objc_restarg objc_varargs] # mac_ruby
def_callback to_define, body: <<~RUBY
node.children.each do |child|
next unless child.class == Node
#{send_code}
end
RUBY
MISSING = to_define if ENV['RUBOCOP_DEBUG']
end
end
end
|