syntax_tree 5.3.0 → 6.0.1

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