syntax_tree 5.3.0 → 6.0.1

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 +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