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
|
# frozen_string_literal: true
module RuboCop
module AST
class NodePattern
# Functionality to turn `match_code` into methods/lambda
module MethodDefiner
def def_node_matcher(base, method_name, **defaults)
def_helper(base, method_name, **defaults) do |name|
params = emit_params('param0 = self')
<<~RUBY
def #{name}(#{params})
#{VAR} = param0
#{compile_init}
#{emit_method_code}
end
RUBY
end
end
def def_node_search(base, method_name, **defaults)
def_helper(base, method_name, **defaults) do |name|
emit_node_search(name)
end
end
def compile_as_lambda
<<~RUBY
->(#{emit_params('param0')}, block: nil) do
#{VAR} = param0
#{compile_init}
#{emit_lambda_code}
end
RUBY
end
def as_lambda
eval(compile_as_lambda) # rubocop:disable Security/Eval
end
private
# This method minimizes the closure for our method
def wrapping_block(method_name, **defaults)
proc do |*args, **values|
send method_name, *args, **defaults, **values
end
end
def def_helper(base, method_name, **defaults)
location = caller_locations(3, 1).first
unless defaults.empty?
call = :"without_defaults_#{method_name}"
base.send :define_method, method_name, &wrapping_block(call, **defaults)
method_name = call
end
src = yield method_name
base.class_eval(src, location.path, location.lineno)
method_name
end
def emit_node_search(method_name)
if method_name.to_s.end_with?('?')
on_match = 'return true'
else
args = emit_params(":#{method_name}", 'param0', forwarding: true)
prelude = "return enum_for(#{args}) unless block_given?\n"
on_match = emit_yield_capture(VAR)
end
emit_node_search_body(method_name, prelude: prelude, on_match: on_match)
end
def emit_node_search_body(method_name, prelude:, on_match:)
<<~RUBY
def #{method_name}(#{emit_params('param0')})
#{compile_init}
#{prelude}
param0.each_node do |#{VAR}|
if #{match_code}
#{on_match}
end
end
nil
end
RUBY
end
def emit_yield_capture(when_no_capture = '', yield_with: 'yield')
yield_val = if captures.zero?
when_no_capture
elsif captures == 1
'captures[0]' # Circumvent https://github.com/jruby/jruby/issues/5710
else
'*captures'
end
"#{yield_with}(#{yield_val})"
end
def emit_retval
if captures.zero?
'true'
elsif captures == 1
'captures[0]'
else
'captures'
end
end
def emit_param_list
(1..positional_parameters).map { |n| "param#{n}" }.join(',')
end
def emit_keyword_list(forwarding: false)
pattern = "%<keyword>s: #{'%<keyword>s' if forwarding}"
named_parameters.map { |k| format(pattern, keyword: k) }.join(',')
end
def emit_params(*first, forwarding: false)
params = emit_param_list
keywords = emit_keyword_list(forwarding: forwarding)
[*first, params, keywords].reject(&:empty?).join(',')
end
def emit_method_code
<<~RUBY
return unless #{match_code}
block_given? ? #{emit_yield_capture} : (return #{emit_retval})
RUBY
end
def emit_lambda_code
<<~RUBY
return unless #{match_code}
block ? #{emit_yield_capture(yield_with: 'block.call')} : (return #{emit_retval})
RUBY
end
def compile_init
"captures = Array.new(#{captures})" if captures.positive?
end
end
end
end
end
|