syntax_tree 5.3.0 → 6.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -1
  3. data/CHANGELOG.md +64 -1
  4. data/Gemfile.lock +2 -2
  5. data/README.md +28 -9
  6. data/Rakefile +12 -8
  7. data/bin/console +1 -0
  8. data/bin/whitequark +79 -0
  9. data/doc/changing_structure.md +16 -0
  10. data/lib/syntax_tree/basic_visitor.rb +44 -5
  11. data/lib/syntax_tree/cli.rb +2 -2
  12. data/lib/syntax_tree/dsl.rb +23 -11
  13. data/lib/syntax_tree/{visitor/field_visitor.rb → field_visitor.rb} +54 -55
  14. data/lib/syntax_tree/formatter.rb +1 -1
  15. data/lib/syntax_tree/index.rb +56 -54
  16. data/lib/syntax_tree/json_visitor.rb +55 -0
  17. data/lib/syntax_tree/language_server.rb +157 -2
  18. data/lib/syntax_tree/match_visitor.rb +120 -0
  19. data/lib/syntax_tree/mermaid.rb +177 -0
  20. data/lib/syntax_tree/mermaid_visitor.rb +69 -0
  21. data/lib/syntax_tree/{visitor/mutation_visitor.rb → mutation_visitor.rb} +27 -27
  22. data/lib/syntax_tree/node.rb +198 -107
  23. data/lib/syntax_tree/parser.rb +322 -118
  24. data/lib/syntax_tree/pretty_print_visitor.rb +83 -0
  25. data/lib/syntax_tree/reflection.rb +241 -0
  26. data/lib/syntax_tree/translation/parser.rb +3019 -0
  27. data/lib/syntax_tree/translation/rubocop_ast.rb +21 -0
  28. data/lib/syntax_tree/translation.rb +28 -0
  29. data/lib/syntax_tree/version.rb +1 -1
  30. data/lib/syntax_tree/with_scope.rb +244 -0
  31. data/lib/syntax_tree/yarv/basic_block.rb +53 -0
  32. data/lib/syntax_tree/yarv/calldata.rb +91 -0
  33. data/lib/syntax_tree/yarv/compiler.rb +110 -100
  34. data/lib/syntax_tree/yarv/control_flow_graph.rb +257 -0
  35. data/lib/syntax_tree/yarv/data_flow_graph.rb +338 -0
  36. data/lib/syntax_tree/yarv/decompiler.rb +1 -1
  37. data/lib/syntax_tree/yarv/disassembler.rb +104 -80
  38. data/lib/syntax_tree/yarv/instruction_sequence.rb +43 -18
  39. data/lib/syntax_tree/yarv/instructions.rb +203 -649
  40. data/lib/syntax_tree/yarv/legacy.rb +12 -24
  41. data/lib/syntax_tree/yarv/sea_of_nodes.rb +534 -0
  42. data/lib/syntax_tree/yarv.rb +18 -0
  43. data/lib/syntax_tree.rb +88 -56
  44. data/tasks/sorbet.rake +277 -0
  45. data/tasks/whitequark.rake +87 -0
  46. metadata +23 -11
  47. data/.gitmodules +0 -9
  48. data/lib/syntax_tree/language_server/inlay_hints.rb +0 -159
  49. data/lib/syntax_tree/visitor/environment.rb +0 -84
  50. data/lib/syntax_tree/visitor/json_visitor.rb +0 -55
  51. data/lib/syntax_tree/visitor/match_visitor.rb +0 -122
  52. data/lib/syntax_tree/visitor/pretty_print_visitor.rb +0 -85
  53. data/lib/syntax_tree/visitor/with_environment.rb +0 -140
@@ -0,0 +1,3019 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module Translation
5
+ # This visitor is responsible for converting the syntax tree produced by
6
+ # Syntax Tree into the syntax tree produced by the whitequark/parser gem.
7
+ class Parser < BasicVisitor
8
+ # Heredocs are represented _very_ differently in the parser gem from how
9
+ # they are represented in the Syntax Tree AST. This class is responsible
10
+ # for handling the translation.
11
+ class HeredocBuilder
12
+ Line = Struct.new(:value, :segments)
13
+
14
+ attr_reader :node, :segments
15
+
16
+ def initialize(node)
17
+ @node = node
18
+ @segments = []
19
+ end
20
+
21
+ def <<(segment)
22
+ if segment.type == :str && segments.last &&
23
+ segments.last.type == :str &&
24
+ !segments.last.children.first.end_with?("\n")
25
+ segments.last.children.first << segment.children.first
26
+ else
27
+ segments << segment
28
+ end
29
+ end
30
+
31
+ def trim!
32
+ return unless node.beginning.value[2] == "~"
33
+ lines = [Line.new(+"", [])]
34
+
35
+ segments.each do |segment|
36
+ lines.last.segments << segment
37
+
38
+ if segment.type == :str
39
+ lines.last.value << segment.children.first
40
+ lines << Line.new(+"", []) if lines.last.value.end_with?("\n")
41
+ end
42
+ end
43
+
44
+ lines.pop if lines.last.value.empty?
45
+ return if lines.empty?
46
+
47
+ segments.clear
48
+ lines.each do |line|
49
+ remaining = node.dedent
50
+
51
+ line.segments.each do |segment|
52
+ if segment.type == :str
53
+ if remaining > 0
54
+ whitespace = segment.children.first[/^\s{0,#{remaining}}/]
55
+ segment.children.first.sub!(/^#{whitespace}/, "")
56
+ remaining -= whitespace.length
57
+ end
58
+
59
+ if node.beginning.value[3] != "'" && segments.any? &&
60
+ segments.last.type == :str &&
61
+ segments.last.children.first.end_with?("\\\n")
62
+ segments.last.children.first.gsub!(/\\\n\z/, "")
63
+ segments.last.children.first.concat(segment.children.first)
64
+ elsif !segment.children.first.empty?
65
+ segments << segment
66
+ end
67
+ else
68
+ segments << segment
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ attr_reader :buffer, :stack
76
+
77
+ def initialize(buffer)
78
+ @buffer = buffer
79
+ @stack = []
80
+ end
81
+
82
+ # For each node that we visit, we keep track of it in a stack as we
83
+ # descend into its children. We do this so that child nodes can reflect on
84
+ # their parents if they need additional information about their context.
85
+ def visit(node)
86
+ stack << node
87
+ result = super
88
+ stack.pop
89
+ result
90
+ end
91
+
92
+ visit_methods do
93
+ # Visit an AliasNode node.
94
+ def visit_alias(node)
95
+ s(
96
+ :alias,
97
+ [visit(node.left), visit(node.right)],
98
+ smap_keyword_bare(
99
+ srange_length(node.start_char, 5),
100
+ srange_node(node)
101
+ )
102
+ )
103
+ end
104
+
105
+ # Visit an ARefNode.
106
+ def visit_aref(node)
107
+ if ::Parser::Builders::Default.emit_index
108
+ if node.index.nil?
109
+ s(
110
+ :index,
111
+ [visit(node.collection)],
112
+ smap_index(
113
+ srange_find(node.collection.end_char, node.end_char, "["),
114
+ srange_length(node.end_char, -1),
115
+ srange_node(node)
116
+ )
117
+ )
118
+ else
119
+ s(
120
+ :index,
121
+ [visit(node.collection)].concat(visit_all(node.index.parts)),
122
+ smap_index(
123
+ srange_find_between(node.collection, node.index, "["),
124
+ srange_length(node.end_char, -1),
125
+ srange_node(node)
126
+ )
127
+ )
128
+ end
129
+ else
130
+ if node.index.nil?
131
+ s(
132
+ :send,
133
+ [visit(node.collection), :[]],
134
+ smap_send_bare(
135
+ srange_find(node.collection.end_char, node.end_char, "[]"),
136
+ srange_node(node)
137
+ )
138
+ )
139
+ else
140
+ s(
141
+ :send,
142
+ [visit(node.collection), :[], *visit_all(node.index.parts)],
143
+ smap_send_bare(
144
+ srange(
145
+ srange_find_between(
146
+ node.collection,
147
+ node.index,
148
+ "["
149
+ ).begin_pos,
150
+ node.end_char
151
+ ),
152
+ srange_node(node)
153
+ )
154
+ )
155
+ end
156
+ end
157
+ end
158
+
159
+ # Visit an ARefField node.
160
+ def visit_aref_field(node)
161
+ if ::Parser::Builders::Default.emit_index
162
+ if node.index.nil?
163
+ s(
164
+ :indexasgn,
165
+ [visit(node.collection)],
166
+ smap_index(
167
+ srange_find(node.collection.end_char, node.end_char, "["),
168
+ srange_length(node.end_char, -1),
169
+ srange_node(node)
170
+ )
171
+ )
172
+ else
173
+ s(
174
+ :indexasgn,
175
+ [visit(node.collection)].concat(visit_all(node.index.parts)),
176
+ smap_index(
177
+ srange_find_between(node.collection, node.index, "["),
178
+ srange_length(node.end_char, -1),
179
+ srange_node(node)
180
+ )
181
+ )
182
+ end
183
+ else
184
+ if node.index.nil?
185
+ s(
186
+ :send,
187
+ [visit(node.collection), :[]=],
188
+ smap_send_bare(
189
+ srange_find(node.collection.end_char, node.end_char, "[]"),
190
+ srange_node(node)
191
+ )
192
+ )
193
+ else
194
+ s(
195
+ :send,
196
+ [visit(node.collection), :[]=].concat(
197
+ visit_all(node.index.parts)
198
+ ),
199
+ smap_send_bare(
200
+ srange(
201
+ srange_find_between(
202
+ node.collection,
203
+ node.index,
204
+ "["
205
+ ).begin_pos,
206
+ node.end_char
207
+ ),
208
+ srange_node(node)
209
+ )
210
+ )
211
+ end
212
+ end
213
+ end
214
+
215
+ # Visit an ArgBlock node.
216
+ def visit_arg_block(node)
217
+ s(
218
+ :block_pass,
219
+ [visit(node.value)],
220
+ smap_operator(srange_length(node.start_char, 1), srange_node(node))
221
+ )
222
+ end
223
+
224
+ # Visit an ArgStar node.
225
+ def visit_arg_star(node)
226
+ if stack[-3].is_a?(MLHSParen) && stack[-3].contents.is_a?(MLHS)
227
+ if node.value.nil?
228
+ s(:restarg, [], smap_variable(nil, srange_node(node)))
229
+ else
230
+ s(
231
+ :restarg,
232
+ [node.value.value.to_sym],
233
+ smap_variable(srange_node(node.value), srange_node(node))
234
+ )
235
+ end
236
+ else
237
+ s(
238
+ :splat,
239
+ node.value.nil? ? [] : [visit(node.value)],
240
+ smap_operator(
241
+ srange_length(node.start_char, 1),
242
+ srange_node(node)
243
+ )
244
+ )
245
+ end
246
+ end
247
+
248
+ # Visit an ArgsForward node.
249
+ def visit_args_forward(node)
250
+ s(:forwarded_args, [], smap(srange_node(node)))
251
+ end
252
+
253
+ # Visit an ArrayLiteral node.
254
+ def visit_array(node)
255
+ s(
256
+ :array,
257
+ node.contents ? visit_all(node.contents.parts) : [],
258
+ if node.lbracket.nil?
259
+ smap_collection_bare(srange_node(node))
260
+ else
261
+ smap_collection(
262
+ srange_node(node.lbracket),
263
+ srange_length(node.end_char, -1),
264
+ srange_node(node)
265
+ )
266
+ end
267
+ )
268
+ end
269
+
270
+ # Visit an AryPtn node.
271
+ def visit_aryptn(node)
272
+ type = :array_pattern
273
+ children = visit_all(node.requireds)
274
+
275
+ if node.rest.is_a?(VarField)
276
+ if !node.rest.value.nil?
277
+ children << s(:match_rest, [visit(node.rest)], nil)
278
+ elsif node.posts.empty? &&
279
+ node.rest.start_char == node.rest.end_char
280
+ # Here we have an implicit rest, as in [foo,]. parser has a
281
+ # specific type for these patterns.
282
+ type = :array_pattern_with_tail
283
+ else
284
+ children << s(:match_rest, [], nil)
285
+ end
286
+ end
287
+
288
+ if node.constant
289
+ s(
290
+ :const_pattern,
291
+ [
292
+ visit(node.constant),
293
+ s(
294
+ type,
295
+ children + visit_all(node.posts),
296
+ smap_collection_bare(
297
+ srange(node.constant.end_char + 1, node.end_char - 1)
298
+ )
299
+ )
300
+ ],
301
+ smap_collection(
302
+ srange_length(node.constant.end_char, 1),
303
+ srange_length(node.end_char, -1),
304
+ srange_node(node)
305
+ )
306
+ )
307
+ else
308
+ s(
309
+ type,
310
+ children + visit_all(node.posts),
311
+ if buffer.source[node.start_char] == "["
312
+ smap_collection(
313
+ srange_length(node.start_char, 1),
314
+ srange_length(node.end_char, -1),
315
+ srange_node(node)
316
+ )
317
+ else
318
+ smap_collection_bare(srange_node(node))
319
+ end
320
+ )
321
+ end
322
+ end
323
+
324
+ # Visit an Assign node.
325
+ def visit_assign(node)
326
+ target = visit(node.target)
327
+ location =
328
+ target
329
+ .location
330
+ .with_operator(srange_find_between(node.target, node.value, "="))
331
+ .with_expression(srange_node(node))
332
+
333
+ s(target.type, target.children + [visit(node.value)], location)
334
+ end
335
+
336
+ # Visit an Assoc node.
337
+ def visit_assoc(node)
338
+ if node.value.nil?
339
+ expression = srange(node.start_char, node.end_char - 1)
340
+
341
+ type, location =
342
+ if node.key.value.start_with?(/[A-Z]/)
343
+ [:const, smap_constant(nil, expression, expression)]
344
+ else
345
+ [:send, smap_send_bare(expression, expression)]
346
+ end
347
+
348
+ s(
349
+ :pair,
350
+ [
351
+ visit(node.key),
352
+ s(type, [nil, node.key.value.chomp(":").to_sym], location)
353
+ ],
354
+ smap_operator(
355
+ srange_length(node.key.end_char, -1),
356
+ srange_node(node)
357
+ )
358
+ )
359
+ else
360
+ s(
361
+ :pair,
362
+ [visit(node.key), visit(node.value)],
363
+ smap_operator(
364
+ srange_search_between(node.key, node.value, "=>") ||
365
+ srange_length(node.key.end_char, -1),
366
+ srange_node(node)
367
+ )
368
+ )
369
+ end
370
+ end
371
+
372
+ # Visit an AssocSplat node.
373
+ def visit_assoc_splat(node)
374
+ s(
375
+ :kwsplat,
376
+ [visit(node.value)],
377
+ smap_operator(srange_length(node.start_char, 2), srange_node(node))
378
+ )
379
+ end
380
+
381
+ # Visit a Backref node.
382
+ def visit_backref(node)
383
+ location = smap(srange_node(node))
384
+
385
+ if node.value.match?(/^\$\d+$/)
386
+ s(:nth_ref, [node.value[1..].to_i], location)
387
+ else
388
+ s(:back_ref, [node.value.to_sym], location)
389
+ end
390
+ end
391
+
392
+ # Visit a BareAssocHash node.
393
+ def visit_bare_assoc_hash(node)
394
+ s(
395
+ if ::Parser::Builders::Default.emit_kwargs &&
396
+ !stack[-2].is_a?(ArrayLiteral)
397
+ :kwargs
398
+ else
399
+ :hash
400
+ end,
401
+ visit_all(node.assocs),
402
+ smap_collection_bare(srange_node(node))
403
+ )
404
+ end
405
+
406
+ # Visit a BEGINBlock node.
407
+ def visit_BEGIN(node)
408
+ s(
409
+ :preexe,
410
+ [visit(node.statements)],
411
+ smap_keyword(
412
+ srange_length(node.start_char, 5),
413
+ srange_find(node.start_char + 5, node.statements.start_char, "{"),
414
+ srange_length(node.end_char, -1),
415
+ srange_node(node)
416
+ )
417
+ )
418
+ end
419
+
420
+ # Visit a Begin node.
421
+ def visit_begin(node)
422
+ location =
423
+ smap_collection(
424
+ srange_length(node.start_char, 5),
425
+ srange_length(node.end_char, -3),
426
+ srange_node(node)
427
+ )
428
+
429
+ if node.bodystmt.empty?
430
+ s(:kwbegin, [], location)
431
+ elsif node.bodystmt.rescue_clause.nil? &&
432
+ node.bodystmt.ensure_clause.nil? &&
433
+ node.bodystmt.else_clause.nil?
434
+ child = visit(node.bodystmt.statements)
435
+
436
+ s(
437
+ :kwbegin,
438
+ child.type == :begin ? child.children : [child],
439
+ location
440
+ )
441
+ else
442
+ s(:kwbegin, [visit(node.bodystmt)], location)
443
+ end
444
+ end
445
+
446
+ # Visit a Binary node.
447
+ def visit_binary(node)
448
+ case node.operator
449
+ when :|
450
+ current = -2
451
+ while stack[current].is_a?(Binary) && stack[current].operator == :|
452
+ current -= 1
453
+ end
454
+
455
+ if stack[current].is_a?(In)
456
+ s(:match_alt, [visit(node.left), visit(node.right)], nil)
457
+ else
458
+ visit(canonical_binary(node))
459
+ end
460
+ when :"=>", :"&&", :and, :"||", :or
461
+ s(
462
+ { "=>": :match_as, "&&": :and, "||": :or }.fetch(
463
+ node.operator,
464
+ node.operator
465
+ ),
466
+ [visit(node.left), visit(node.right)],
467
+ smap_operator(
468
+ srange_find_between(node.left, node.right, node.operator.to_s),
469
+ srange_node(node)
470
+ )
471
+ )
472
+ when :=~
473
+ # When you use a regular expression on the left hand side of a =~
474
+ # operator and it doesn't have interpolatoin, then its named capture
475
+ # groups introduce local variables into the scope. In this case the
476
+ # parser gem has a different node (match_with_lvasgn) instead of the
477
+ # regular send.
478
+ if node.left.is_a?(RegexpLiteral) && node.left.parts.length == 1 &&
479
+ node.left.parts.first.is_a?(TStringContent)
480
+ s(
481
+ :match_with_lvasgn,
482
+ [visit(node.left), visit(node.right)],
483
+ smap_operator(
484
+ srange_find_between(
485
+ node.left,
486
+ node.right,
487
+ node.operator.to_s
488
+ ),
489
+ srange_node(node)
490
+ )
491
+ )
492
+ else
493
+ visit(canonical_binary(node))
494
+ end
495
+ else
496
+ visit(canonical_binary(node))
497
+ end
498
+ end
499
+
500
+ # Visit a BlockArg node.
501
+ def visit_blockarg(node)
502
+ if node.name.nil?
503
+ s(:blockarg, [nil], smap_variable(nil, srange_node(node)))
504
+ else
505
+ s(
506
+ :blockarg,
507
+ [node.name.value.to_sym],
508
+ smap_variable(srange_node(node.name), srange_node(node))
509
+ )
510
+ end
511
+ end
512
+
513
+ # Visit a BlockVar node.
514
+ def visit_block_var(node)
515
+ shadowargs =
516
+ node.locals.map do |local|
517
+ s(
518
+ :shadowarg,
519
+ [local.value.to_sym],
520
+ smap_variable(srange_node(local), srange_node(local))
521
+ )
522
+ end
523
+
524
+ params = node.params
525
+ children =
526
+ if ::Parser::Builders::Default.emit_procarg0 && node.arg0?
527
+ # There is a special node type in the parser gem for when a single
528
+ # required parameter to a block would potentially be expanded
529
+ # automatically. We handle that case here.
530
+ required = params.requireds.first
531
+ procarg0 =
532
+ if ::Parser::Builders::Default.emit_arg_inside_procarg0 &&
533
+ required.is_a?(Ident)
534
+ s(
535
+ :procarg0,
536
+ [
537
+ s(
538
+ :arg,
539
+ [required.value.to_sym],
540
+ smap_variable(
541
+ srange_node(required),
542
+ srange_node(required)
543
+ )
544
+ )
545
+ ],
546
+ smap_collection_bare(srange_node(required))
547
+ )
548
+ else
549
+ child = visit(required)
550
+ s(:procarg0, child, child.location)
551
+ end
552
+
553
+ [procarg0]
554
+ else
555
+ visit(params).children
556
+ end
557
+
558
+ s(
559
+ :args,
560
+ children + shadowargs,
561
+ smap_collection(
562
+ srange_length(node.start_char, 1),
563
+ srange_length(node.end_char, -1),
564
+ srange_node(node)
565
+ )
566
+ )
567
+ end
568
+
569
+ # Visit a BodyStmt node.
570
+ def visit_bodystmt(node)
571
+ result = visit(node.statements)
572
+
573
+ if node.rescue_clause
574
+ rescue_node = visit(node.rescue_clause)
575
+
576
+ children = [result] + rescue_node.children
577
+ location = rescue_node.location
578
+
579
+ if node.else_clause
580
+ children.pop
581
+ children << visit(node.else_clause)
582
+
583
+ location =
584
+ smap_condition(
585
+ nil,
586
+ nil,
587
+ srange_length(node.else_clause.start_char - 3, -4),
588
+ nil,
589
+ srange(
590
+ location.expression.begin_pos,
591
+ node.else_clause.end_char
592
+ )
593
+ )
594
+ end
595
+
596
+ result = s(rescue_node.type, children, location)
597
+ end
598
+
599
+ if node.ensure_clause
600
+ ensure_node = visit(node.ensure_clause)
601
+
602
+ expression =
603
+ (
604
+ if result
605
+ result.location.expression.join(
606
+ ensure_node.location.expression
607
+ )
608
+ else
609
+ ensure_node.location.expression
610
+ end
611
+ )
612
+ location = ensure_node.location.with_expression(expression)
613
+
614
+ result =
615
+ s(ensure_node.type, [result] + ensure_node.children, location)
616
+ end
617
+
618
+ result
619
+ end
620
+
621
+ # Visit a Break node.
622
+ def visit_break(node)
623
+ s(
624
+ :break,
625
+ visit_all(node.arguments.parts),
626
+ smap_keyword_bare(
627
+ srange_length(node.start_char, 5),
628
+ srange_node(node)
629
+ )
630
+ )
631
+ end
632
+
633
+ # Visit a CallNode node.
634
+ def visit_call(node)
635
+ visit_command_call(
636
+ CommandCall.new(
637
+ receiver: node.receiver,
638
+ operator: node.operator,
639
+ message: node.message,
640
+ arguments: node.arguments,
641
+ block: nil,
642
+ location: node.location
643
+ )
644
+ )
645
+ end
646
+
647
+ # Visit a Case node.
648
+ def visit_case(node)
649
+ clauses = [node.consequent]
650
+ while clauses.last && !clauses.last.is_a?(Else)
651
+ clauses << clauses.last.consequent
652
+ end
653
+
654
+ else_token =
655
+ if clauses.last.is_a?(Else)
656
+ srange_length(clauses.last.start_char, 4)
657
+ end
658
+
659
+ s(
660
+ node.consequent.is_a?(In) ? :case_match : :case,
661
+ [visit(node.value)] + clauses.map { |clause| visit(clause) },
662
+ smap_condition(
663
+ srange_length(node.start_char, 4),
664
+ nil,
665
+ else_token,
666
+ srange_length(node.end_char, -3),
667
+ srange_node(node)
668
+ )
669
+ )
670
+ end
671
+
672
+ # Visit a CHAR node.
673
+ def visit_CHAR(node)
674
+ s(
675
+ :str,
676
+ [node.value[1..]],
677
+ smap_collection(
678
+ srange_length(node.start_char, 1),
679
+ nil,
680
+ srange_node(node)
681
+ )
682
+ )
683
+ end
684
+
685
+ # Visit a ClassDeclaration node.
686
+ def visit_class(node)
687
+ operator =
688
+ if node.superclass
689
+ srange_find_between(node.constant, node.superclass, "<")
690
+ end
691
+
692
+ s(
693
+ :class,
694
+ [
695
+ visit(node.constant),
696
+ visit(node.superclass),
697
+ visit(node.bodystmt)
698
+ ],
699
+ smap_definition(
700
+ srange_length(node.start_char, 5),
701
+ operator,
702
+ srange_node(node.constant),
703
+ srange_length(node.end_char, -3)
704
+ ).with_expression(srange_node(node))
705
+ )
706
+ end
707
+
708
+ # Visit a Command node.
709
+ def visit_command(node)
710
+ visit_command_call(
711
+ CommandCall.new(
712
+ receiver: nil,
713
+ operator: nil,
714
+ message: node.message,
715
+ arguments: node.arguments,
716
+ block: node.block,
717
+ location: node.location
718
+ )
719
+ )
720
+ end
721
+
722
+ # Visit a CommandCall node.
723
+ def visit_command_call(node)
724
+ children = [
725
+ visit(node.receiver),
726
+ node.message == :call ? :call : node.message.value.to_sym
727
+ ]
728
+
729
+ begin_token = nil
730
+ end_token = nil
731
+
732
+ case node.arguments
733
+ when Args
734
+ children += visit_all(node.arguments.parts)
735
+ when ArgParen
736
+ case node.arguments.arguments
737
+ when nil
738
+ # skip
739
+ when ArgsForward
740
+ children << visit(node.arguments.arguments)
741
+ else
742
+ children += visit_all(node.arguments.arguments.parts)
743
+ end
744
+
745
+ begin_token = srange_length(node.arguments.start_char, 1)
746
+ end_token = srange_length(node.arguments.end_char, -1)
747
+ end
748
+
749
+ dot_bound =
750
+ if node.arguments
751
+ node.arguments.start_char
752
+ elsif node.block
753
+ node.block.start_char
754
+ else
755
+ node.end_char
756
+ end
757
+
758
+ expression =
759
+ if node.arguments.is_a?(ArgParen)
760
+ srange(node.start_char, node.arguments.end_char)
761
+ elsif node.arguments.is_a?(Args) && node.arguments.parts.any?
762
+ last_part = node.arguments.parts.last
763
+ end_char =
764
+ if last_part.is_a?(Heredoc)
765
+ last_part.beginning.end_char
766
+ else
767
+ last_part.end_char
768
+ end
769
+
770
+ srange(node.start_char, end_char)
771
+ elsif node.block
772
+ srange_node(node.message)
773
+ else
774
+ srange_node(node)
775
+ end
776
+
777
+ call =
778
+ s(
779
+ if node.operator.is_a?(Op) && node.operator.value == "&."
780
+ :csend
781
+ else
782
+ :send
783
+ end,
784
+ children,
785
+ smap_send(
786
+ if node.operator == :"::"
787
+ srange_find(
788
+ node.receiver.end_char,
789
+ if node.message == :call
790
+ dot_bound
791
+ else
792
+ node.message.start_char
793
+ end,
794
+ "::"
795
+ )
796
+ elsif node.operator
797
+ srange_node(node.operator)
798
+ end,
799
+ node.message == :call ? nil : srange_node(node.message),
800
+ begin_token,
801
+ end_token,
802
+ expression
803
+ )
804
+ )
805
+
806
+ if node.block
807
+ type, arguments = block_children(node.block)
808
+
809
+ s(
810
+ type,
811
+ [call, arguments, visit(node.block.bodystmt)],
812
+ smap_collection(
813
+ srange_node(node.block.opening),
814
+ srange_length(
815
+ node.end_char,
816
+ node.block.opening.is_a?(Kw) ? -3 : -1
817
+ ),
818
+ srange_node(node)
819
+ )
820
+ )
821
+ else
822
+ call
823
+ end
824
+ end
825
+
826
+ # Visit a Const node.
827
+ def visit_const(node)
828
+ s(
829
+ :const,
830
+ [nil, node.value.to_sym],
831
+ smap_constant(nil, srange_node(node), srange_node(node))
832
+ )
833
+ end
834
+
835
+ # Visit a ConstPathField node.
836
+ def visit_const_path_field(node)
837
+ if node.parent.is_a?(VarRef) && node.parent.value.is_a?(Kw) &&
838
+ node.parent.value.value == "self" && node.constant.is_a?(Ident)
839
+ s(:send, [visit(node.parent), :"#{node.constant.value}="], nil)
840
+ else
841
+ s(
842
+ :casgn,
843
+ [visit(node.parent), node.constant.value.to_sym],
844
+ smap_constant(
845
+ srange_find_between(node.parent, node.constant, "::"),
846
+ srange_node(node.constant),
847
+ srange_node(node)
848
+ )
849
+ )
850
+ end
851
+ end
852
+
853
+ # Visit a ConstPathRef node.
854
+ def visit_const_path_ref(node)
855
+ s(
856
+ :const,
857
+ [visit(node.parent), node.constant.value.to_sym],
858
+ smap_constant(
859
+ srange_find_between(node.parent, node.constant, "::"),
860
+ srange_node(node.constant),
861
+ srange_node(node)
862
+ )
863
+ )
864
+ end
865
+
866
+ # Visit a ConstRef node.
867
+ def visit_const_ref(node)
868
+ s(
869
+ :const,
870
+ [nil, node.constant.value.to_sym],
871
+ smap_constant(nil, srange_node(node.constant), srange_node(node))
872
+ )
873
+ end
874
+
875
+ # Visit a CVar node.
876
+ def visit_cvar(node)
877
+ s(
878
+ :cvar,
879
+ [node.value.to_sym],
880
+ smap_variable(srange_node(node), srange_node(node))
881
+ )
882
+ end
883
+
884
+ # Visit a DefNode node.
885
+ def visit_def(node)
886
+ name = node.name.value.to_sym
887
+ args =
888
+ case node.params
889
+ when Params
890
+ child = visit(node.params)
891
+
892
+ s(
893
+ child.type,
894
+ child.children,
895
+ smap_collection_bare(child.location&.expression)
896
+ )
897
+ when Paren
898
+ child = visit(node.params.contents)
899
+
900
+ s(
901
+ child.type,
902
+ child.children,
903
+ smap_collection(
904
+ srange_length(node.params.start_char, 1),
905
+ srange_length(node.params.end_char, -1),
906
+ srange_node(node.params)
907
+ )
908
+ )
909
+ else
910
+ s(:args, [], smap_collection_bare(nil))
911
+ end
912
+
913
+ location =
914
+ if node.endless?
915
+ smap_method_definition(
916
+ srange_length(node.start_char, 3),
917
+ nil,
918
+ srange_node(node.name),
919
+ nil,
920
+ srange_find_between(
921
+ (node.params || node.name),
922
+ node.bodystmt,
923
+ "="
924
+ ),
925
+ srange_node(node)
926
+ )
927
+ else
928
+ smap_method_definition(
929
+ srange_length(node.start_char, 3),
930
+ nil,
931
+ srange_node(node.name),
932
+ srange_length(node.end_char, -3),
933
+ nil,
934
+ srange_node(node)
935
+ )
936
+ end
937
+
938
+ if node.target
939
+ target =
940
+ node.target.is_a?(Paren) ? node.target.contents : node.target
941
+
942
+ s(
943
+ :defs,
944
+ [visit(target), name, args, visit(node.bodystmt)],
945
+ smap_method_definition(
946
+ location.keyword,
947
+ srange_node(node.operator),
948
+ location.name,
949
+ location.end,
950
+ location.assignment,
951
+ location.expression
952
+ )
953
+ )
954
+ else
955
+ s(:def, [name, args, visit(node.bodystmt)], location)
956
+ end
957
+ end
958
+
959
+ # Visit a Defined node.
960
+ def visit_defined(node)
961
+ paren_range = (node.start_char + 8)...node.end_char
962
+ begin_token, end_token =
963
+ if buffer.source[paren_range].include?("(")
964
+ [
965
+ srange_find(paren_range.begin, paren_range.end, "("),
966
+ srange_length(node.end_char, -1)
967
+ ]
968
+ end
969
+
970
+ s(
971
+ :defined?,
972
+ [visit(node.value)],
973
+ smap_keyword(
974
+ srange_length(node.start_char, 8),
975
+ begin_token,
976
+ end_token,
977
+ srange_node(node)
978
+ )
979
+ )
980
+ end
981
+
982
+ # Visit a DynaSymbol node.
983
+ def visit_dyna_symbol(node)
984
+ location =
985
+ if node.quote
986
+ smap_collection(
987
+ srange_length(node.start_char, node.quote.length),
988
+ srange_length(node.end_char, -1),
989
+ srange_node(node)
990
+ )
991
+ else
992
+ smap_collection_bare(srange_node(node))
993
+ end
994
+
995
+ if node.parts.length == 1 && node.parts.first.is_a?(TStringContent)
996
+ s(:sym, ["\"#{node.parts.first.value}\"".undump.to_sym], location)
997
+ else
998
+ s(:dsym, visit_all(node.parts), location)
999
+ end
1000
+ end
1001
+
1002
+ # Visit an Else node.
1003
+ def visit_else(node)
1004
+ if node.statements.empty? && stack[-2].is_a?(Case)
1005
+ s(:empty_else, [], nil)
1006
+ else
1007
+ visit(node.statements)
1008
+ end
1009
+ end
1010
+
1011
+ # Visit an Elsif node.
1012
+ def visit_elsif(node)
1013
+ else_token =
1014
+ case node.consequent
1015
+ when Elsif
1016
+ srange_length(node.consequent.start_char, 5)
1017
+ when Else
1018
+ srange_length(node.consequent.start_char, 4)
1019
+ end
1020
+
1021
+ expression = srange(node.start_char, node.statements.end_char - 1)
1022
+
1023
+ s(
1024
+ :if,
1025
+ [
1026
+ visit(node.predicate),
1027
+ visit(node.statements),
1028
+ visit(node.consequent)
1029
+ ],
1030
+ smap_condition(
1031
+ srange_length(node.start_char, 5),
1032
+ nil,
1033
+ else_token,
1034
+ nil,
1035
+ expression
1036
+ )
1037
+ )
1038
+ end
1039
+
1040
+ # Visit an ENDBlock node.
1041
+ def visit_END(node)
1042
+ s(
1043
+ :postexe,
1044
+ [visit(node.statements)],
1045
+ smap_keyword(
1046
+ srange_length(node.start_char, 3),
1047
+ srange_find(node.start_char + 3, node.statements.start_char, "{"),
1048
+ srange_length(node.end_char, -1),
1049
+ srange_node(node)
1050
+ )
1051
+ )
1052
+ end
1053
+
1054
+ # Visit an Ensure node.
1055
+ def visit_ensure(node)
1056
+ start_char = node.start_char
1057
+ end_char =
1058
+ if node.statements.empty?
1059
+ start_char + 6
1060
+ else
1061
+ node.statements.body.last.end_char
1062
+ end
1063
+
1064
+ s(
1065
+ :ensure,
1066
+ [visit(node.statements)],
1067
+ smap_condition(
1068
+ srange_length(start_char, 6),
1069
+ nil,
1070
+ nil,
1071
+ nil,
1072
+ srange(start_char, end_char)
1073
+ )
1074
+ )
1075
+ end
1076
+
1077
+ # Visit a Field node.
1078
+ def visit_field(node)
1079
+ message =
1080
+ case stack[-2]
1081
+ when Assign, MLHS
1082
+ Ident.new(
1083
+ value: "#{node.name.value}=",
1084
+ location: node.name.location
1085
+ )
1086
+ else
1087
+ node.name
1088
+ end
1089
+
1090
+ visit_command_call(
1091
+ CommandCall.new(
1092
+ receiver: node.parent,
1093
+ operator: node.operator,
1094
+ message: message,
1095
+ arguments: nil,
1096
+ block: nil,
1097
+ location: node.location
1098
+ )
1099
+ )
1100
+ end
1101
+
1102
+ # Visit a FloatLiteral node.
1103
+ def visit_float(node)
1104
+ operator =
1105
+ if %w[+ -].include?(buffer.source[node.start_char])
1106
+ srange_length(node.start_char, 1)
1107
+ end
1108
+
1109
+ s(
1110
+ :float,
1111
+ [node.value.to_f],
1112
+ smap_operator(operator, srange_node(node))
1113
+ )
1114
+ end
1115
+
1116
+ # Visit a FndPtn node.
1117
+ def visit_fndptn(node)
1118
+ left, right =
1119
+ [node.left, node.right].map do |child|
1120
+ location =
1121
+ smap_operator(
1122
+ srange_length(child.start_char, 1),
1123
+ srange_node(child)
1124
+ )
1125
+
1126
+ if child.is_a?(VarField) && child.value.nil?
1127
+ s(:match_rest, [], location)
1128
+ else
1129
+ s(:match_rest, [visit(child)], location)
1130
+ end
1131
+ end
1132
+
1133
+ inner =
1134
+ s(
1135
+ :find_pattern,
1136
+ [left, *visit_all(node.values), right],
1137
+ smap_collection(
1138
+ srange_length(node.start_char, 1),
1139
+ srange_length(node.end_char, -1),
1140
+ srange_node(node)
1141
+ )
1142
+ )
1143
+
1144
+ if node.constant
1145
+ s(:const_pattern, [visit(node.constant), inner], nil)
1146
+ else
1147
+ inner
1148
+ end
1149
+ end
1150
+
1151
+ # Visit a For node.
1152
+ def visit_for(node)
1153
+ s(
1154
+ :for,
1155
+ [visit(node.index), visit(node.collection), visit(node.statements)],
1156
+ smap_for(
1157
+ srange_length(node.start_char, 3),
1158
+ srange_find_between(node.index, node.collection, "in"),
1159
+ srange_search_between(node.collection, node.statements, "do") ||
1160
+ srange_search_between(node.collection, node.statements, ";"),
1161
+ srange_length(node.end_char, -3),
1162
+ srange_node(node)
1163
+ )
1164
+ )
1165
+ end
1166
+
1167
+ # Visit a GVar node.
1168
+ def visit_gvar(node)
1169
+ s(
1170
+ :gvar,
1171
+ [node.value.to_sym],
1172
+ smap_variable(srange_node(node), srange_node(node))
1173
+ )
1174
+ end
1175
+
1176
+ # Visit a HashLiteral node.
1177
+ def visit_hash(node)
1178
+ s(
1179
+ :hash,
1180
+ visit_all(node.assocs),
1181
+ smap_collection(
1182
+ srange_length(node.start_char, 1),
1183
+ srange_length(node.end_char, -1),
1184
+ srange_node(node)
1185
+ )
1186
+ )
1187
+ end
1188
+
1189
+ # Visit a Heredoc node.
1190
+ def visit_heredoc(node)
1191
+ heredoc = HeredocBuilder.new(node)
1192
+
1193
+ # For each part of the heredoc, if it's a string content node, split
1194
+ # it into multiple string content nodes, one for each line. Otherwise,
1195
+ # visit the node as normal.
1196
+ node.parts.each do |part|
1197
+ if part.is_a?(TStringContent) && part.value.count("\n") > 1
1198
+ index = part.start_char
1199
+ lines = part.value.split("\n")
1200
+
1201
+ lines.each do |line|
1202
+ length = line.length + 1
1203
+ location = smap_collection_bare(srange_length(index, length))
1204
+
1205
+ heredoc << s(:str, ["#{line}\n"], location)
1206
+ index += length
1207
+ end
1208
+ else
1209
+ heredoc << visit(part)
1210
+ end
1211
+ end
1212
+
1213
+ # Now that we have all of the pieces on the heredoc, we can trim it if
1214
+ # it is a heredoc that supports trimming (i.e., it has a ~ on the
1215
+ # declaration).
1216
+ heredoc.trim!
1217
+
1218
+ # Generate the location for the heredoc, which goes from the
1219
+ # declaration to the ending delimiter.
1220
+ location =
1221
+ smap_heredoc(
1222
+ srange_node(node.beginning),
1223
+ srange(
1224
+ if node.parts.empty?
1225
+ node.beginning.end_char + 1
1226
+ else
1227
+ node.parts.first.start_char
1228
+ end,
1229
+ node.ending.start_char
1230
+ ),
1231
+ srange(node.ending.start_char, node.ending.end_char - 1)
1232
+ )
1233
+
1234
+ # Finally, decide which kind of heredoc node to generate based on its
1235
+ # declaration and contents.
1236
+ if node.beginning.value.match?(/`\w+`\z/)
1237
+ s(:xstr, heredoc.segments, location)
1238
+ elsif heredoc.segments.length == 1
1239
+ segment = heredoc.segments.first
1240
+ s(segment.type, segment.children, location)
1241
+ else
1242
+ s(:dstr, heredoc.segments, location)
1243
+ end
1244
+ end
1245
+
1246
+ # Visit a HshPtn node.
1247
+ def visit_hshptn(node)
1248
+ children =
1249
+ node.keywords.map do |(keyword, value)|
1250
+ next s(:pair, [visit(keyword), visit(value)], nil) if value
1251
+
1252
+ case keyword
1253
+ when DynaSymbol
1254
+ raise if keyword.parts.length > 1
1255
+ s(:match_var, [keyword.parts.first.value.to_sym], nil)
1256
+ when Label
1257
+ s(:match_var, [keyword.value.chomp(":").to_sym], nil)
1258
+ end
1259
+ end
1260
+
1261
+ if node.keyword_rest.is_a?(VarField)
1262
+ children << if node.keyword_rest.value.nil?
1263
+ s(:match_rest, [], nil)
1264
+ elsif node.keyword_rest.value == :nil
1265
+ s(:match_nil_pattern, [], nil)
1266
+ else
1267
+ s(:match_rest, [visit(node.keyword_rest)], nil)
1268
+ end
1269
+ end
1270
+
1271
+ inner = s(:hash_pattern, children, nil)
1272
+ if node.constant
1273
+ s(:const_pattern, [visit(node.constant), inner], nil)
1274
+ else
1275
+ inner
1276
+ end
1277
+ end
1278
+
1279
+ # Visit an Ident node.
1280
+ def visit_ident(node)
1281
+ s(
1282
+ :lvar,
1283
+ [node.value.to_sym],
1284
+ smap_variable(srange_node(node), srange_node(node))
1285
+ )
1286
+ end
1287
+
1288
+ # Visit an IfNode node.
1289
+ def visit_if(node)
1290
+ predicate =
1291
+ case node.predicate
1292
+ when RangeNode
1293
+ type =
1294
+ node.predicate.operator.value == ".." ? :iflipflop : :eflipflop
1295
+ s(type, visit(node.predicate).children, nil)
1296
+ when RegexpLiteral
1297
+ s(:match_current_line, [visit(node.predicate)], nil)
1298
+ when Unary
1299
+ if node.predicate.operator.value == "!" &&
1300
+ node.predicate.statement.is_a?(RegexpLiteral)
1301
+ s(
1302
+ :send,
1303
+ [
1304
+ s(:match_current_line, [visit(node.predicate.statement)]),
1305
+ :!
1306
+ ],
1307
+ nil
1308
+ )
1309
+ else
1310
+ visit(node.predicate)
1311
+ end
1312
+ else
1313
+ visit(node.predicate)
1314
+ end
1315
+
1316
+ s(
1317
+ :if,
1318
+ [predicate, visit(node.statements), visit(node.consequent)],
1319
+ if node.modifier?
1320
+ smap_keyword_bare(
1321
+ srange_find_between(node.statements, node.predicate, "if"),
1322
+ srange_node(node)
1323
+ )
1324
+ else
1325
+ begin_start = node.predicate.end_char
1326
+ begin_end =
1327
+ if node.statements.empty?
1328
+ node.statements.end_char
1329
+ else
1330
+ node.statements.body.first.start_char
1331
+ end
1332
+
1333
+ begin_token =
1334
+ if buffer.source[begin_start...begin_end].include?("then")
1335
+ srange_find(begin_start, begin_end, "then")
1336
+ elsif buffer.source[begin_start...begin_end].include?(";")
1337
+ srange_find(begin_start, begin_end, ";")
1338
+ end
1339
+
1340
+ else_token =
1341
+ case node.consequent
1342
+ when Elsif
1343
+ srange_length(node.consequent.start_char, 5)
1344
+ when Else
1345
+ srange_length(node.consequent.start_char, 4)
1346
+ end
1347
+
1348
+ smap_condition(
1349
+ srange_length(node.start_char, 2),
1350
+ begin_token,
1351
+ else_token,
1352
+ srange_length(node.end_char, -3),
1353
+ srange_node(node)
1354
+ )
1355
+ end
1356
+ )
1357
+ end
1358
+
1359
+ # Visit an IfOp node.
1360
+ def visit_if_op(node)
1361
+ s(
1362
+ :if,
1363
+ [visit(node.predicate), visit(node.truthy), visit(node.falsy)],
1364
+ smap_ternary(
1365
+ srange_find_between(node.predicate, node.truthy, "?"),
1366
+ srange_find_between(node.truthy, node.falsy, ":"),
1367
+ srange_node(node)
1368
+ )
1369
+ )
1370
+ end
1371
+
1372
+ # Visit an Imaginary node.
1373
+ def visit_imaginary(node)
1374
+ s(
1375
+ :complex,
1376
+ [
1377
+ # We have to do an eval here in order to get the value in case
1378
+ # it's something like 42ri. to_c will not give the right value in
1379
+ # that case. Maybe there's an API for this but I can't find it.
1380
+ eval(node.value)
1381
+ ],
1382
+ smap_operator(nil, srange_node(node))
1383
+ )
1384
+ end
1385
+
1386
+ # Visit an In node.
1387
+ def visit_in(node)
1388
+ case node.pattern
1389
+ when IfNode
1390
+ s(
1391
+ :in_pattern,
1392
+ [
1393
+ visit(node.pattern.statements),
1394
+ s(:if_guard, [visit(node.pattern.predicate)], nil),
1395
+ visit(node.statements)
1396
+ ],
1397
+ nil
1398
+ )
1399
+ when UnlessNode
1400
+ s(
1401
+ :in_pattern,
1402
+ [
1403
+ visit(node.pattern.statements),
1404
+ s(:unless_guard, [visit(node.pattern.predicate)], nil),
1405
+ visit(node.statements)
1406
+ ],
1407
+ nil
1408
+ )
1409
+ else
1410
+ begin_token =
1411
+ srange_search_between(node.pattern, node.statements, "then")
1412
+
1413
+ end_char =
1414
+ if begin_token || node.statements.empty?
1415
+ node.statements.end_char - 1
1416
+ else
1417
+ node.statements.body.last.start_char
1418
+ end
1419
+
1420
+ s(
1421
+ :in_pattern,
1422
+ [visit(node.pattern), nil, visit(node.statements)],
1423
+ smap_keyword(
1424
+ srange_length(node.start_char, 2),
1425
+ begin_token,
1426
+ nil,
1427
+ srange(node.start_char, end_char)
1428
+ )
1429
+ )
1430
+ end
1431
+ end
1432
+
1433
+ # Visit an Int node.
1434
+ def visit_int(node)
1435
+ operator =
1436
+ if %w[+ -].include?(buffer.source[node.start_char])
1437
+ srange_length(node.start_char, 1)
1438
+ end
1439
+
1440
+ s(:int, [node.value.to_i], smap_operator(operator, srange_node(node)))
1441
+ end
1442
+
1443
+ # Visit an IVar node.
1444
+ def visit_ivar(node)
1445
+ s(
1446
+ :ivar,
1447
+ [node.value.to_sym],
1448
+ smap_variable(srange_node(node), srange_node(node))
1449
+ )
1450
+ end
1451
+
1452
+ # Visit a Kw node.
1453
+ def visit_kw(node)
1454
+ location = smap(srange_node(node))
1455
+
1456
+ case node.value
1457
+ when "__FILE__"
1458
+ s(:str, [buffer.name], location)
1459
+ when "__LINE__"
1460
+ s(
1461
+ :int,
1462
+ [node.location.start_line + buffer.first_line - 1],
1463
+ location
1464
+ )
1465
+ when "__ENCODING__"
1466
+ if ::Parser::Builders::Default.emit_encoding
1467
+ s(:__ENCODING__, [], location)
1468
+ else
1469
+ s(:const, [s(:const, [nil, :Encoding], nil), :UTF_8], location)
1470
+ end
1471
+ else
1472
+ s(node.value.to_sym, [], location)
1473
+ end
1474
+ end
1475
+
1476
+ # Visit a KwRestParam node.
1477
+ def visit_kwrest_param(node)
1478
+ if node.name.nil?
1479
+ s(:kwrestarg, [], smap_variable(nil, srange_node(node)))
1480
+ else
1481
+ s(
1482
+ :kwrestarg,
1483
+ [node.name.value.to_sym],
1484
+ smap_variable(srange_node(node.name), srange_node(node))
1485
+ )
1486
+ end
1487
+ end
1488
+
1489
+ # Visit a Label node.
1490
+ def visit_label(node)
1491
+ s(
1492
+ :sym,
1493
+ [node.value.chomp(":").to_sym],
1494
+ smap_collection_bare(srange(node.start_char, node.end_char - 1))
1495
+ )
1496
+ end
1497
+
1498
+ # Visit a Lambda node.
1499
+ def visit_lambda(node)
1500
+ args =
1501
+ node.params.is_a?(LambdaVar) ? node.params : node.params.contents
1502
+ args_node = visit(args)
1503
+
1504
+ type = :block
1505
+ if args.empty? && (maximum = num_block_type(node.statements))
1506
+ type = :numblock
1507
+ args_node = maximum
1508
+ end
1509
+
1510
+ begin_token, end_token =
1511
+ if (
1512
+ srange =
1513
+ srange_search_between(node.params, node.statements, "{")
1514
+ )
1515
+ [srange, srange_length(node.end_char, -1)]
1516
+ else
1517
+ [
1518
+ srange_find_between(node.params, node.statements, "do"),
1519
+ srange_length(node.end_char, -3)
1520
+ ]
1521
+ end
1522
+
1523
+ selector = srange_length(node.start_char, 2)
1524
+
1525
+ s(
1526
+ type,
1527
+ [
1528
+ if ::Parser::Builders::Default.emit_lambda
1529
+ s(:lambda, [], smap(selector))
1530
+ else
1531
+ s(:send, [nil, :lambda], smap_send_bare(selector, selector))
1532
+ end,
1533
+ args_node,
1534
+ visit(node.statements)
1535
+ ],
1536
+ smap_collection(begin_token, end_token, srange_node(node))
1537
+ )
1538
+ end
1539
+
1540
+ # Visit a LambdaVar node.
1541
+ def visit_lambda_var(node)
1542
+ shadowargs =
1543
+ node.locals.map do |local|
1544
+ s(
1545
+ :shadowarg,
1546
+ [local.value.to_sym],
1547
+ smap_variable(srange_node(local), srange_node(local))
1548
+ )
1549
+ end
1550
+
1551
+ location =
1552
+ if node.start_char == node.end_char
1553
+ smap_collection_bare(nil)
1554
+ else
1555
+ smap_collection(
1556
+ srange_length(node.start_char, 1),
1557
+ srange_length(node.end_char, -1),
1558
+ srange_node(node)
1559
+ )
1560
+ end
1561
+
1562
+ s(:args, visit(node.params).children + shadowargs, location)
1563
+ end
1564
+
1565
+ # Visit an MAssign node.
1566
+ def visit_massign(node)
1567
+ s(
1568
+ :masgn,
1569
+ [visit(node.target), visit(node.value)],
1570
+ smap_operator(
1571
+ srange_find_between(node.target, node.value, "="),
1572
+ srange_node(node)
1573
+ )
1574
+ )
1575
+ end
1576
+
1577
+ # Visit a MethodAddBlock node.
1578
+ def visit_method_add_block(node)
1579
+ case node.call
1580
+ when Break, Next, ReturnNode
1581
+ type, arguments = block_children(node.block)
1582
+ call = visit(node.call)
1583
+
1584
+ s(
1585
+ call.type,
1586
+ [
1587
+ s(
1588
+ type,
1589
+ [*call.children, arguments, visit(node.block.bodystmt)],
1590
+ nil
1591
+ )
1592
+ ],
1593
+ nil
1594
+ )
1595
+ when ARef, Super, ZSuper
1596
+ type, arguments = block_children(node.block)
1597
+
1598
+ s(
1599
+ type,
1600
+ [visit(node.call), arguments, visit(node.block.bodystmt)],
1601
+ nil
1602
+ )
1603
+ else
1604
+ visit_command_call(
1605
+ CommandCall.new(
1606
+ receiver: node.call.receiver,
1607
+ operator: node.call.operator,
1608
+ message: node.call.message,
1609
+ arguments: node.call.arguments,
1610
+ block: node.block,
1611
+ location: node.location
1612
+ )
1613
+ )
1614
+ end
1615
+ end
1616
+
1617
+ # Visit an MLHS node.
1618
+ def visit_mlhs(node)
1619
+ s(
1620
+ :mlhs,
1621
+ node.parts.map do |part|
1622
+ if part.is_a?(Ident)
1623
+ s(
1624
+ :arg,
1625
+ [part.value.to_sym],
1626
+ smap_variable(srange_node(part), srange_node(part))
1627
+ )
1628
+ else
1629
+ visit(part)
1630
+ end
1631
+ end,
1632
+ smap_collection_bare(srange_node(node))
1633
+ )
1634
+ end
1635
+
1636
+ # Visit an MLHSParen node.
1637
+ def visit_mlhs_paren(node)
1638
+ child = visit(node.contents)
1639
+
1640
+ s(
1641
+ child.type,
1642
+ child.children,
1643
+ smap_collection(
1644
+ srange_length(node.start_char, 1),
1645
+ srange_length(node.end_char, -1),
1646
+ srange_node(node)
1647
+ )
1648
+ )
1649
+ end
1650
+
1651
+ # Visit a ModuleDeclaration node.
1652
+ def visit_module(node)
1653
+ s(
1654
+ :module,
1655
+ [visit(node.constant), visit(node.bodystmt)],
1656
+ smap_definition(
1657
+ srange_length(node.start_char, 6),
1658
+ nil,
1659
+ srange_node(node.constant),
1660
+ srange_length(node.end_char, -3)
1661
+ ).with_expression(srange_node(node))
1662
+ )
1663
+ end
1664
+
1665
+ # Visit an MRHS node.
1666
+ def visit_mrhs(node)
1667
+ visit_array(
1668
+ ArrayLiteral.new(
1669
+ lbracket: nil,
1670
+ contents: Args.new(parts: node.parts, location: node.location),
1671
+ location: node.location
1672
+ )
1673
+ )
1674
+ end
1675
+
1676
+ # Visit a Next node.
1677
+ def visit_next(node)
1678
+ s(
1679
+ :next,
1680
+ visit_all(node.arguments.parts),
1681
+ smap_keyword_bare(
1682
+ srange_length(node.start_char, 4),
1683
+ srange_node(node)
1684
+ )
1685
+ )
1686
+ end
1687
+
1688
+ # Visit a Not node.
1689
+ def visit_not(node)
1690
+ if node.statement.nil?
1691
+ begin_token = srange_find(node.start_char, nil, "(")
1692
+ end_token = srange_find(node.start_char, nil, ")")
1693
+
1694
+ s(
1695
+ :send,
1696
+ [
1697
+ s(
1698
+ :begin,
1699
+ [],
1700
+ smap_collection(
1701
+ begin_token,
1702
+ end_token,
1703
+ begin_token.join(end_token)
1704
+ )
1705
+ ),
1706
+ :!
1707
+ ],
1708
+ smap_send_bare(
1709
+ srange_length(node.start_char, 3),
1710
+ srange_node(node)
1711
+ )
1712
+ )
1713
+ else
1714
+ begin_token, end_token =
1715
+ if node.parentheses?
1716
+ [
1717
+ srange_find(
1718
+ node.start_char + 3,
1719
+ node.statement.start_char,
1720
+ "("
1721
+ ),
1722
+ srange_length(node.end_char, -1)
1723
+ ]
1724
+ end
1725
+
1726
+ s(
1727
+ :send,
1728
+ [visit(node.statement), :!],
1729
+ smap_send(
1730
+ nil,
1731
+ srange_length(node.start_char, 3),
1732
+ begin_token,
1733
+ end_token,
1734
+ srange_node(node)
1735
+ )
1736
+ )
1737
+ end
1738
+ end
1739
+
1740
+ # Visit an OpAssign node.
1741
+ def visit_opassign(node)
1742
+ target = visit(node.target)
1743
+ location =
1744
+ target
1745
+ .location
1746
+ .with_expression(srange_node(node))
1747
+ .with_operator(srange_node(node.operator))
1748
+
1749
+ case node.operator.value
1750
+ when "||="
1751
+ s(:or_asgn, [target, visit(node.value)], location)
1752
+ when "&&="
1753
+ s(:and_asgn, [target, visit(node.value)], location)
1754
+ else
1755
+ s(
1756
+ :op_asgn,
1757
+ [
1758
+ target,
1759
+ node.operator.value.chomp("=").to_sym,
1760
+ visit(node.value)
1761
+ ],
1762
+ location
1763
+ )
1764
+ end
1765
+ end
1766
+
1767
+ # Visit a Params node.
1768
+ def visit_params(node)
1769
+ children = []
1770
+
1771
+ children +=
1772
+ node.requireds.map do |required|
1773
+ case required
1774
+ when MLHSParen
1775
+ visit(required)
1776
+ else
1777
+ s(
1778
+ :arg,
1779
+ [required.value.to_sym],
1780
+ smap_variable(srange_node(required), srange_node(required))
1781
+ )
1782
+ end
1783
+ end
1784
+
1785
+ children +=
1786
+ node.optionals.map do |(name, value)|
1787
+ s(
1788
+ :optarg,
1789
+ [name.value.to_sym, visit(value)],
1790
+ smap_variable(
1791
+ srange_node(name),
1792
+ srange_node(name).join(srange_node(value))
1793
+ ).with_operator(srange_find_between(name, value, "="))
1794
+ )
1795
+ end
1796
+
1797
+ if node.rest && !node.rest.is_a?(ExcessedComma)
1798
+ children << visit(node.rest)
1799
+ end
1800
+
1801
+ children +=
1802
+ node.posts.map do |post|
1803
+ s(
1804
+ :arg,
1805
+ [post.value.to_sym],
1806
+ smap_variable(srange_node(post), srange_node(post))
1807
+ )
1808
+ end
1809
+
1810
+ children +=
1811
+ node.keywords.map do |(name, value)|
1812
+ key = name.value.chomp(":").to_sym
1813
+
1814
+ if value
1815
+ s(
1816
+ :kwoptarg,
1817
+ [key, visit(value)],
1818
+ smap_variable(
1819
+ srange(name.start_char, name.end_char - 1),
1820
+ srange_node(name).join(srange_node(value))
1821
+ )
1822
+ )
1823
+ else
1824
+ s(
1825
+ :kwarg,
1826
+ [key],
1827
+ smap_variable(
1828
+ srange(name.start_char, name.end_char - 1),
1829
+ srange_node(name)
1830
+ )
1831
+ )
1832
+ end
1833
+ end
1834
+
1835
+ case node.keyword_rest
1836
+ when nil, ArgsForward
1837
+ # do nothing
1838
+ when :nil
1839
+ children << s(
1840
+ :kwnilarg,
1841
+ [],
1842
+ smap_variable(srange_length(node.end_char, -3), srange_node(node))
1843
+ )
1844
+ else
1845
+ children << visit(node.keyword_rest)
1846
+ end
1847
+
1848
+ children << visit(node.block) if node.block
1849
+
1850
+ if node.keyword_rest.is_a?(ArgsForward)
1851
+ location = smap(srange_node(node.keyword_rest))
1852
+
1853
+ # If there are no other arguments and we have the emit_forward_arg
1854
+ # option enabled, then the entire argument list is represented by a
1855
+ # single forward_args node.
1856
+ if children.empty? && !::Parser::Builders::Default.emit_forward_arg
1857
+ return s(:forward_args, [], location)
1858
+ end
1859
+
1860
+ # Otherwise, we need to insert a forward_arg node into the list of
1861
+ # parameters before any keyword rest or block parameters.
1862
+ index =
1863
+ node.requireds.length + node.optionals.length +
1864
+ node.keywords.length
1865
+ children.insert(index, s(:forward_arg, [], location))
1866
+ end
1867
+
1868
+ location =
1869
+ unless children.empty?
1870
+ first = children.first.location.expression
1871
+ last = children.last.location.expression
1872
+ smap_collection_bare(first.join(last))
1873
+ end
1874
+
1875
+ s(:args, children, location)
1876
+ end
1877
+
1878
+ # Visit a Paren node.
1879
+ def visit_paren(node)
1880
+ location =
1881
+ smap_collection(
1882
+ srange_length(node.start_char, 1),
1883
+ srange_length(node.end_char, -1),
1884
+ srange_node(node)
1885
+ )
1886
+
1887
+ if node.contents.nil? ||
1888
+ (node.contents.is_a?(Statements) && node.contents.empty?)
1889
+ s(:begin, [], location)
1890
+ else
1891
+ child = visit(node.contents)
1892
+ child.type == :begin ? child : s(:begin, [child], location)
1893
+ end
1894
+ end
1895
+
1896
+ # Visit a PinnedBegin node.
1897
+ def visit_pinned_begin(node)
1898
+ s(
1899
+ :pin,
1900
+ [
1901
+ s(
1902
+ :begin,
1903
+ [visit(node.statement)],
1904
+ smap_collection(
1905
+ srange_length(node.start_char + 1, 1),
1906
+ srange_length(node.end_char, -1),
1907
+ srange(node.start_char + 1, node.end_char)
1908
+ )
1909
+ )
1910
+ ],
1911
+ smap_send_bare(srange_length(node.start_char, 1), srange_node(node))
1912
+ )
1913
+ end
1914
+
1915
+ # Visit a PinnedVarRef node.
1916
+ def visit_pinned_var_ref(node)
1917
+ s(
1918
+ :pin,
1919
+ [visit(node.value)],
1920
+ smap_send_bare(srange_length(node.start_char, 1), srange_node(node))
1921
+ )
1922
+ end
1923
+
1924
+ # Visit a Program node.
1925
+ def visit_program(node)
1926
+ visit(node.statements)
1927
+ end
1928
+
1929
+ # Visit a QSymbols node.
1930
+ def visit_qsymbols(node)
1931
+ parts =
1932
+ node.elements.map do |element|
1933
+ SymbolLiteral.new(value: element, location: element.location)
1934
+ end
1935
+
1936
+ visit_array(
1937
+ ArrayLiteral.new(
1938
+ lbracket: node.beginning,
1939
+ contents: Args.new(parts: parts, location: node.location),
1940
+ location: node.location
1941
+ )
1942
+ )
1943
+ end
1944
+
1945
+ # Visit a QWords node.
1946
+ def visit_qwords(node)
1947
+ visit_array(
1948
+ ArrayLiteral.new(
1949
+ lbracket: node.beginning,
1950
+ contents: Args.new(parts: node.elements, location: node.location),
1951
+ location: node.location
1952
+ )
1953
+ )
1954
+ end
1955
+
1956
+ # Visit a RangeNode node.
1957
+ def visit_range(node)
1958
+ s(
1959
+ node.operator.value == ".." ? :irange : :erange,
1960
+ [visit(node.left), visit(node.right)],
1961
+ smap_operator(srange_node(node.operator), srange_node(node))
1962
+ )
1963
+ end
1964
+
1965
+ # Visit an RAssign node.
1966
+ def visit_rassign(node)
1967
+ s(
1968
+ node.operator.value == "=>" ? :match_pattern : :match_pattern_p,
1969
+ [visit(node.value), visit(node.pattern)],
1970
+ smap_operator(srange_node(node.operator), srange_node(node))
1971
+ )
1972
+ end
1973
+
1974
+ # Visit a Rational node.
1975
+ def visit_rational(node)
1976
+ s(:rational, [node.value.to_r], smap_operator(nil, srange_node(node)))
1977
+ end
1978
+
1979
+ # Visit a Redo node.
1980
+ def visit_redo(node)
1981
+ s(:redo, [], smap_keyword_bare(srange_node(node), srange_node(node)))
1982
+ end
1983
+
1984
+ # Visit a RegexpLiteral node.
1985
+ def visit_regexp_literal(node)
1986
+ s(
1987
+ :regexp,
1988
+ visit_all(node.parts).push(
1989
+ s(
1990
+ :regopt,
1991
+ node.ending.scan(/[a-z]/).sort.map(&:to_sym),
1992
+ smap(srange_length(node.end_char, -(node.ending.length - 1)))
1993
+ )
1994
+ ),
1995
+ smap_collection(
1996
+ srange_length(node.start_char, node.beginning.length),
1997
+ srange_length(node.end_char - node.ending.length, 1),
1998
+ srange_node(node)
1999
+ )
2000
+ )
2001
+ end
2002
+
2003
+ # Visit a Rescue node.
2004
+ def visit_rescue(node)
2005
+ # In the parser gem, there is a separation between the rescue node and
2006
+ # the rescue body. They have different bounds, so we have to calculate
2007
+ # those here.
2008
+ start_char = node.start_char
2009
+
2010
+ body_end_char =
2011
+ if node.statements.empty?
2012
+ start_char + 6
2013
+ else
2014
+ node.statements.body.last.end_char
2015
+ end
2016
+
2017
+ end_char =
2018
+ if node.consequent
2019
+ end_node = node.consequent
2020
+ end_node = end_node.consequent while end_node.consequent
2021
+
2022
+ if end_node.statements.empty?
2023
+ start_char + 6
2024
+ else
2025
+ end_node.statements.body.last.end_char
2026
+ end
2027
+ else
2028
+ body_end_char
2029
+ end
2030
+
2031
+ # These locations are reused for multiple children.
2032
+ keyword = srange_length(start_char, 6)
2033
+ body_expression = srange(start_char, body_end_char)
2034
+ expression = srange(start_char, end_char)
2035
+
2036
+ exceptions =
2037
+ case node.exception&.exceptions
2038
+ when nil
2039
+ nil
2040
+ when MRHS
2041
+ visit_array(
2042
+ ArrayLiteral.new(
2043
+ lbracket: nil,
2044
+ contents:
2045
+ Args.new(
2046
+ parts: node.exception.exceptions.parts,
2047
+ location: node.exception.exceptions.location
2048
+ ),
2049
+ location: node.exception.exceptions.location
2050
+ )
2051
+ )
2052
+ else
2053
+ visit_array(
2054
+ ArrayLiteral.new(
2055
+ lbracket: nil,
2056
+ contents:
2057
+ Args.new(
2058
+ parts: [node.exception.exceptions],
2059
+ location: node.exception.exceptions.location
2060
+ ),
2061
+ location: node.exception.exceptions.location
2062
+ )
2063
+ )
2064
+ end
2065
+
2066
+ resbody =
2067
+ if node.exception.nil?
2068
+ s(
2069
+ :resbody,
2070
+ [nil, nil, visit(node.statements)],
2071
+ smap_rescue_body(keyword, nil, nil, body_expression)
2072
+ )
2073
+ elsif node.exception.variable.nil?
2074
+ s(
2075
+ :resbody,
2076
+ [exceptions, nil, visit(node.statements)],
2077
+ smap_rescue_body(keyword, nil, nil, body_expression)
2078
+ )
2079
+ else
2080
+ s(
2081
+ :resbody,
2082
+ [
2083
+ exceptions,
2084
+ visit(node.exception.variable),
2085
+ visit(node.statements)
2086
+ ],
2087
+ smap_rescue_body(
2088
+ keyword,
2089
+ srange_find(
2090
+ node.start_char + 6,
2091
+ node.exception.variable.start_char,
2092
+ "=>"
2093
+ ),
2094
+ nil,
2095
+ body_expression
2096
+ )
2097
+ )
2098
+ end
2099
+
2100
+ children = [resbody]
2101
+ if node.consequent
2102
+ children += visit(node.consequent).children
2103
+ else
2104
+ children << nil
2105
+ end
2106
+
2107
+ s(:rescue, children, smap_condition_bare(expression))
2108
+ end
2109
+
2110
+ # Visit a RescueMod node.
2111
+ def visit_rescue_mod(node)
2112
+ keyword = srange_find_between(node.statement, node.value, "rescue")
2113
+
2114
+ s(
2115
+ :rescue,
2116
+ [
2117
+ visit(node.statement),
2118
+ s(
2119
+ :resbody,
2120
+ [nil, nil, visit(node.value)],
2121
+ smap_rescue_body(
2122
+ keyword,
2123
+ nil,
2124
+ nil,
2125
+ keyword.join(srange_node(node.value))
2126
+ )
2127
+ ),
2128
+ nil
2129
+ ],
2130
+ smap_condition_bare(srange_node(node))
2131
+ )
2132
+ end
2133
+
2134
+ # Visit a RestParam node.
2135
+ def visit_rest_param(node)
2136
+ if node.name
2137
+ s(
2138
+ :restarg,
2139
+ [node.name.value.to_sym],
2140
+ smap_variable(srange_node(node.name), srange_node(node))
2141
+ )
2142
+ else
2143
+ s(:restarg, [], smap_variable(nil, srange_node(node)))
2144
+ end
2145
+ end
2146
+
2147
+ # Visit a Retry node.
2148
+ def visit_retry(node)
2149
+ s(:retry, [], smap_keyword_bare(srange_node(node), srange_node(node)))
2150
+ end
2151
+
2152
+ # Visit a ReturnNode node.
2153
+ def visit_return(node)
2154
+ s(
2155
+ :return,
2156
+ node.arguments ? visit_all(node.arguments.parts) : [],
2157
+ smap_keyword_bare(
2158
+ srange_length(node.start_char, 6),
2159
+ srange_node(node)
2160
+ )
2161
+ )
2162
+ end
2163
+
2164
+ # Visit an SClass node.
2165
+ def visit_sclass(node)
2166
+ s(
2167
+ :sclass,
2168
+ [visit(node.target), visit(node.bodystmt)],
2169
+ smap_definition(
2170
+ srange_length(node.start_char, 5),
2171
+ srange_find(node.start_char + 5, node.target.start_char, "<<"),
2172
+ nil,
2173
+ srange_length(node.end_char, -3)
2174
+ ).with_expression(srange_node(node))
2175
+ )
2176
+ end
2177
+
2178
+ # Visit a Statements node.
2179
+ def visit_statements(node)
2180
+ children =
2181
+ node.body.reject do |child|
2182
+ child.is_a?(Comment) || child.is_a?(EmbDoc) ||
2183
+ child.is_a?(EndContent) || child.is_a?(VoidStmt)
2184
+ end
2185
+
2186
+ case children.length
2187
+ when 0
2188
+ nil
2189
+ when 1
2190
+ visit(children.first)
2191
+ else
2192
+ s(
2193
+ :begin,
2194
+ visit_all(children),
2195
+ smap_collection_bare(
2196
+ srange(children.first.start_char, children.last.end_char)
2197
+ )
2198
+ )
2199
+ end
2200
+ end
2201
+
2202
+ # Visit a StringConcat node.
2203
+ def visit_string_concat(node)
2204
+ s(
2205
+ :dstr,
2206
+ [visit(node.left), visit(node.right)],
2207
+ smap_collection_bare(srange_node(node))
2208
+ )
2209
+ end
2210
+
2211
+ # Visit a StringDVar node.
2212
+ def visit_string_dvar(node)
2213
+ visit(node.variable)
2214
+ end
2215
+
2216
+ # Visit a StringEmbExpr node.
2217
+ def visit_string_embexpr(node)
2218
+ s(
2219
+ :begin,
2220
+ visit(node.statements).then { |child| child ? [child] : [] },
2221
+ smap_collection(
2222
+ srange_length(node.start_char, 2),
2223
+ srange_length(node.end_char, -1),
2224
+ srange_node(node)
2225
+ )
2226
+ )
2227
+ end
2228
+
2229
+ # Visit a StringLiteral node.
2230
+ def visit_string_literal(node)
2231
+ location =
2232
+ if node.quote
2233
+ smap_collection(
2234
+ srange_length(node.start_char, node.quote.length),
2235
+ srange_length(node.end_char, -1),
2236
+ srange_node(node)
2237
+ )
2238
+ else
2239
+ smap_collection_bare(srange_node(node))
2240
+ end
2241
+
2242
+ if node.parts.empty?
2243
+ s(:str, [""], location)
2244
+ elsif node.parts.length == 1 && node.parts.first.is_a?(TStringContent)
2245
+ child = visit(node.parts.first)
2246
+ s(child.type, child.children, location)
2247
+ else
2248
+ s(:dstr, visit_all(node.parts), location)
2249
+ end
2250
+ end
2251
+
2252
+ # Visit a Super node.
2253
+ def visit_super(node)
2254
+ if node.arguments.is_a?(Args)
2255
+ s(
2256
+ :super,
2257
+ visit_all(node.arguments.parts),
2258
+ smap_keyword_bare(
2259
+ srange_length(node.start_char, 5),
2260
+ srange_node(node)
2261
+ )
2262
+ )
2263
+ else
2264
+ case node.arguments.arguments
2265
+ when nil
2266
+ s(
2267
+ :super,
2268
+ [],
2269
+ smap_keyword(
2270
+ srange_length(node.start_char, 5),
2271
+ srange_find(node.start_char + 5, node.end_char, "("),
2272
+ srange_length(node.end_char, -1),
2273
+ srange_node(node)
2274
+ )
2275
+ )
2276
+ when ArgsForward
2277
+ s(:super, [visit(node.arguments.arguments)], nil)
2278
+ else
2279
+ s(
2280
+ :super,
2281
+ visit_all(node.arguments.arguments.parts),
2282
+ smap_keyword(
2283
+ srange_length(node.start_char, 5),
2284
+ srange_find(node.start_char + 5, node.end_char, "("),
2285
+ srange_length(node.end_char, -1),
2286
+ srange_node(node)
2287
+ )
2288
+ )
2289
+ end
2290
+ end
2291
+ end
2292
+
2293
+ # Visit a SymbolLiteral node.
2294
+ def visit_symbol_literal(node)
2295
+ begin_token =
2296
+ if buffer.source[node.start_char] == ":"
2297
+ srange_length(node.start_char, 1)
2298
+ end
2299
+
2300
+ s(
2301
+ :sym,
2302
+ [node.value.value.to_sym],
2303
+ smap_collection(begin_token, nil, srange_node(node))
2304
+ )
2305
+ end
2306
+
2307
+ # Visit a Symbols node.
2308
+ def visit_symbols(node)
2309
+ parts =
2310
+ node.elements.map do |element|
2311
+ part = element.parts.first
2312
+
2313
+ if element.parts.length == 1 && part.is_a?(TStringContent)
2314
+ SymbolLiteral.new(value: part, location: part.location)
2315
+ else
2316
+ DynaSymbol.new(
2317
+ parts: element.parts,
2318
+ quote: nil,
2319
+ location: element.location
2320
+ )
2321
+ end
2322
+ end
2323
+
2324
+ visit_array(
2325
+ ArrayLiteral.new(
2326
+ lbracket: node.beginning,
2327
+ contents: Args.new(parts: parts, location: node.location),
2328
+ location: node.location
2329
+ )
2330
+ )
2331
+ end
2332
+
2333
+ # Visit a TopConstField node.
2334
+ def visit_top_const_field(node)
2335
+ s(
2336
+ :casgn,
2337
+ [
2338
+ s(:cbase, [], smap(srange_length(node.start_char, 2))),
2339
+ node.constant.value.to_sym
2340
+ ],
2341
+ smap_constant(
2342
+ srange_length(node.start_char, 2),
2343
+ srange_node(node.constant),
2344
+ srange_node(node)
2345
+ )
2346
+ )
2347
+ end
2348
+
2349
+ # Visit a TopConstRef node.
2350
+ def visit_top_const_ref(node)
2351
+ s(
2352
+ :const,
2353
+ [
2354
+ s(:cbase, [], smap(srange_length(node.start_char, 2))),
2355
+ node.constant.value.to_sym
2356
+ ],
2357
+ smap_constant(
2358
+ srange_length(node.start_char, 2),
2359
+ srange_node(node.constant),
2360
+ srange_node(node)
2361
+ )
2362
+ )
2363
+ end
2364
+
2365
+ # Visit a TStringContent node.
2366
+ def visit_tstring_content(node)
2367
+ dumped = node.value.gsub(/([^[:ascii:]])/) { $1.dump[1...-1] }
2368
+
2369
+ s(
2370
+ :str,
2371
+ ["\"#{dumped}\"".undump],
2372
+ smap_collection_bare(srange_node(node))
2373
+ )
2374
+ end
2375
+
2376
+ # Visit a Unary node.
2377
+ def visit_unary(node)
2378
+ # Special handling here for flipflops
2379
+ if node.statement.is_a?(Paren) &&
2380
+ node.statement.contents.is_a?(Statements) &&
2381
+ node.statement.contents.body.length == 1 &&
2382
+ (range = node.statement.contents.body.first).is_a?(RangeNode) &&
2383
+ node.operator == "!"
2384
+ type = range.operator.value == ".." ? :iflipflop : :eflipflop
2385
+ return(
2386
+ s(
2387
+ :send,
2388
+ [s(:begin, [s(type, visit(range).children, nil)], nil), :!],
2389
+ nil
2390
+ )
2391
+ )
2392
+ end
2393
+
2394
+ visit(canonical_unary(node))
2395
+ end
2396
+
2397
+ # Visit an Undef node.
2398
+ def visit_undef(node)
2399
+ s(
2400
+ :undef,
2401
+ visit_all(node.symbols),
2402
+ smap_keyword_bare(
2403
+ srange_length(node.start_char, 5),
2404
+ srange_node(node)
2405
+ )
2406
+ )
2407
+ end
2408
+
2409
+ # Visit an UnlessNode node.
2410
+ def visit_unless(node)
2411
+ predicate =
2412
+ case node.predicate
2413
+ when RegexpLiteral
2414
+ s(:match_current_line, [visit(node.predicate)], nil)
2415
+ when Unary
2416
+ if node.predicate.operator.value == "!" &&
2417
+ node.predicate.statement.is_a?(RegexpLiteral)
2418
+ s(
2419
+ :send,
2420
+ [
2421
+ s(:match_current_line, [visit(node.predicate.statement)]),
2422
+ :!
2423
+ ],
2424
+ nil
2425
+ )
2426
+ else
2427
+ visit(node.predicate)
2428
+ end
2429
+ else
2430
+ visit(node.predicate)
2431
+ end
2432
+
2433
+ s(
2434
+ :if,
2435
+ [predicate, visit(node.consequent), visit(node.statements)],
2436
+ if node.modifier?
2437
+ smap_keyword_bare(
2438
+ srange_find_between(node.statements, node.predicate, "unless"),
2439
+ srange_node(node)
2440
+ )
2441
+ else
2442
+ smap_condition(
2443
+ srange_length(node.start_char, 6),
2444
+ srange_search_between(node.predicate, node.statements, "then"),
2445
+ nil,
2446
+ srange_length(node.end_char, -3),
2447
+ srange_node(node)
2448
+ )
2449
+ end
2450
+ )
2451
+ end
2452
+
2453
+ # Visit an UntilNode node.
2454
+ def visit_until(node)
2455
+ s(
2456
+ loop_post?(node) ? :until_post : :until,
2457
+ [visit(node.predicate), visit(node.statements)],
2458
+ if node.modifier?
2459
+ smap_keyword_bare(
2460
+ srange_find_between(node.statements, node.predicate, "until"),
2461
+ srange_node(node)
2462
+ )
2463
+ else
2464
+ smap_keyword(
2465
+ srange_length(node.start_char, 5),
2466
+ srange_search_between(node.predicate, node.statements, "do") ||
2467
+ srange_search_between(node.predicate, node.statements, ";"),
2468
+ srange_length(node.end_char, -3),
2469
+ srange_node(node)
2470
+ )
2471
+ end
2472
+ )
2473
+ end
2474
+
2475
+ # Visit a VarField node.
2476
+ def visit_var_field(node)
2477
+ name = node.value.value.to_sym
2478
+ match_var =
2479
+ [stack[-3], stack[-2]].any? do |parent|
2480
+ case parent
2481
+ when AryPtn, FndPtn, HshPtn, In, RAssign
2482
+ true
2483
+ when Binary
2484
+ parent.operator == :"=>"
2485
+ else
2486
+ false
2487
+ end
2488
+ end
2489
+
2490
+ if match_var
2491
+ s(
2492
+ :match_var,
2493
+ [name],
2494
+ smap_variable(srange_node(node.value), srange_node(node.value))
2495
+ )
2496
+ elsif node.value.is_a?(Const)
2497
+ s(
2498
+ :casgn,
2499
+ [nil, name],
2500
+ smap_constant(nil, srange_node(node.value), srange_node(node))
2501
+ )
2502
+ else
2503
+ location = smap_variable(srange_node(node), srange_node(node))
2504
+
2505
+ case node.value
2506
+ when CVar
2507
+ s(:cvasgn, [name], location)
2508
+ when GVar
2509
+ s(:gvasgn, [name], location)
2510
+ when Ident
2511
+ s(:lvasgn, [name], location)
2512
+ when IVar
2513
+ s(:ivasgn, [name], location)
2514
+ when VarRef
2515
+ s(:lvasgn, [name], location)
2516
+ else
2517
+ s(:match_rest, [], nil)
2518
+ end
2519
+ end
2520
+ end
2521
+
2522
+ # Visit a VarRef node.
2523
+ def visit_var_ref(node)
2524
+ visit(node.value)
2525
+ end
2526
+
2527
+ # Visit a VCall node.
2528
+ def visit_vcall(node)
2529
+ visit_command_call(
2530
+ CommandCall.new(
2531
+ receiver: nil,
2532
+ operator: nil,
2533
+ message: node.value,
2534
+ arguments: nil,
2535
+ block: nil,
2536
+ location: node.location
2537
+ )
2538
+ )
2539
+ end
2540
+
2541
+ # Visit a When node.
2542
+ def visit_when(node)
2543
+ keyword = srange_length(node.start_char, 4)
2544
+ begin_token =
2545
+ if buffer.source[node.statements.start_char] == ";"
2546
+ srange_length(node.statements.start_char, 1)
2547
+ end
2548
+
2549
+ end_char =
2550
+ if node.statements.body.empty?
2551
+ node.statements.end_char
2552
+ else
2553
+ node.statements.body.last.end_char
2554
+ end
2555
+
2556
+ s(
2557
+ :when,
2558
+ visit_all(node.arguments.parts) + [visit(node.statements)],
2559
+ smap_keyword(
2560
+ keyword,
2561
+ begin_token,
2562
+ nil,
2563
+ srange(keyword.begin_pos, end_char)
2564
+ )
2565
+ )
2566
+ end
2567
+
2568
+ # Visit a WhileNode node.
2569
+ def visit_while(node)
2570
+ s(
2571
+ loop_post?(node) ? :while_post : :while,
2572
+ [visit(node.predicate), visit(node.statements)],
2573
+ if node.modifier?
2574
+ smap_keyword_bare(
2575
+ srange_find_between(node.statements, node.predicate, "while"),
2576
+ srange_node(node)
2577
+ )
2578
+ else
2579
+ smap_keyword(
2580
+ srange_length(node.start_char, 5),
2581
+ srange_search_between(node.predicate, node.statements, "do") ||
2582
+ srange_search_between(node.predicate, node.statements, ";"),
2583
+ srange_length(node.end_char, -3),
2584
+ srange_node(node)
2585
+ )
2586
+ end
2587
+ )
2588
+ end
2589
+
2590
+ # Visit a Word node.
2591
+ def visit_word(node)
2592
+ visit_string_literal(
2593
+ StringLiteral.new(
2594
+ parts: node.parts,
2595
+ quote: nil,
2596
+ location: node.location
2597
+ )
2598
+ )
2599
+ end
2600
+
2601
+ # Visit a Words node.
2602
+ def visit_words(node)
2603
+ visit_array(
2604
+ ArrayLiteral.new(
2605
+ lbracket: node.beginning,
2606
+ contents: Args.new(parts: node.elements, location: node.location),
2607
+ location: node.location
2608
+ )
2609
+ )
2610
+ end
2611
+
2612
+ # Visit an XStringLiteral node.
2613
+ def visit_xstring_literal(node)
2614
+ s(
2615
+ :xstr,
2616
+ visit_all(node.parts),
2617
+ smap_collection(
2618
+ srange_length(
2619
+ node.start_char,
2620
+ buffer.source[node.start_char] == "%" ? 3 : 1
2621
+ ),
2622
+ srange_length(node.end_char, -1),
2623
+ srange_node(node)
2624
+ )
2625
+ )
2626
+ end
2627
+
2628
+ def visit_yield(node)
2629
+ case node.arguments
2630
+ when nil
2631
+ s(
2632
+ :yield,
2633
+ [],
2634
+ smap_keyword_bare(
2635
+ srange_length(node.start_char, 5),
2636
+ srange_node(node)
2637
+ )
2638
+ )
2639
+ when Args
2640
+ s(
2641
+ :yield,
2642
+ visit_all(node.arguments.parts),
2643
+ smap_keyword_bare(
2644
+ srange_length(node.start_char, 5),
2645
+ srange_node(node)
2646
+ )
2647
+ )
2648
+ else
2649
+ s(
2650
+ :yield,
2651
+ visit_all(node.arguments.contents.parts),
2652
+ smap_keyword(
2653
+ srange_length(node.start_char, 5),
2654
+ srange_length(node.arguments.start_char, 1),
2655
+ srange_length(node.end_char, -1),
2656
+ srange_node(node)
2657
+ )
2658
+ )
2659
+ end
2660
+ end
2661
+
2662
+ # Visit a ZSuper node.
2663
+ def visit_zsuper(node)
2664
+ s(
2665
+ :zsuper,
2666
+ [],
2667
+ smap_keyword_bare(
2668
+ srange_length(node.start_char, 5),
2669
+ srange_node(node)
2670
+ )
2671
+ )
2672
+ end
2673
+ end
2674
+
2675
+ private
2676
+
2677
+ def block_children(node)
2678
+ arguments =
2679
+ if node.block_var
2680
+ visit(node.block_var)
2681
+ else
2682
+ s(:args, [], smap_collection_bare(nil))
2683
+ end
2684
+
2685
+ type = :block
2686
+ if !node.block_var && (maximum = num_block_type(node.bodystmt))
2687
+ type = :numblock
2688
+ arguments = maximum
2689
+ end
2690
+
2691
+ [type, arguments]
2692
+ end
2693
+
2694
+ # Convert a Unary node into a canonical CommandCall node.
2695
+ def canonical_unary(node)
2696
+ # For integers and floats with a leading + or -, parser represents them
2697
+ # as just their values with the signs attached.
2698
+ if %w[+ -].include?(node.operator) &&
2699
+ (node.statement.is_a?(Int) || node.statement.is_a?(FloatLiteral))
2700
+ return(
2701
+ node.statement.class.new(
2702
+ value: "#{node.operator}#{node.statement.value}",
2703
+ location: node.location
2704
+ )
2705
+ )
2706
+ end
2707
+
2708
+ value = { "+" => "+@", "-" => "-@" }.fetch(node.operator, node.operator)
2709
+ length = node.operator.length
2710
+
2711
+ CommandCall.new(
2712
+ receiver: node.statement,
2713
+ operator: nil,
2714
+ message:
2715
+ Op.new(
2716
+ value: value,
2717
+ location:
2718
+ Location.new(
2719
+ start_line: node.location.start_line,
2720
+ start_char: node.start_char,
2721
+ start_column: node.location.start_column,
2722
+ end_line: node.location.start_line,
2723
+ end_char: node.start_char + length,
2724
+ end_column: node.location.start_column + length
2725
+ )
2726
+ ),
2727
+ arguments: nil,
2728
+ block: nil,
2729
+ location: node.location
2730
+ )
2731
+ end
2732
+
2733
+ # Convert a Binary node into a canonical CommandCall node.
2734
+ def canonical_binary(node)
2735
+ operator = node.operator.to_s
2736
+
2737
+ start_char = node.left.end_char
2738
+ end_char = node.right.start_char
2739
+
2740
+ index = buffer.source[start_char...end_char].index(operator)
2741
+ start_line =
2742
+ node.location.start_line +
2743
+ buffer.source[start_char...index].count("\n")
2744
+ start_column =
2745
+ index - (buffer.source[start_char...index].rindex("\n") || 0)
2746
+
2747
+ op_location =
2748
+ Location.new(
2749
+ start_line: start_line,
2750
+ start_column: start_column,
2751
+ start_char: start_char + index,
2752
+ end_line: start_line,
2753
+ end_column: start_column + operator.length,
2754
+ end_char: start_char + index + operator.length
2755
+ )
2756
+
2757
+ CommandCall.new(
2758
+ receiver: node.left,
2759
+ operator: nil,
2760
+ message: Op.new(value: operator, location: op_location),
2761
+ arguments:
2762
+ Args.new(parts: [node.right], location: node.right.location),
2763
+ block: nil,
2764
+ location: node.location
2765
+ )
2766
+ end
2767
+
2768
+ # When you have a begin..end while or begin..end until, it's a special
2769
+ # kind of syntax that executes the block in a loop. In this case the
2770
+ # parser gem has a special node type for it.
2771
+ def loop_post?(node)
2772
+ node.modifier? && node.statements.is_a?(Statements) &&
2773
+ node.statements.body.length == 1 &&
2774
+ node.statements.body.first.is_a?(Begin)
2775
+ end
2776
+
2777
+ # We need to find if we should transform this block into a numblock
2778
+ # since there could be new numbered variables like _1.
2779
+ def num_block_type(statements)
2780
+ variables = []
2781
+ queue = [statements]
2782
+
2783
+ while (child_node = queue.shift)
2784
+ if child_node.is_a?(VarRef) && child_node.value.is_a?(Ident) &&
2785
+ child_node.value.value =~ /^_(\d+)$/
2786
+ variables << $1.to_i
2787
+ end
2788
+
2789
+ queue += child_node.child_nodes.compact
2790
+ end
2791
+
2792
+ variables.max
2793
+ end
2794
+
2795
+ # This method comes almost directly from the parser gem and creates a new
2796
+ # parser gem node from the given s-expression. type is expected to be a
2797
+ # symbol, children is expected to be an array, and location is expected to
2798
+ # be a source map.
2799
+ def s(type, children, location)
2800
+ ::Parser::AST::Node.new(type, children, location: location)
2801
+ end
2802
+
2803
+ # Constructs a plain source map just for an expression.
2804
+ def smap(expression)
2805
+ ::Parser::Source::Map.new(expression)
2806
+ end
2807
+
2808
+ # Constructs a new source map for a collection.
2809
+ def smap_collection(begin_token, end_token, expression)
2810
+ ::Parser::Source::Map::Collection.new(
2811
+ begin_token,
2812
+ end_token,
2813
+ expression
2814
+ )
2815
+ end
2816
+
2817
+ # Constructs a new source map for a collection without a begin or end.
2818
+ def smap_collection_bare(expression)
2819
+ smap_collection(nil, nil, expression)
2820
+ end
2821
+
2822
+ # Constructs a new source map for a conditional expression.
2823
+ def smap_condition(
2824
+ keyword,
2825
+ begin_token,
2826
+ else_token,
2827
+ end_token,
2828
+ expression
2829
+ )
2830
+ ::Parser::Source::Map::Condition.new(
2831
+ keyword,
2832
+ begin_token,
2833
+ else_token,
2834
+ end_token,
2835
+ expression
2836
+ )
2837
+ end
2838
+
2839
+ # Constructs a new source map for a conditional expression with no begin
2840
+ # or end.
2841
+ def smap_condition_bare(expression)
2842
+ smap_condition(nil, nil, nil, nil, expression)
2843
+ end
2844
+
2845
+ # Constructs a new source map for a constant reference.
2846
+ def smap_constant(double_colon, name, expression)
2847
+ ::Parser::Source::Map::Constant.new(double_colon, name, expression)
2848
+ end
2849
+
2850
+ # Constructs a new source map for a class definition.
2851
+ def smap_definition(keyword, operator, name, end_token)
2852
+ ::Parser::Source::Map::Definition.new(
2853
+ keyword,
2854
+ operator,
2855
+ name,
2856
+ end_token
2857
+ )
2858
+ end
2859
+
2860
+ # Constructs a new source map for a for loop.
2861
+ def smap_for(keyword, in_token, begin_token, end_token, expression)
2862
+ ::Parser::Source::Map::For.new(
2863
+ keyword,
2864
+ in_token,
2865
+ begin_token,
2866
+ end_token,
2867
+ expression
2868
+ )
2869
+ end
2870
+
2871
+ # Constructs a new source map for a heredoc.
2872
+ def smap_heredoc(expression, heredoc_body, heredoc_end)
2873
+ ::Parser::Source::Map::Heredoc.new(
2874
+ expression,
2875
+ heredoc_body,
2876
+ heredoc_end
2877
+ )
2878
+ end
2879
+
2880
+ # Construct a source map for an index operation.
2881
+ def smap_index(begin_token, end_token, expression)
2882
+ ::Parser::Source::Map::Index.new(begin_token, end_token, expression)
2883
+ end
2884
+
2885
+ # Constructs a new source map for the use of a keyword.
2886
+ def smap_keyword(keyword, begin_token, end_token, expression)
2887
+ ::Parser::Source::Map::Keyword.new(
2888
+ keyword,
2889
+ begin_token,
2890
+ end_token,
2891
+ expression
2892
+ )
2893
+ end
2894
+
2895
+ # Constructs a new source map for the use of a keyword without a begin or
2896
+ # end token.
2897
+ def smap_keyword_bare(keyword, expression)
2898
+ smap_keyword(keyword, nil, nil, expression)
2899
+ end
2900
+
2901
+ # Constructs a new source map for a method definition.
2902
+ def smap_method_definition(
2903
+ keyword,
2904
+ operator,
2905
+ name,
2906
+ end_token,
2907
+ assignment,
2908
+ expression
2909
+ )
2910
+ ::Parser::Source::Map::MethodDefinition.new(
2911
+ keyword,
2912
+ operator,
2913
+ name,
2914
+ end_token,
2915
+ assignment,
2916
+ expression
2917
+ )
2918
+ end
2919
+
2920
+ # Constructs a new source map for an operator.
2921
+ def smap_operator(operator, expression)
2922
+ ::Parser::Source::Map::Operator.new(operator, expression)
2923
+ end
2924
+
2925
+ # Constructs a source map for the body of a rescue clause.
2926
+ def smap_rescue_body(keyword, assoc, begin_token, expression)
2927
+ ::Parser::Source::Map::RescueBody.new(
2928
+ keyword,
2929
+ assoc,
2930
+ begin_token,
2931
+ expression
2932
+ )
2933
+ end
2934
+
2935
+ # Constructs a new source map for a method call.
2936
+ def smap_send(dot, selector, begin_token, end_token, expression)
2937
+ ::Parser::Source::Map::Send.new(
2938
+ dot,
2939
+ selector,
2940
+ begin_token,
2941
+ end_token,
2942
+ expression
2943
+ )
2944
+ end
2945
+
2946
+ # Constructs a new source map for a method call without a begin or end.
2947
+ def smap_send_bare(selector, expression)
2948
+ smap_send(nil, selector, nil, nil, expression)
2949
+ end
2950
+
2951
+ # Constructs a new source map for a ternary expression.
2952
+ def smap_ternary(question, colon, expression)
2953
+ ::Parser::Source::Map::Ternary.new(question, colon, expression)
2954
+ end
2955
+
2956
+ # Constructs a new source map for a variable.
2957
+ def smap_variable(name, expression)
2958
+ ::Parser::Source::Map::Variable.new(name, expression)
2959
+ end
2960
+
2961
+ # Constructs a new source range from the given start and end offsets.
2962
+ def srange(start_char, end_char)
2963
+ ::Parser::Source::Range.new(buffer, start_char, end_char)
2964
+ end
2965
+
2966
+ # Constructs a new source range by finding the given needle in the given
2967
+ # range of the source. If the needle is not found, returns nil.
2968
+ def srange_search(start_char, end_char, needle)
2969
+ index = buffer.source[start_char...end_char].index(needle)
2970
+ return unless index
2971
+
2972
+ offset = start_char + index
2973
+ srange(offset, offset + needle.length)
2974
+ end
2975
+
2976
+ # Constructs a new source range by searching for the given needle between
2977
+ # the end location of the start node and the start location of the end
2978
+ # node. If the needle is not found, returns nil.
2979
+ def srange_search_between(start_node, end_node, needle)
2980
+ srange_search(start_node.end_char, end_node.start_char, needle)
2981
+ end
2982
+
2983
+ # Constructs a new source range by finding the given needle in the given
2984
+ # range of the source. If it needle is not found, raises an error.
2985
+ def srange_find(start_char, end_char, needle)
2986
+ srange = srange_search(start_char, end_char, needle)
2987
+
2988
+ unless srange
2989
+ slice = buffer.source[start_char...end_char].inspect
2990
+ raise "Could not find #{needle.inspect} in #{slice}"
2991
+ end
2992
+
2993
+ srange
2994
+ end
2995
+
2996
+ # Constructs a new source range by finding the given needle between the
2997
+ # end location of the start node and the start location of the end node.
2998
+ # If the needle is not found, returns raises an error.
2999
+ def srange_find_between(start_node, end_node, needle)
3000
+ srange_find(start_node.end_char, end_node.start_char, needle)
3001
+ end
3002
+
3003
+ # Constructs a new source range from the given start offset and length.
3004
+ def srange_length(start_char, length)
3005
+ if length > 0
3006
+ srange(start_char, start_char + length)
3007
+ else
3008
+ srange(start_char + length, start_char)
3009
+ end
3010
+ end
3011
+
3012
+ # Constructs a new source range using the given node's location.
3013
+ def srange_node(node)
3014
+ location = node.location
3015
+ srange(location.start_char, location.end_char)
3016
+ end
3017
+ end
3018
+ end
3019
+ end