rubocop-ast 0.5.1 → 1.0.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rubocop/ast.rb +17 -0
  3. data/lib/rubocop/ast/builder.rb +1 -0
  4. data/lib/rubocop/ast/node.rb +44 -125
  5. data/lib/rubocop/ast/node/array_node.rb +1 -0
  6. data/lib/rubocop/ast/node/block_node.rb +1 -0
  7. data/lib/rubocop/ast/node/def_node.rb +5 -0
  8. data/lib/rubocop/ast/node/keyword_splat_node.rb +1 -0
  9. data/lib/rubocop/ast/node/mixin/collection_node.rb +1 -0
  10. data/lib/rubocop/ast/node/mixin/descendence.rb +116 -0
  11. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +2 -0
  12. data/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +9 -0
  13. data/lib/rubocop/ast/node/mixin/numeric_node.rb +1 -0
  14. data/lib/rubocop/ast/node/mixin/predicate_operator_node.rb +7 -3
  15. data/lib/rubocop/ast/node/pair_node.rb +4 -0
  16. data/lib/rubocop/ast/node/regexp_node.rb +9 -4
  17. data/lib/rubocop/ast/node_pattern.rb +44 -870
  18. data/lib/rubocop/ast/node_pattern/builder.rb +72 -0
  19. data/lib/rubocop/ast/node_pattern/comment.rb +45 -0
  20. data/lib/rubocop/ast/node_pattern/compiler.rb +104 -0
  21. data/lib/rubocop/ast/node_pattern/compiler/atom_subcompiler.rb +56 -0
  22. data/lib/rubocop/ast/node_pattern/compiler/binding.rb +78 -0
  23. data/lib/rubocop/ast/node_pattern/compiler/debug.rb +168 -0
  24. data/lib/rubocop/ast/node_pattern/compiler/node_pattern_subcompiler.rb +146 -0
  25. data/lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb +420 -0
  26. data/lib/rubocop/ast/node_pattern/compiler/subcompiler.rb +57 -0
  27. data/lib/rubocop/ast/node_pattern/lexer.rb +70 -0
  28. data/lib/rubocop/ast/node_pattern/lexer.rex +39 -0
  29. data/lib/rubocop/ast/node_pattern/lexer.rex.rb +182 -0
  30. data/lib/rubocop/ast/node_pattern/method_definer.rb +143 -0
  31. data/lib/rubocop/ast/node_pattern/node.rb +275 -0
  32. data/lib/rubocop/ast/node_pattern/parser.racc.rb +470 -0
  33. data/lib/rubocop/ast/node_pattern/parser.rb +66 -0
  34. data/lib/rubocop/ast/node_pattern/parser.y +103 -0
  35. data/lib/rubocop/ast/node_pattern/sets.rb +37 -0
  36. data/lib/rubocop/ast/node_pattern/with_meta.rb +111 -0
  37. data/lib/rubocop/ast/processed_source.rb +5 -1
  38. data/lib/rubocop/ast/traversal.rb +149 -172
  39. data/lib/rubocop/ast/version.rb +1 -1
  40. metadata +37 -3
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parser.racc'
4
+
5
+ module RuboCop
6
+ module AST
7
+ class NodePattern
8
+ # Parser for NodePattern
9
+ # Note: class reopened in `parser.racc`
10
+ #
11
+ # Doc on how this fits in the compiling process:
12
+ # /doc/modules/ROOT/pages/node_pattern.md
13
+ class Parser < Racc::Parser
14
+ extend Forwardable
15
+
16
+ Builder = NodePattern::Builder
17
+ Lexer = NodePattern::Lexer
18
+
19
+ def initialize(builder = self.class::Builder.new)
20
+ super()
21
+ @builder = builder
22
+ end
23
+
24
+ ##
25
+ # (Similar API to `parser` gem)
26
+ # Parses a source and returns the AST.
27
+ #
28
+ # @param [Parser::Source::Buffer, String] source_buffer The source buffer to parse.
29
+ # @return [NodePattern::Node]
30
+ #
31
+ def parse(source)
32
+ @lexer = self.class::Lexer.new(source)
33
+ do_parse
34
+ rescue Lexer::Error => e
35
+ raise NodePattern::Invalid, e.message
36
+ ensure
37
+ @lexer = nil # Don't keep references
38
+ end
39
+
40
+ def inspect
41
+ "<##{self.class}>"
42
+ end
43
+
44
+ private
45
+
46
+ def_delegators :@builder, :emit_list, :emit_unary_op, :emit_atom, :emit_capture,
47
+ :emit_call, :emit_union
48
+ def_delegators :@lexer, :next_token
49
+
50
+ def enforce_unary(node)
51
+ return node if node.arity == 1
52
+
53
+ detail = node.loc&.expression&.source || node.to_s
54
+ raise NodePattern::Invalid, 'parse error, expected unary node pattern ' \
55
+ "but got expression matching multiple elements: #{detail}"
56
+ end
57
+
58
+ # Overrides Racc::Parser's method:
59
+ def on_error(token, val, _vstack)
60
+ detail = token_to_str(token) || '?'
61
+ raise NodePattern::Invalid, "parse error on value #{val.inspect} (#{detail})"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,103 @@
1
+ class RuboCop::AST::NodePattern::Parser
2
+ options no_result_var
3
+ token tSYMBOL tNUMBER tSTRING tWILDCARD tPARAM_NAMED tPARAM_CONST tPARAM_NUMBER
4
+ tFUNCTION_CALL tPREDICATE tNODE_TYPE tARG_LIST tUNIFY tREGEXP
5
+ rule
6
+ node_pattern # @return Node
7
+ : node_pattern_no_union
8
+ | union { enforce_unary(val[0]) }
9
+ ;
10
+
11
+ node_pattern_no_union # @return Node
12
+ : '(' variadic_pattern_list ')' { emit_list :sequence, *val }
13
+ | '[' node_pattern_list ']' { emit_list :intersection, *val }
14
+ | '!' node_pattern { emit_unary_op :negation, *val }
15
+ | '^' node_pattern { emit_unary_op :ascend, *val }
16
+ | '`' node_pattern { emit_unary_op :descend, *val }
17
+ | '$' node_pattern { emit_capture(*val) }
18
+ | tFUNCTION_CALL args { emit_call :function_call, *val }
19
+ | tPREDICATE args { emit_call :predicate, *val }
20
+ | tNODE_TYPE { emit_call :node_type, *val }
21
+ | atom
22
+ ;
23
+
24
+ atom # @return Node
25
+ : tSYMBOL { emit_atom :symbol, *val }
26
+ | tNUMBER { emit_atom :number, *val }
27
+ | tSTRING { emit_atom :string, *val }
28
+ | tPARAM_CONST { emit_atom :const, *val }
29
+ | tPARAM_NAMED { emit_atom :named_parameter, *val }
30
+ | tPARAM_NUMBER { emit_atom :positional_parameter, *val }
31
+ | tREGEXP { emit_atom :regexp, *val }
32
+ | tWILDCARD { emit_atom :wildcard, *val }
33
+ | tUNIFY { emit_atom :unify, *val }
34
+ ;
35
+
36
+ union # @return Node
37
+ : '{' separated_variadic_patterns '}' { emit_union(*val) }
38
+ ;
39
+
40
+ variadic_pattern # @return Node
41
+ : node_pattern_no_union
42
+ | union
43
+ | node_pattern repetition
44
+ {
45
+ main, repeat_t = val
46
+ emit_unary_op(:repetition, repeat_t, main, repeat_t)
47
+ }
48
+ | opt_capture '<' node_pattern_list opt_rest '>'
49
+ {
50
+ opt_capture, bracket, node_pattern_list, opt_rest, close_bracket = val
51
+ node_pattern_list << opt_rest if opt_rest
52
+ main = emit_list :any_order, bracket, node_pattern_list, close_bracket
53
+ emit_capture(opt_capture, main)
54
+ }
55
+ | rest
56
+ ;
57
+
58
+ repetition # @return Token
59
+ : '?'
60
+ | '*'
61
+ | '+'
62
+ ;
63
+
64
+ opt_capture # @return Token | nil
65
+ :
66
+ | '$'
67
+ ;
68
+
69
+ rest # @return Node
70
+ : opt_capture '...' { emit_capture(val[0], emit_atom(:rest, val[1])) }
71
+ ;
72
+
73
+ opt_rest # @return Node | nil
74
+ :
75
+ | rest
76
+ ;
77
+
78
+ args # @return [Token, Array<Node>, Token] | nil
79
+ :
80
+ | tARG_LIST arg_list ')' { val }
81
+ ;
82
+
83
+ arg_list # @return Array<Node>
84
+ : node_pattern { val }
85
+ | arg_list ',' node_pattern { val[0] << val[2] }
86
+ ;
87
+
88
+ node_pattern_list # @return Array<Node>
89
+ : node_pattern { val }
90
+ | node_pattern_list node_pattern { val[0] << val[1] }
91
+ ;
92
+
93
+ variadic_pattern_list # @return Array<Node>
94
+ : variadic_pattern { val }
95
+ | variadic_pattern_list variadic_pattern { val[0] << val[1] }
96
+ ;
97
+
98
+ separated_variadic_patterns # @return Array<Array<Node>>
99
+ : { [[]] }
100
+ | separated_variadic_patterns variadic_pattern { val[0].last << val[1]; val[0] }
101
+ | separated_variadic_patterns '|' { val[0] << [] }
102
+ ;
103
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ class NodePattern
6
+ # Utility to assign a set of values to a constant
7
+ module Sets
8
+ REGISTRY = Hash.new do |h, set|
9
+ name = Sets.name(set).freeze
10
+ Sets.const_set(name, set)
11
+ h[set] = "::RuboCop::AST::NodePattern::Sets::#{name}"
12
+ end
13
+
14
+ MAX = 4
15
+ def self.name(set)
16
+ elements = set
17
+ elements = set.first(MAX - 1) << :etc if set.size > MAX
18
+ name = elements.to_a.join('_').upcase.gsub(/[^A-Z0-9_]/, '')
19
+ uniq("SET_#{name}")
20
+ end
21
+
22
+ def self.uniq(name)
23
+ return name unless Sets.const_defined?(name)
24
+
25
+ (2..Float::INFINITY).each do |i|
26
+ uniq = "#{name}_#{i}"
27
+ return uniq unless Sets.const_defined?(uniq)
28
+ end
29
+ end
30
+
31
+ def self.[](set)
32
+ REGISTRY[set]
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ class NodePattern
6
+ class Parser
7
+ # Overrides Parser to use `WithMeta` variants and provide additional methods
8
+ class WithMeta < Parser
9
+ # Overrides Lexer to token locations and comments
10
+ class Lexer < NodePattern::Lexer
11
+ attr_reader :source_buffer
12
+
13
+ def initialize(str_or_buffer)
14
+ @source_buffer = if str_or_buffer.respond_to?(:source)
15
+ str_or_buffer
16
+ else
17
+ ::Parser::Source::Buffer.new('(string)', source: str_or_buffer)
18
+ end
19
+ @comments = []
20
+ super(@source_buffer.source)
21
+ end
22
+
23
+ def token(type, value)
24
+ super(type, [value, pos])
25
+ end
26
+
27
+ def emit_comment
28
+ @comments << Comment.new(pos)
29
+ super
30
+ end
31
+
32
+ # @return [::Parser::Source::Range] last match's position
33
+ def pos
34
+ ::Parser::Source::Range.new(source_buffer, ss.pos - ss.matched_size, ss.pos)
35
+ end
36
+ end
37
+
38
+ # Overrides Builder to emit nodes with locations
39
+ class Builder < NodePattern::Builder
40
+ def emit_atom(type, token)
41
+ value, loc = token
42
+ begin_l = loc.resize(1)
43
+ end_l = loc.end.adjust(begin_pos: -1)
44
+ begin_l = nil if begin_l.source.match?(/\w/)
45
+ end_l = nil if end_l.source.match?(/\w/)
46
+ n(type, [value], source_map(token, begin_t: begin_l, end_t: end_l))
47
+ end
48
+
49
+ def emit_unary_op(type, operator_t = nil, *children)
50
+ children[-1] = children[-1].first if children[-1].is_a?(Array) # token?
51
+ map = source_map(children.first.loc.expression, operator_t: operator_t)
52
+ n(type, children, map)
53
+ end
54
+
55
+ def emit_list(type, begin_t, children, end_t)
56
+ expr = children.first.loc.expression.join(children.last.loc.expression)
57
+ map = source_map(expr, begin_t: begin_t, end_t: end_t)
58
+ n(type, children, map)
59
+ end
60
+
61
+ def emit_call(type, selector_t, args = nil)
62
+ selector, = selector_t
63
+ begin_t, arg_nodes, end_t = args
64
+
65
+ map = source_map(selector_t, begin_t: begin_t, end_t: end_t, selector_t: selector_t)
66
+ n(type, [selector, *arg_nodes], map)
67
+ end
68
+
69
+ private
70
+
71
+ def n(type, children, source_map)
72
+ super(type, children, { location: source_map })
73
+ end
74
+
75
+ def loc(token_or_range)
76
+ return token_or_range[1] if token_or_range.is_a?(Array)
77
+
78
+ token_or_range
79
+ end
80
+
81
+ def join_exprs(left_expr, right_expr)
82
+ left_expr.loc.expression
83
+ .join(right_expr.loc.expression)
84
+ end
85
+
86
+ def source_map(token_or_range, begin_t: nil, end_t: nil, operator_t: nil, selector_t: nil)
87
+ expression_l = loc(token_or_range)
88
+ expression_l = expression_l.expression if expression_l.respond_to?(:expression)
89
+ locs = [begin_t, end_t, operator_t, selector_t].map { |token| loc(token) }
90
+ begin_l, end_l, operator_l, selector_l = locs
91
+
92
+ expression_l = locs.compact.inject(expression_l, :join)
93
+
94
+ ::Parser::Source::Map::Send.new(_dot_l = nil, selector_l, begin_l, end_l, expression_l)
95
+ .with_operator(operator_l)
96
+ end
97
+ end
98
+
99
+ attr_reader :comments, :tokens
100
+
101
+ def do_parse
102
+ r = super
103
+ @comments = @lexer.comments
104
+ @tokens = @lexer.tokens
105
+ r
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -9,6 +9,7 @@ module RuboCop
9
9
  # and other information such as disabled lines for cops.
10
10
  # It also provides a convenient way to access source lines.
11
11
  class ProcessedSource
12
+ # @api private
12
13
  STRING_SOURCE_NAME = '(string)'
13
14
 
14
15
  attr_reader :path, :buffer, :ast, :comments, :tokens, :diagnostics,
@@ -19,6 +20,9 @@ module RuboCop
19
20
  new(file, ruby_version, path)
20
21
  end
21
22
 
23
+ INVALID_LEVELS = %i[error fatal].freeze
24
+ private_constant :INVALID_LEVELS
25
+
22
26
  def initialize(source, ruby_version, path = nil)
23
27
  # Defaults source encoding to UTF-8, regardless of the encoding it has
24
28
  # been read with, which could be non-utf8 depending on the default
@@ -63,7 +67,7 @@ module RuboCop
63
67
  def valid_syntax?
64
68
  return false if @parser_error
65
69
 
66
- @diagnostics.none? { |d| %i[error fatal].include?(d.level) }
70
+ @diagnostics.none? { |d| INVALID_LEVELS.include?(d.level) }
67
71
  end
68
72
 
69
73
  # Raw source checksum for tracking infinite loops.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/ModuleLength
4
3
  module RuboCop
5
4
  module AST
6
5
  # Provides methods for traversing an AST.
@@ -8,197 +7,175 @@ module RuboCop
8
7
  # Override methods to perform custom processing. Remember to call `super`
9
8
  # if you want to recursively process descendant nodes.
10
9
  module Traversal
10
+ # Only for debugging.
11
+ # @api private
12
+ class DebugError < RuntimeError
13
+ end
14
+
15
+ TYPE_TO_METHOD = Hash.new { |h, type| h[type] = :"on_#{type}" }
16
+
11
17
  def walk(node)
12
18
  return if node.nil?
13
19
 
14
- send(:"on_#{node.type}", node)
20
+ send(TYPE_TO_METHOD[node.type], node)
15
21
  nil
16
22
  end
17
23
 
18
- NO_CHILD_NODES = %i[true false nil int float complex
19
- rational str sym regopt self lvar
20
- ivar cvar gvar nth_ref back_ref cbase
21
- arg restarg blockarg shadowarg
22
- kwrestarg zsuper redo retry
23
- forward_args forwarded_args
24
- match_var match_nil_pattern empty_else
25
- forward_arg lambda procarg0 __ENCODING__].freeze
26
- ONE_CHILD_NODE = %i[splat kwsplat block_pass not break next
27
- preexe postexe match_current_line defined?
28
- arg_expr pin match_rest if_guard unless_guard
29
- match_with_trailing_comma].freeze
30
- MANY_CHILD_NODES = %i[dstr dsym xstr regexp array hash pair
31
- mlhs masgn or_asgn and_asgn rasgn mrasgn
32
- undef alias args super yield or and
33
- while_post until_post iflipflop eflipflop
34
- match_with_lvasgn begin kwbegin return
35
- in_match match_alt
36
- match_as array_pattern array_pattern_with_tail
37
- hash_pattern const_pattern find_pattern
38
- index indexasgn].freeze
39
- SECOND_CHILD_ONLY = %i[lvasgn ivasgn cvasgn gvasgn optarg kwarg
40
- kwoptarg].freeze
41
-
42
- NO_CHILD_NODES.each do |type|
43
- module_eval("def on_#{type}(node); end", __FILE__, __LINE__)
44
- end
45
-
46
- ONE_CHILD_NODE.each do |type|
47
- module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
48
- def on_#{type}(node)
49
- if (child = node.children[0])
50
- send(:"on_\#{child.type}", child)
24
+ # @api private
25
+ module CallbackCompiler
26
+ SEND = 'send(TYPE_TO_METHOD[child.type], child)'
27
+ assign_code = 'child = node.children[%<index>i]'
28
+ code = "#{assign_code}\n#{SEND}"
29
+ TEMPLATE = {
30
+ skip: '',
31
+ always: code,
32
+ nil?: "#{code} if child"
33
+ }.freeze
34
+
35
+ def def_callback(type, *signature,
36
+ arity: signature.size..signature.size,
37
+ arity_check: ENV['RUBOCOP_DEBUG'] && self.arity_check(arity),
38
+ body: self.body(signature, arity_check))
39
+ type, *aliases = type
40
+ lineno = caller_locations(1, 1).first.lineno
41
+ module_eval(<<~RUBY, __FILE__, lineno) # rubocop:disable Style/EvalWithLocation
42
+ def on_#{type}(node)
43
+ #{body}
44
+ nil
51
45
  end
46
+ RUBY
47
+ aliases.each do |m|
48
+ alias_method "on_#{m}", "on_#{type}"
52
49
  end
53
- RUBY
54
- end
55
-
56
- MANY_CHILD_NODES.each do |type|
57
- module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
58
- def on_#{type}(node)
59
- node.children.each { |child| send(:"on_\#{child.type}", child) }
60
- nil
61
- end
62
- RUBY
63
- end
50
+ end
64
51
 
65
- SECOND_CHILD_ONLY.each do |type|
66
- # Guard clause is for nodes nested within mlhs
67
- module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
68
- def on_#{type}(node)
69
- if (child = node.children[1])
70
- send(:"on_\#{child.type}", child)
52
+ def body(signature, prelude)
53
+ signature
54
+ .map.with_index do |arg, i|
55
+ TEMPLATE[arg].gsub('%<index>i', i.to_s)
71
56
  end
72
- end
73
- RUBY
74
- end
75
-
76
- def on_const(node)
77
- return unless (child = node.children[0])
78
-
79
- send(:"on_#{child.type}", child)
80
- end
81
-
82
- def on_casgn(node)
83
- children = node.children
84
- if (child = children[0]) # always const???
85
- send(:"on_#{child.type}", child)
57
+ .unshift(prelude)
58
+ .join("\n")
86
59
  end
87
- return unless (child = children[2])
88
-
89
- send(:"on_#{child.type}", child)
90
- end
91
60
 
92
- def on_class(node)
93
- children = node.children
94
- child = children[0] # always const???
95
- send(:"on_#{child.type}", child)
96
- if (child = children[1])
97
- send(:"on_#{child.type}", child)
61
+ def arity_check(range)
62
+ <<~RUBY
63
+ n = node.children.size
64
+ raise DebugError, [
65
+ 'Expected #{range} children, got',
66
+ n, 'for', node.inspect
67
+ ].join(' ') unless (#{range}).cover?(node.children.size)
68
+ RUBY
98
69
  end
99
- return unless (child = children[2])
100
-
101
- send(:"on_#{child.type}", child)
102
- end
103
-
104
- def on_def(node)
105
- children = node.children
106
- on_args(children[1])
107
- return unless (child = children[2])
108
-
109
- send(:"on_#{child.type}", child)
110
70
  end
111
-
112
- def on_send(node)
71
+ private_constant :CallbackCompiler
72
+ extend CallbackCompiler
73
+ send_code = CallbackCompiler::SEND
74
+
75
+ ### arity == 0
76
+ no_children = %i[true false nil self cbase zsuper redo retry
77
+ forward_args forwarded_args match_nil_pattern
78
+ forward_arg lambda empty_else kwnilarg
79
+ __FILE__ __LINE__ __ENCODING__]
80
+
81
+ ### arity == 0..1
82
+ opt_symbol_child = %i[restarg kwrestarg]
83
+ opt_node_child = %i[splat kwsplat match_rest]
84
+
85
+ ### arity == 1
86
+ literal_child = %i[int float complex
87
+ rational str sym lvar
88
+ ivar cvar gvar nth_ref back_ref
89
+ arg blockarg shadowarg
90
+ kwarg match_var]
91
+
92
+ many_symbol_children = %i[regopt]
93
+
94
+ node_child = %i[block_pass not
95
+ match_current_line defined?
96
+ arg_expr pin if_guard unless_guard
97
+ match_with_trailing_comma]
98
+ node_or_nil_child = %i[preexe postexe]
99
+
100
+ NO_CHILD_NODES = (no_children + opt_symbol_child + literal_child).to_set.freeze
101
+ private_constant :NO_CHILD_NODES # Used by Commissioner
102
+
103
+ ### arity > 1
104
+ symbol_then_opt_node = %i[lvasgn ivasgn cvasgn gvasgn]
105
+ symbol_then_node_or_nil = %i[optarg kwoptarg]
106
+ node_then_opt_node = %i[while until module sclass]
107
+
108
+ ### variable arity
109
+ many_node_children = %i[dstr dsym xstr regexp array hash pair
110
+ mlhs masgn or_asgn and_asgn rasgn mrasgn
111
+ undef alias args super yield or and
112
+ while_post until_post iflipflop eflipflop
113
+ match_with_lvasgn begin kwbegin return
114
+ in_match match_alt break next
115
+ match_as array_pattern array_pattern_with_tail
116
+ hash_pattern const_pattern find_pattern
117
+ index indexasgn procarg0]
118
+ many_opt_node_children = %i[case rescue resbody ensure for when
119
+ case_match in_pattern irange erange]
120
+
121
+ ### Callbacks for above
122
+ def_callback no_children
123
+ def_callback opt_symbol_child, :skip, arity: 0..1
124
+ def_callback opt_node_child, :nil?, arity: 0..1
125
+
126
+ def_callback literal_child, :skip
127
+ def_callback node_child, :always
128
+ def_callback node_or_nil_child, :nil?
129
+
130
+ def_callback symbol_then_opt_node, :skip, :nil?, arity: 1..2
131
+ def_callback symbol_then_node_or_nil, :skip, :nil?
132
+ def_callback node_then_opt_node, :always, :nil?
133
+
134
+ def_callback many_symbol_children, :skip, arity_check: nil
135
+ def_callback many_node_children, body: <<~RUBY
136
+ node.children.each { |child| #{send_code} }
137
+ RUBY
138
+ def_callback many_opt_node_children,
139
+ body: <<~RUBY
140
+ node.children.each { |child| #{send_code} if child }
141
+ RUBY
142
+
143
+ ### Other particular cases
144
+ def_callback :const, :nil?, :skip
145
+ def_callback :casgn, :nil?, :skip, :nil?, arity: 2..3
146
+ def_callback :class, :always, :nil?, :nil?
147
+ def_callback :def, :skip, :always, :nil?
148
+ def_callback :op_asgn, :always, :skip, :always
149
+ def_callback :if, :always, :nil?, :nil?
150
+ def_callback :block, :always, :always, :nil?
151
+ def_callback :numblock, :always, :skip, :nil?
152
+ def_callback :defs, :always, :skip, :always, :nil?
153
+
154
+ def_callback %i[send csend], body: <<~RUBY
113
155
  node.children.each_with_index do |child, i|
114
156
  next if i == 1
115
157
 
116
- send(:"on_#{child.type}", child) if child
158
+ #{send_code} if child
117
159
  end
118
- nil
119
- end
120
-
121
- alias on_csend on_send
122
-
123
- def on_op_asgn(node)
124
- children = node.children
125
- child = children[0]
126
- send(:"on_#{child.type}", child)
127
- child = children[2]
128
- send(:"on_#{child.type}", child)
129
- end
130
-
131
- def on_defs(node)
132
- children = node.children
133
- child = children[0]
134
- send(:"on_#{child.type}", child)
135
- on_args(children[2])
136
- return unless (child = children[3])
137
-
138
- send(:"on_#{child.type}", child)
139
- end
140
-
141
- def on_if(node)
142
- children = node.children
143
- child = children[0]
144
- send(:"on_#{child.type}", child)
145
- if (child = children[1])
146
- send(:"on_#{child.type}", child)
147
- end
148
- return unless (child = children[2])
149
-
150
- send(:"on_#{child.type}", child)
151
- end
152
-
153
- def on_while(node)
154
- children = node.children
155
- child = children[0]
156
- send(:"on_#{child.type}", child)
157
- return unless (child = children[1])
158
-
159
- send(:"on_#{child.type}", child)
160
- end
161
-
162
- alias on_until on_while
163
- alias on_module on_while
164
- alias on_sclass on_while
165
-
166
- def on_block(node)
167
- children = node.children
168
- child = children[0]
169
- send(:"on_#{child.type}", child) # can be send, zsuper...
170
- on_args(children[1])
171
- return unless (child = children[2])
172
-
173
- send(:"on_#{child.type}", child)
174
- end
175
-
176
- def on_case(node)
160
+ RUBY
161
+
162
+ ### generic processing of any other node (forward compatibility)
163
+ defined = instance_methods(false)
164
+ .grep(/^on_/)
165
+ .map { |s| s.to_s[3..-1].to_sym } # :on_foo => :foo
166
+
167
+ to_define = ::Parser::Meta::NODE_TYPES.to_a
168
+ to_define -= defined
169
+ to_define -= %i[numargs ident] # transient
170
+ to_define -= %i[blockarg_expr restarg_expr] # obsolete
171
+ to_define -= %i[objc_kwarg objc_restarg objc_varargs] # mac_ruby
172
+ def_callback to_define, body: <<~RUBY
177
173
  node.children.each do |child|
178
- send(:"on_#{child.type}", child) if child
174
+ next unless child.class == Node
175
+ #{send_code}
179
176
  end
180
- nil
181
- end
182
-
183
- alias on_rescue on_case
184
- alias on_resbody on_case
185
- alias on_ensure on_case
186
- alias on_for on_case
187
- alias on_when on_case
188
- alias on_case_match on_case
189
- alias on_in_pattern on_case
190
- alias on_irange on_case
191
- alias on_erange on_case
192
-
193
- def on_numblock(node)
194
- children = node.children
195
- child = children[0]
196
- send(:"on_#{child.type}", child)
197
- return unless (child = children[2])
198
-
199
- send(:"on_#{child.type}", child)
200
- end
177
+ RUBY
178
+ MISSING = to_define if ENV['RUBOCOP_DEBUG']
201
179
  end
202
180
  end
203
181
  end
204
- # rubocop:enable Metrics/ModuleLength