rubocop-ast 0.3.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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,
@@ -159,6 +160,20 @@ module RuboCop
159
160
  .length
160
161
  end
161
162
 
163
+ def tokens_within(range_or_node)
164
+ begin_index = first_token_index(range_or_node)
165
+ end_index = last_token_index(range_or_node)
166
+ sorted_tokens[begin_index..end_index]
167
+ end
168
+
169
+ def first_token_of(range_or_node)
170
+ sorted_tokens[first_token_index(range_or_node)]
171
+ end
172
+
173
+ def last_token_of(range_or_node)
174
+ sorted_tokens[last_token_index(range_or_node)]
175
+ end
176
+
162
177
  private
163
178
 
164
179
  def comment_index
@@ -215,9 +230,9 @@ module RuboCop
215
230
  when 2.7
216
231
  require 'parser/ruby27'
217
232
  Parser::Ruby27
218
- when 2.8
219
- require 'parser/ruby28'
220
- Parser::Ruby28
233
+ when 2.8, 3.0
234
+ require 'parser/ruby30'
235
+ Parser::Ruby30
221
236
  else
222
237
  raise ArgumentError,
223
238
  "RuboCop found unknown Ruby version: #{ruby_version.inspect}"
@@ -240,6 +255,32 @@ module RuboCop
240
255
  end
241
256
  end
242
257
  end
258
+
259
+ def first_token_index(range_or_node)
260
+ begin_pos = source_range(range_or_node).begin_pos
261
+ sorted_tokens.bsearch_index { |token| token.begin_pos >= begin_pos }
262
+ end
263
+
264
+ def last_token_index(range_or_node)
265
+ end_pos = source_range(range_or_node).end_pos
266
+ sorted_tokens.bsearch_index { |token| token.end_pos >= end_pos }
267
+ end
268
+
269
+ # The tokens list is always sorted by token position, except for cases when heredoc
270
+ # is passed as a method argument. In this case tokens are interleaved by
271
+ # heredoc contents' tokens.
272
+ def sorted_tokens
273
+ # Use stable sort.
274
+ @sorted_tokens ||= tokens.sort_by.with_index { |token, i| [token.begin_pos, i] }
275
+ end
276
+
277
+ def source_range(range_or_node)
278
+ if range_or_node.respond_to?(:source_range)
279
+ range_or_node.source_range
280
+ else
281
+ range_or_node
282
+ end
283
+ end
243
284
  end
244
285
  end
245
286
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # ...
5
+ module AST
6
+ # Responsible for compatibility with main gem
7
+ # @api private
8
+ module RuboCopCompatibility
9
+ INCOMPATIBLE_COPS = {
10
+ '0.89.0' => 'Layout/LineLength',
11
+ '0.92.0' => 'Style/MixinUsage'
12
+ }.freeze
13
+ def rubocop_loaded
14
+ loaded = Gem::Version.new(RuboCop::Version::STRING)
15
+ incompatible = INCOMPATIBLE_COPS.select do |k, _v|
16
+ loaded < Gem::Version.new(k)
17
+ end.values
18
+ return if incompatible.empty?
19
+
20
+ warn <<~WARNING
21
+ *** WARNING – Incompatible versions of `rubocop` and `rubocop-ast`
22
+ You may encounter issues with the following \
23
+ Cop#{'s' if incompatible.size > 1}: #{incompatible.join(', ')}
24
+ Please upgrade rubocop to at least v#{INCOMPATIBLE_COPS.keys.last}
25
+ WARNING
26
+ end
27
+ end
28
+
29
+ extend RuboCopCompatibility
30
+ end
31
+ end
@@ -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
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