syntax_tree 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3098 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ class Parser < Ripper
5
+ # A special parser error so that we can get nice syntax displays on the
6
+ # error message when prettier prints out the results.
7
+ class ParseError < StandardError
8
+ attr_reader :lineno, :column
9
+
10
+ def initialize(error, lineno, column)
11
+ super(error)
12
+ @lineno = lineno
13
+ @column = column
14
+ end
15
+ end
16
+
17
+ # Represents a line in the source. If this class is being used, it means
18
+ # that every character in the string is 1 byte in length, so we can just
19
+ # return the start of the line + the index.
20
+ class SingleByteString
21
+ attr_reader :start
22
+
23
+ def initialize(start)
24
+ @start = start
25
+ end
26
+
27
+ def [](byteindex)
28
+ start + byteindex
29
+ end
30
+ end
31
+
32
+ # Represents a line in the source. If this class is being used, it means
33
+ # that there are characters in the string that are multi-byte, so we will
34
+ # build up an array of indices, such that array[byteindex] will be equal to
35
+ # the index of the character within the string.
36
+ class MultiByteString
37
+ attr_reader :start, :indices
38
+
39
+ def initialize(start, line)
40
+ @start = start
41
+ @indices = []
42
+
43
+ line.each_char.with_index(start) do |char, index|
44
+ char.bytesize.times { @indices << index }
45
+ end
46
+ end
47
+
48
+ # Technically it's possible for the column index to be a negative value if
49
+ # there's a BOM at the beginning of the file, which is the reason we need
50
+ # to compare it to 0 here.
51
+ def [](byteindex)
52
+ indices[byteindex < 0 ? 0 : byteindex]
53
+ end
54
+ end
55
+
56
+ # [String] the source being parsed
57
+ attr_reader :source
58
+
59
+ # [Array[ String ]] the list of lines in the source
60
+ attr_reader :lines
61
+
62
+ # [Array[ SingleByteString | MultiByteString ]] the list of objects that
63
+ # represent the start of each line in character offsets
64
+ attr_reader :line_counts
65
+
66
+ # [Array[ untyped ]] a running list of tokens that have been found in the
67
+ # source. This list changes a lot as certain nodes will "consume" these
68
+ # tokens to determine their bounds.
69
+ attr_reader :tokens
70
+
71
+ # [Array[ Comment | EmbDoc ]] the list of comments that have been found
72
+ # while parsing the source.
73
+ attr_reader :comments
74
+
75
+ def initialize(source, *)
76
+ super
77
+
78
+ # We keep the source around so that we can refer back to it when we're
79
+ # generating the AST. Sometimes it's easier to just reference the source
80
+ # string when you want to check if it contains a certain character, for
81
+ # example.
82
+ @source = source
83
+
84
+ # Similarly, we keep the lines of the source string around to be able to
85
+ # check if certain lines contain certain characters. For example, we'll
86
+ # use this to generate the content that goes after the __END__ keyword.
87
+ # Or we'll use this to check if a comment has other content on its line.
88
+ @lines = source.split(/\r?\n/)
89
+
90
+ # This is the full set of comments that have been found by the parser.
91
+ # It's a running list. At the end of every block of statements, they will
92
+ # go in and attempt to grab any comments that are on their own line and
93
+ # turn them into regular statements. So at the end of parsing the only
94
+ # comments left in here will be comments on lines that also contain code.
95
+ @comments = []
96
+
97
+ # This is the current embdoc (comments that start with =begin and end with
98
+ # =end). Since they can't be nested, there's no need for a stack here, as
99
+ # there can only be one active. These end up getting dumped into the
100
+ # comments list before getting picked up by the statements that surround
101
+ # them.
102
+ @embdoc = nil
103
+
104
+ # This is an optional node that can be present if the __END__ keyword is
105
+ # used in the file. In that case, this will represent the content after
106
+ # that keyword.
107
+ @__end__ = nil
108
+
109
+ # Heredocs can actually be nested together if you're using interpolation,
110
+ # so this is a stack of heredoc nodes that are currently being created.
111
+ # When we get to the token that finishes off a heredoc node, we pop the
112
+ # top one off. If there are others surrounding it, then the body events
113
+ # will now be added to the correct nodes.
114
+ @heredocs = []
115
+
116
+ # This is a running list of tokens that have fired. It's useful mostly for
117
+ # maintaining location information. For example, if you're inside the
118
+ # handle of a def event, then in order to determine where the AST node
119
+ # started, you need to look backward in the tokens to find a def keyword.
120
+ # Most of the time, when a parser event consumes one of these events, it
121
+ # will be deleted from the list. So ideally, this list stays pretty short
122
+ # over the course of parsing a source string.
123
+ @tokens = []
124
+
125
+ # Here we're going to build up a list of SingleByteString or
126
+ # MultiByteString objects. They're each going to represent a string in the
127
+ # source. They are used by the `char_pos` method to determine where we are
128
+ # in the source string.
129
+ @line_counts = []
130
+ last_index = 0
131
+
132
+ @source.lines.each do |line|
133
+ if line.size == line.bytesize
134
+ @line_counts << SingleByteString.new(last_index)
135
+ else
136
+ @line_counts << MultiByteString.new(last_index, line)
137
+ end
138
+
139
+ last_index += line.size
140
+ end
141
+
142
+ # Make sure line counts is filled out with the first and last line at
143
+ # minimum so that it has something to compare against if the parser is in
144
+ # a lineno=2 state for an empty file.
145
+ @line_counts << SingleByteString.new(0) if @line_counts.empty?
146
+ @line_counts << SingleByteString.new(last_index)
147
+ end
148
+
149
+ private
150
+
151
+ # --------------------------------------------------------------------------
152
+ # :section: Helper methods
153
+ # The following methods are used by the ripper event handlers to either
154
+ # determine their bounds or query other nodes.
155
+ # --------------------------------------------------------------------------
156
+
157
+ # This represents the current place in the source string that we've gotten
158
+ # to so far. We have a memoized line_counts object that we can use to get
159
+ # the number of characters that we've had to go through to get to the
160
+ # beginning of this line, then we add the number of columns into this line
161
+ # that we've gone through.
162
+ def char_pos
163
+ line_counts[lineno - 1][column]
164
+ end
165
+
166
+ # As we build up a list of tokens, we'll periodically need to go backwards
167
+ # and find the ones that we've already hit in order to determine the
168
+ # location information for nodes that use them. For example, if you have a
169
+ # module node then you'll look backward for a kw token to determine your
170
+ # start location.
171
+ #
172
+ # This works with nesting since we're deleting tokens from the list once
173
+ # they've been used up. For example if you had nested module declarations
174
+ # then the innermost declaration would grab the last kw node that matches
175
+ # "module" (which would happen to be the innermost keyword). Then the outer
176
+ # one would only be able to grab the first one. In this way all of the
177
+ # tokens act as their own stack.
178
+ def find_token(type, value = :any, consume: true, location: nil)
179
+ index =
180
+ tokens.rindex do |token|
181
+ token.is_a?(type) && (value == :any || (token.value == value))
182
+ end
183
+
184
+ if consume
185
+ # If we're expecting to be able to find a token and consume it, but
186
+ # can't actually find it, then we need to raise an error. This is
187
+ # _usually_ caused by a syntax error in the source that we're printing.
188
+ # It could also be caused by accidentally attempting to consume a token
189
+ # twice by two different parser event handlers.
190
+ unless index
191
+ token = value == :any ? type.name.split("::", 2).last : value
192
+ message = "Cannot find expected #{token}"
193
+
194
+ if location
195
+ lineno = location.start_line
196
+ column = location.start_char - line_counts[lineno - 1].start
197
+ raise ParseError.new(message, lineno, column)
198
+ else
199
+ raise ParseError.new(message, lineno, column)
200
+ end
201
+ end
202
+
203
+ tokens.delete_at(index)
204
+ elsif index
205
+ tokens[index]
206
+ end
207
+ end
208
+
209
+ # A helper function to find a :: operator. We do special handling instead of
210
+ # using find_token here because we don't pop off all of the :: operators so
211
+ # you could end up getting the wrong information if you have for instance
212
+ # ::X::Y::Z.
213
+ def find_colon2_before(const)
214
+ index =
215
+ tokens.rindex do |token|
216
+ token.is_a?(Op) && token.value == "::" &&
217
+ token.location.start_char < const.location.start_char
218
+ end
219
+
220
+ tokens[index]
221
+ end
222
+
223
+ # Finds the next position in the source string that begins a statement. This
224
+ # is used to bind statements lists and make sure they don't include a
225
+ # preceding comment. For example, we want the following comment to be
226
+ # attached to the class node and not the statement node:
227
+ #
228
+ # class Foo # :nodoc:
229
+ # ...
230
+ # end
231
+ #
232
+ # By finding the next non-space character, we can make sure that the bounds
233
+ # of the statement list are correct.
234
+ def find_next_statement_start(position)
235
+ remaining = source[position..-1]
236
+
237
+ if remaining.sub(/\A +/, "")[0] == "#"
238
+ return position + remaining.index("\n")
239
+ end
240
+
241
+ position
242
+ end
243
+
244
+ # --------------------------------------------------------------------------
245
+ # :section: Ripper event handlers
246
+ # The following methods all handle a dispatched ripper event.
247
+ # --------------------------------------------------------------------------
248
+
249
+ # :call-seq:
250
+ # on_BEGIN: (Statements statements) -> BEGINBlock
251
+ def on_BEGIN(statements)
252
+ lbrace = find_token(LBrace)
253
+ rbrace = find_token(RBrace)
254
+
255
+ statements.bind(
256
+ find_next_statement_start(lbrace.location.end_char),
257
+ rbrace.location.start_char
258
+ )
259
+
260
+ keyword = find_token(Kw, "BEGIN")
261
+
262
+ BEGINBlock.new(
263
+ lbrace: lbrace,
264
+ statements: statements,
265
+ location: keyword.location.to(rbrace.location)
266
+ )
267
+ end
268
+
269
+ # :call-seq:
270
+ # on_CHAR: (String value) -> CHAR
271
+ def on_CHAR(value)
272
+ CHAR.new(
273
+ value: value,
274
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
275
+ )
276
+ end
277
+
278
+ # :call-seq:
279
+ # on_END: (Statements statements) -> ENDBlock
280
+ def on_END(statements)
281
+ lbrace = find_token(LBrace)
282
+ rbrace = find_token(RBrace)
283
+
284
+ statements.bind(
285
+ find_next_statement_start(lbrace.location.end_char),
286
+ rbrace.location.start_char
287
+ )
288
+
289
+ keyword = find_token(Kw, "END")
290
+
291
+ ENDBlock.new(
292
+ lbrace: lbrace,
293
+ statements: statements,
294
+ location: keyword.location.to(rbrace.location)
295
+ )
296
+ end
297
+
298
+ # :call-seq:
299
+ # on___end__: (String value) -> EndContent
300
+ def on___end__(value)
301
+ @__end__ =
302
+ EndContent.new(
303
+ value: source[(char_pos + value.length)..-1],
304
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
305
+ )
306
+ end
307
+
308
+ # :call-seq:
309
+ # on_alias: (
310
+ # (DynaSymbol | SymbolLiteral) left,
311
+ # (DynaSymbol | SymbolLiteral) right
312
+ # ) -> Alias
313
+ def on_alias(left, right)
314
+ keyword = find_token(Kw, "alias")
315
+
316
+ Alias.new(
317
+ left: left,
318
+ right: right,
319
+ location: keyword.location.to(right.location)
320
+ )
321
+ end
322
+
323
+ # :call-seq:
324
+ # on_aref: (untyped collection, (nil | Args) index) -> ARef
325
+ def on_aref(collection, index)
326
+ find_token(LBracket)
327
+ rbracket = find_token(RBracket)
328
+
329
+ ARef.new(
330
+ collection: collection,
331
+ index: index,
332
+ location: collection.location.to(rbracket.location)
333
+ )
334
+ end
335
+
336
+ # :call-seq:
337
+ # on_aref_field: (
338
+ # untyped collection,
339
+ # (nil | Args) index
340
+ # ) -> ARefField
341
+ def on_aref_field(collection, index)
342
+ find_token(LBracket)
343
+ rbracket = find_token(RBracket)
344
+
345
+ ARefField.new(
346
+ collection: collection,
347
+ index: index,
348
+ location: collection.location.to(rbracket.location)
349
+ )
350
+ end
351
+
352
+ # def on_arg_ambiguous(value)
353
+ # value
354
+ # end
355
+
356
+ # :call-seq:
357
+ # on_arg_paren: (
358
+ # (nil | Args | ArgsForward) arguments
359
+ # ) -> ArgParen
360
+ def on_arg_paren(arguments)
361
+ lparen = find_token(LParen)
362
+ rparen = find_token(RParen)
363
+
364
+ # If the arguments exceed the ending of the parentheses, then we know we
365
+ # have a heredoc in the arguments, and we need to use the bounds of the
366
+ # arguments to determine how large the arg_paren is.
367
+ ending =
368
+ if arguments && arguments.location.end_line > rparen.location.end_line
369
+ arguments
370
+ else
371
+ rparen
372
+ end
373
+
374
+ ArgParen.new(
375
+ arguments: arguments,
376
+ location: lparen.location.to(ending.location)
377
+ )
378
+ end
379
+
380
+ # :call-seq:
381
+ # on_args_add: (Args arguments, untyped argument) -> Args
382
+ def on_args_add(arguments, argument)
383
+ if arguments.parts.empty?
384
+ # If this is the first argument being passed into the list of arguments,
385
+ # then we're going to use the bounds of the argument to override the
386
+ # parent node's location since this will be more accurate.
387
+ Args.new(parts: [argument], location: argument.location)
388
+ else
389
+ # Otherwise we're going to update the existing list with the argument
390
+ # being added as well as the new end bounds.
391
+ Args.new(
392
+ parts: arguments.parts << argument,
393
+ location: arguments.location.to(argument.location)
394
+ )
395
+ end
396
+ end
397
+
398
+ # :call-seq:
399
+ # on_args_add_block: (
400
+ # Args arguments,
401
+ # (false | untyped) block
402
+ # ) -> Args
403
+ def on_args_add_block(arguments, block)
404
+ operator = find_token(Op, "&", consume: false)
405
+
406
+ # If we can't find the & operator, then there's no block to add to the
407
+ # list, so we're just going to return the arguments as-is.
408
+ return arguments unless operator
409
+
410
+ # Now we know we have an & operator, so we're going to delete it from the
411
+ # list of tokens to make sure it doesn't get confused with anything else.
412
+ tokens.delete(operator)
413
+
414
+ # Construct the location that represents the block argument.
415
+ location = operator.location
416
+ location = operator.location.to(block.location) if block
417
+
418
+ # If there are any arguments and the operator we found from the list is
419
+ # not after them, then we're going to return the arguments as-is because
420
+ # we're looking at an & that occurs before the arguments are done.
421
+ if arguments.parts.any? && location.start_char < arguments.location.end_char
422
+ return arguments
423
+ end
424
+
425
+ # Otherwise, we're looking at an actual block argument (with or without a
426
+ # block, which could be missing because it could be a bare & since 3.1.0).
427
+ arg_block = ArgBlock.new(value: block, location: location)
428
+
429
+ Args.new(
430
+ parts: arguments.parts << arg_block,
431
+ location: arguments.location.to(location)
432
+ )
433
+ end
434
+
435
+ # :call-seq:
436
+ # on_args_add_star: (Args arguments, untyped star) -> Args
437
+ def on_args_add_star(arguments, argument)
438
+ beginning = find_token(Op, "*")
439
+ ending = argument || beginning
440
+
441
+ location =
442
+ if arguments.parts.empty?
443
+ ending.location
444
+ else
445
+ arguments.location.to(ending.location)
446
+ end
447
+
448
+ arg_star =
449
+ ArgStar.new(
450
+ value: argument,
451
+ location: beginning.location.to(ending.location)
452
+ )
453
+
454
+ Args.new(parts: arguments.parts << arg_star, location: location)
455
+ end
456
+
457
+ # :call-seq:
458
+ # on_args_forward: () -> ArgsForward
459
+ def on_args_forward
460
+ op = find_token(Op, "...")
461
+
462
+ ArgsForward.new(value: op.value, location: op.location)
463
+ end
464
+
465
+ # :call-seq:
466
+ # on_args_new: () -> Args
467
+ def on_args_new
468
+ Args.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
469
+ end
470
+
471
+ # :call-seq:
472
+ # on_array: ((nil | Args) contents) ->
473
+ # ArrayLiteral | QSymbols | QWords | Symbols | Words
474
+ def on_array(contents)
475
+ if !contents || contents.is_a?(Args)
476
+ lbracket = find_token(LBracket)
477
+ rbracket = find_token(RBracket)
478
+
479
+ ArrayLiteral.new(
480
+ lbracket: lbracket,
481
+ contents: contents,
482
+ location: lbracket.location.to(rbracket.location)
483
+ )
484
+ else
485
+ tstring_end =
486
+ find_token(TStringEnd, location: contents.beginning.location)
487
+
488
+ contents.class.new(
489
+ beginning: contents.beginning,
490
+ elements: contents.elements,
491
+ location: contents.location.to(tstring_end.location)
492
+ )
493
+ end
494
+ end
495
+
496
+ # :call-seq:
497
+ # on_aryptn: (
498
+ # (nil | VarRef) constant,
499
+ # (nil | Array[untyped]) requireds,
500
+ # (nil | VarField) rest,
501
+ # (nil | Array[untyped]) posts
502
+ # ) -> AryPtn
503
+ def on_aryptn(constant, requireds, rest, posts)
504
+ parts = [constant, *requireds, rest, *posts].compact
505
+
506
+ AryPtn.new(
507
+ constant: constant,
508
+ requireds: requireds || [],
509
+ rest: rest,
510
+ posts: posts || [],
511
+ location: parts[0].location.to(parts[-1].location)
512
+ )
513
+ end
514
+
515
+ # :call-seq:
516
+ # on_assign: (
517
+ # (ARefField | ConstPathField | Field | TopConstField | VarField) target,
518
+ # untyped value
519
+ # ) -> Assign
520
+ def on_assign(target, value)
521
+ Assign.new(
522
+ target: target,
523
+ value: value,
524
+ location: target.location.to(value.location)
525
+ )
526
+ end
527
+
528
+ # :call-seq:
529
+ # on_assoc_new: (untyped key, untyped value) -> Assoc
530
+ def on_assoc_new(key, value)
531
+ location = key.location
532
+ location = location.to(value.location) if value
533
+
534
+ Assoc.new(key: key, value: value, location: location)
535
+ end
536
+
537
+ # :call-seq:
538
+ # on_assoc_splat: (untyped value) -> AssocSplat
539
+ def on_assoc_splat(value)
540
+ operator = find_token(Op, "**")
541
+
542
+ AssocSplat.new(value: value, location: operator.location.to(value.location))
543
+ end
544
+
545
+ # def on_assoclist_from_args(assocs)
546
+ # assocs
547
+ # end
548
+
549
+ # :call-seq:
550
+ # on_backref: (String value) -> Backref
551
+ def on_backref(value)
552
+ Backref.new(
553
+ value: value,
554
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
555
+ )
556
+ end
557
+
558
+ # :call-seq:
559
+ # on_backtick: (String value) -> Backtick
560
+ def on_backtick(value)
561
+ node =
562
+ Backtick.new(
563
+ value: value,
564
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
565
+ )
566
+
567
+ tokens << node
568
+ node
569
+ end
570
+
571
+ # :call-seq:
572
+ # on_bare_assoc_hash: (Array[AssocNew | AssocSplat] assocs) -> BareAssocHash
573
+ def on_bare_assoc_hash(assocs)
574
+ BareAssocHash.new(
575
+ assocs: assocs,
576
+ location: assocs[0].location.to(assocs[-1].location)
577
+ )
578
+ end
579
+
580
+ # :call-seq:
581
+ # on_begin: (untyped bodystmt) -> Begin | PinnedBegin
582
+ def on_begin(bodystmt)
583
+ pin = find_token(Op, "^", consume: false)
584
+
585
+ if pin && pin.location.start_char < bodystmt.location.start_char
586
+ tokens.delete(pin)
587
+ find_token(LParen)
588
+
589
+ rparen = find_token(RParen)
590
+ location = pin.location.to(rparen.location)
591
+
592
+ PinnedBegin.new(statement: bodystmt, location: location)
593
+ else
594
+ keyword = find_token(Kw, "begin")
595
+ end_char =
596
+ if bodystmt.rescue_clause || bodystmt.ensure_clause ||
597
+ bodystmt.else_clause
598
+ bodystmt.location.end_char
599
+ else
600
+ find_token(Kw, "end").location.end_char
601
+ end
602
+
603
+ bodystmt.bind(keyword.location.end_char, end_char)
604
+ location = keyword.location.to(bodystmt.location)
605
+
606
+ Begin.new(bodystmt: bodystmt, location: location)
607
+ end
608
+ end
609
+
610
+ # :call-seq:
611
+ # on_binary: (untyped left, (Op | Symbol) operator, untyped right) -> Binary
612
+ def on_binary(left, operator, right)
613
+ if operator.is_a?(Symbol)
614
+ # Here, we're going to search backward for the token that's between the
615
+ # two operands that matches the operator so we can delete it from the
616
+ # list.
617
+ index =
618
+ tokens.rindex do |token|
619
+ location = token.location
620
+
621
+ token.is_a?(Op) && token.value == operator.to_s &&
622
+ location.start_char > left.location.end_char &&
623
+ location.end_char < right.location.start_char
624
+ end
625
+
626
+ tokens.delete_at(index) if index
627
+ else
628
+ # On most Ruby implementations, operator is a Symbol that represents
629
+ # that operation being performed. For instance in the example `1 < 2`,
630
+ # the `operator` object would be `:<`. However, on JRuby, it's an `@op`
631
+ # node, so here we're going to explicitly convert it into the same
632
+ # normalized form.
633
+ operator = tokens.delete(operator).value
634
+ end
635
+
636
+ Binary.new(
637
+ left: left,
638
+ operator: operator,
639
+ right: right,
640
+ location: left.location.to(right.location)
641
+ )
642
+ end
643
+
644
+ # :call-seq:
645
+ # on_block_var: (Params params, (nil | Array[Ident]) locals) -> BlockVar
646
+ def on_block_var(params, locals)
647
+ index =
648
+ tokens.rindex do |node|
649
+ node.is_a?(Op) && %w[| ||].include?(node.value) &&
650
+ node.location.start_char < params.location.start_char
651
+ end
652
+
653
+ beginning = tokens[index]
654
+ ending = tokens[-1]
655
+
656
+ BlockVar.new(
657
+ params: params,
658
+ locals: locals || [],
659
+ location: beginning.location.to(ending.location)
660
+ )
661
+ end
662
+
663
+ # :call-seq:
664
+ # on_blockarg: (Ident name) -> BlockArg
665
+ def on_blockarg(name)
666
+ operator = find_token(Op, "&")
667
+
668
+ location = operator.location
669
+ location = location.to(name.location) if name
670
+
671
+ BlockArg.new(name: name, location: location)
672
+ end
673
+
674
+ # :call-seq:
675
+ # on_bodystmt: (
676
+ # Statements statements,
677
+ # (nil | Rescue) rescue_clause,
678
+ # (nil | Statements) else_clause,
679
+ # (nil | Ensure) ensure_clause
680
+ # ) -> BodyStmt
681
+ def on_bodystmt(statements, rescue_clause, else_clause, ensure_clause)
682
+ BodyStmt.new(
683
+ statements: statements,
684
+ rescue_clause: rescue_clause,
685
+ else_clause: else_clause,
686
+ ensure_clause: ensure_clause,
687
+ location: Location.fixed(line: lineno, char: char_pos)
688
+ )
689
+ end
690
+
691
+ # :call-seq:
692
+ # on_brace_block: (
693
+ # (nil | BlockVar) block_var,
694
+ # Statements statements
695
+ # ) -> BraceBlock
696
+ def on_brace_block(block_var, statements)
697
+ lbrace = find_token(LBrace)
698
+ rbrace = find_token(RBrace)
699
+
700
+ statements.bind(
701
+ find_next_statement_start((block_var || lbrace).location.end_char),
702
+ rbrace.location.start_char
703
+ )
704
+
705
+ location =
706
+ Location.new(
707
+ start_line: lbrace.location.start_line,
708
+ start_char: lbrace.location.start_char,
709
+ end_line: [rbrace.location.end_line, statements.location.end_line].max,
710
+ end_char: rbrace.location.end_char
711
+ )
712
+
713
+ BraceBlock.new(
714
+ lbrace: lbrace,
715
+ block_var: block_var,
716
+ statements: statements,
717
+ location: location
718
+ )
719
+ end
720
+
721
+ # :call-seq:
722
+ # on_break: (Args arguments) -> Break
723
+ def on_break(arguments)
724
+ keyword = find_token(Kw, "break")
725
+
726
+ location = keyword.location
727
+ location = location.to(arguments.location) if arguments.parts.any?
728
+
729
+ Break.new(arguments: arguments, location: location)
730
+ end
731
+
732
+ # :call-seq:
733
+ # on_call: (
734
+ # untyped receiver,
735
+ # (:"::" | Op | Period) operator,
736
+ # (:call | Backtick | Const | Ident | Op) message
737
+ # ) -> Call
738
+ def on_call(receiver, operator, message)
739
+ ending = message
740
+ ending = operator if message == :call
741
+
742
+ Call.new(
743
+ receiver: receiver,
744
+ operator: operator,
745
+ message: message,
746
+ arguments: nil,
747
+ location: receiver.location.to(ending.location)
748
+ )
749
+ end
750
+
751
+ # :call-seq:
752
+ # on_case: (untyped value, untyped consequent) -> Case | RAssign
753
+ def on_case(value, consequent)
754
+ if keyword = find_token(Kw, "case", consume: false)
755
+ tokens.delete(keyword)
756
+
757
+ Case.new(
758
+ keyword: keyword,
759
+ value: value,
760
+ consequent: consequent,
761
+ location: keyword.location.to(consequent.location)
762
+ )
763
+ else
764
+ operator = find_token(Kw, "in", consume: false) || find_token(Op, "=>")
765
+
766
+ RAssign.new(
767
+ value: value,
768
+ operator: operator,
769
+ pattern: consequent,
770
+ location: value.location.to(consequent.location)
771
+ )
772
+ end
773
+ end
774
+
775
+ # :call-seq:
776
+ # on_class: (
777
+ # (ConstPathRef | ConstRef | TopConstRef) constant,
778
+ # untyped superclass,
779
+ # BodyStmt bodystmt
780
+ # ) -> ClassDeclaration
781
+ def on_class(constant, superclass, bodystmt)
782
+ beginning = find_token(Kw, "class")
783
+ ending = find_token(Kw, "end")
784
+
785
+ bodystmt.bind(
786
+ find_next_statement_start((superclass || constant).location.end_char),
787
+ ending.location.start_char
788
+ )
789
+
790
+ ClassDeclaration.new(
791
+ constant: constant,
792
+ superclass: superclass,
793
+ bodystmt: bodystmt,
794
+ location: beginning.location.to(ending.location)
795
+ )
796
+ end
797
+
798
+ # :call-seq:
799
+ # on_comma: (String value) -> Comma
800
+ def on_comma(value)
801
+ node =
802
+ Comma.new(
803
+ value: value,
804
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
805
+ )
806
+
807
+ tokens << node
808
+ node
809
+ end
810
+
811
+ # :call-seq:
812
+ # on_command: ((Const | Ident) message, Args arguments) -> Command
813
+ def on_command(message, arguments)
814
+ Command.new(
815
+ message: message,
816
+ arguments: arguments,
817
+ location: message.location.to(arguments.location)
818
+ )
819
+ end
820
+
821
+ # :call-seq:
822
+ # on_command_call: (
823
+ # untyped receiver,
824
+ # (:"::" | Op | Period) operator,
825
+ # (Const | Ident | Op) message,
826
+ # (nil | Args) arguments
827
+ # ) -> CommandCall
828
+ def on_command_call(receiver, operator, message, arguments)
829
+ ending = arguments || message
830
+
831
+ CommandCall.new(
832
+ receiver: receiver,
833
+ operator: operator,
834
+ message: message,
835
+ arguments: arguments,
836
+ location: receiver.location.to(ending.location)
837
+ )
838
+ end
839
+
840
+ # :call-seq:
841
+ # on_comment: (String value) -> Comment
842
+ def on_comment(value)
843
+ line = lineno
844
+ comment =
845
+ Comment.new(
846
+ value: value.chomp,
847
+ inline: value.strip != lines[line - 1].strip,
848
+ location:
849
+ Location.token(line: line, char: char_pos, size: value.size - 1)
850
+ )
851
+
852
+ @comments << comment
853
+ comment
854
+ end
855
+
856
+ # :call-seq:
857
+ # on_const: (String value) -> Const
858
+ def on_const(value)
859
+ Const.new(
860
+ value: value,
861
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
862
+ )
863
+ end
864
+
865
+ # :call-seq:
866
+ # on_const_path_field: (untyped parent, Const constant) -> ConstPathField
867
+ def on_const_path_field(parent, constant)
868
+ ConstPathField.new(
869
+ parent: parent,
870
+ constant: constant,
871
+ location: parent.location.to(constant.location)
872
+ )
873
+ end
874
+
875
+ # :call-seq:
876
+ # on_const_path_ref: (untyped parent, Const constant) -> ConstPathRef
877
+ def on_const_path_ref(parent, constant)
878
+ ConstPathRef.new(
879
+ parent: parent,
880
+ constant: constant,
881
+ location: parent.location.to(constant.location)
882
+ )
883
+ end
884
+
885
+ # :call-seq:
886
+ # on_const_ref: (Const constant) -> ConstRef
887
+ def on_const_ref(constant)
888
+ ConstRef.new(constant: constant, location: constant.location)
889
+ end
890
+
891
+ # :call-seq:
892
+ # on_cvar: (String value) -> CVar
893
+ def on_cvar(value)
894
+ CVar.new(
895
+ value: value,
896
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
897
+ )
898
+ end
899
+
900
+ # :call-seq:
901
+ # on_def: (
902
+ # (Backtick | Const | Ident | Kw | Op) name,
903
+ # (nil | Params | Paren) params,
904
+ # untyped bodystmt
905
+ # ) -> Def | DefEndless
906
+ def on_def(name, params, bodystmt)
907
+ # Make sure to delete this token in case you're defining something like
908
+ # def class which would lead to this being a kw and causing all kinds of
909
+ # trouble
910
+ tokens.delete(name)
911
+
912
+ # Find the beginning of the method definition, which works for single-line
913
+ # and normal method definitions.
914
+ beginning = find_token(Kw, "def")
915
+
916
+ # If there aren't any params then we need to correct the params node
917
+ # location information
918
+ if params.is_a?(Params) && params.empty?
919
+ end_char = name.location.end_char
920
+ location =
921
+ Location.new(
922
+ start_line: params.location.start_line,
923
+ start_char: end_char,
924
+ end_line: params.location.end_line,
925
+ end_char: end_char
926
+ )
927
+
928
+ params = Params.new(location: location)
929
+ end
930
+
931
+ ending = find_token(Kw, "end", consume: false)
932
+
933
+ if ending
934
+ tokens.delete(ending)
935
+ bodystmt.bind(
936
+ find_next_statement_start(params.location.end_char),
937
+ ending.location.start_char
938
+ )
939
+
940
+ Def.new(
941
+ name: name,
942
+ params: params,
943
+ bodystmt: bodystmt,
944
+ location: beginning.location.to(ending.location)
945
+ )
946
+ else
947
+ # In Ruby >= 3.1.0, this is a BodyStmt that wraps a single statement in
948
+ # the statements list. Before, it was just the individual statement.
949
+ statement = bodystmt.is_a?(BodyStmt) ? bodystmt.statements : bodystmt
950
+
951
+ DefEndless.new(
952
+ target: nil,
953
+ operator: nil,
954
+ name: name,
955
+ paren: params,
956
+ statement: statement,
957
+ location: beginning.location.to(bodystmt.location)
958
+ )
959
+ end
960
+ end
961
+
962
+ # :call-seq:
963
+ # on_defined: (untyped value) -> Defined
964
+ def on_defined(value)
965
+ beginning = find_token(Kw, "defined?")
966
+ ending = value
967
+
968
+ range = beginning.location.end_char...value.location.start_char
969
+ if source[range].include?("(")
970
+ find_token(LParen)
971
+ ending = find_token(RParen)
972
+ end
973
+
974
+ Defined.new(value: value, location: beginning.location.to(ending.location))
975
+ end
976
+
977
+ # :call-seq:
978
+ # on_defs: (
979
+ # untyped target,
980
+ # (Op | Period) operator,
981
+ # (Backtick | Const | Ident | Kw | Op) name,
982
+ # (Params | Paren) params,
983
+ # BodyStmt bodystmt
984
+ # ) -> Defs
985
+ def on_defs(target, operator, name, params, bodystmt)
986
+ # Make sure to delete this token in case you're defining something
987
+ # like def class which would lead to this being a kw and causing all kinds
988
+ # of trouble
989
+ tokens.delete(name)
990
+
991
+ # If there aren't any params then we need to correct the params node
992
+ # location information
993
+ if params.is_a?(Params) && params.empty?
994
+ end_char = name.location.end_char
995
+ location =
996
+ Location.new(
997
+ start_line: params.location.start_line,
998
+ start_char: end_char,
999
+ end_line: params.location.end_line,
1000
+ end_char: end_char
1001
+ )
1002
+
1003
+ params = Params.new(location: location)
1004
+ end
1005
+
1006
+ beginning = find_token(Kw, "def")
1007
+ ending = find_token(Kw, "end", consume: false)
1008
+
1009
+ if ending
1010
+ tokens.delete(ending)
1011
+ bodystmt.bind(
1012
+ find_next_statement_start(params.location.end_char),
1013
+ ending.location.start_char
1014
+ )
1015
+
1016
+ Defs.new(
1017
+ target: target,
1018
+ operator: operator,
1019
+ name: name,
1020
+ params: params,
1021
+ bodystmt: bodystmt,
1022
+ location: beginning.location.to(ending.location)
1023
+ )
1024
+ else
1025
+ # In Ruby >= 3.1.0, this is a BodyStmt that wraps a single statement in
1026
+ # the statements list. Before, it was just the individual statement.
1027
+ statement = bodystmt.is_a?(BodyStmt) ? bodystmt.statements : bodystmt
1028
+
1029
+ DefEndless.new(
1030
+ target: target,
1031
+ operator: operator,
1032
+ name: name,
1033
+ paren: params,
1034
+ statement: statement,
1035
+ location: beginning.location.to(bodystmt.location)
1036
+ )
1037
+ end
1038
+ end
1039
+
1040
+ # :call-seq:
1041
+ # on_do_block: (BlockVar block_var, BodyStmt bodystmt) -> DoBlock
1042
+ def on_do_block(block_var, bodystmt)
1043
+ beginning = find_token(Kw, "do")
1044
+ ending = find_token(Kw, "end")
1045
+
1046
+ bodystmt.bind(
1047
+ find_next_statement_start((block_var || beginning).location.end_char),
1048
+ ending.location.start_char
1049
+ )
1050
+
1051
+ DoBlock.new(
1052
+ keyword: beginning,
1053
+ block_var: block_var,
1054
+ bodystmt: bodystmt,
1055
+ location: beginning.location.to(ending.location)
1056
+ )
1057
+ end
1058
+
1059
+ # :call-seq:
1060
+ # on_dot2: ((nil | untyped) left, (nil | untyped) right) -> Dot2
1061
+ def on_dot2(left, right)
1062
+ operator = find_token(Op, "..")
1063
+
1064
+ beginning = left || operator
1065
+ ending = right || operator
1066
+
1067
+ Dot2.new(
1068
+ left: left,
1069
+ right: right,
1070
+ location: beginning.location.to(ending.location)
1071
+ )
1072
+ end
1073
+
1074
+ # :call-seq:
1075
+ # on_dot3: ((nil | untyped) left, (nil | untyped) right) -> Dot3
1076
+ def on_dot3(left, right)
1077
+ operator = find_token(Op, "...")
1078
+
1079
+ beginning = left || operator
1080
+ ending = right || operator
1081
+
1082
+ Dot3.new(
1083
+ left: left,
1084
+ right: right,
1085
+ location: beginning.location.to(ending.location)
1086
+ )
1087
+ end
1088
+
1089
+ # :call-seq:
1090
+ # on_dyna_symbol: (StringContent string_content) -> DynaSymbol
1091
+ def on_dyna_symbol(string_content)
1092
+ if find_token(SymBeg, consume: false)
1093
+ # A normal dynamic symbol
1094
+ symbeg = find_token(SymBeg)
1095
+ tstring_end = find_token(TStringEnd, location: symbeg.location)
1096
+
1097
+ DynaSymbol.new(
1098
+ quote: symbeg.value,
1099
+ parts: string_content.parts,
1100
+ location: symbeg.location.to(tstring_end.location)
1101
+ )
1102
+ else
1103
+ # A dynamic symbol as a hash key
1104
+ tstring_beg = find_token(TStringBeg)
1105
+ label_end = find_token(LabelEnd)
1106
+
1107
+ DynaSymbol.new(
1108
+ parts: string_content.parts,
1109
+ quote: label_end.value[0],
1110
+ location: tstring_beg.location.to(label_end.location)
1111
+ )
1112
+ end
1113
+ end
1114
+
1115
+ # :call-seq:
1116
+ # on_else: (Statements statements) -> Else
1117
+ def on_else(statements)
1118
+ beginning = find_token(Kw, "else")
1119
+
1120
+ # else can either end with an end keyword (in which case we'll want to
1121
+ # consume that event) or it can end with an ensure keyword (in which case
1122
+ # we'll leave that to the ensure to handle).
1123
+ index =
1124
+ tokens.rindex do |token|
1125
+ token.is_a?(Kw) && %w[end ensure].include?(token.value)
1126
+ end
1127
+
1128
+ node = tokens[index]
1129
+ ending = node.value == "end" ? tokens.delete_at(index) : node
1130
+ # ending = node
1131
+
1132
+ statements.bind(beginning.location.end_char, ending.location.start_char)
1133
+
1134
+ Else.new(
1135
+ statements: statements,
1136
+ location: beginning.location.to(ending.location)
1137
+ )
1138
+ end
1139
+
1140
+ # :call-seq:
1141
+ # on_elsif: (
1142
+ # untyped predicate,
1143
+ # Statements statements,
1144
+ # (nil | Elsif | Else) consequent
1145
+ # ) -> Elsif
1146
+ def on_elsif(predicate, statements, consequent)
1147
+ beginning = find_token(Kw, "elsif")
1148
+ ending = consequent || find_token(Kw, "end")
1149
+
1150
+ statements.bind(predicate.location.end_char, ending.location.start_char)
1151
+
1152
+ Elsif.new(
1153
+ predicate: predicate,
1154
+ statements: statements,
1155
+ consequent: consequent,
1156
+ location: beginning.location.to(ending.location)
1157
+ )
1158
+ end
1159
+
1160
+ # :call-seq:
1161
+ # on_embdoc: (String value) -> EmbDoc
1162
+ def on_embdoc(value)
1163
+ @embdoc.value << value
1164
+ @embdoc
1165
+ end
1166
+
1167
+ # :call-seq:
1168
+ # on_embdoc_beg: (String value) -> EmbDoc
1169
+ def on_embdoc_beg(value)
1170
+ @embdoc =
1171
+ EmbDoc.new(
1172
+ value: value,
1173
+ location: Location.fixed(line: lineno, char: char_pos)
1174
+ )
1175
+ end
1176
+
1177
+ # :call-seq:
1178
+ # on_embdoc_end: (String value) -> EmbDoc
1179
+ def on_embdoc_end(value)
1180
+ location = @embdoc.location
1181
+ embdoc =
1182
+ EmbDoc.new(
1183
+ value: @embdoc.value << value.chomp,
1184
+ location:
1185
+ Location.new(
1186
+ start_line: location.start_line,
1187
+ start_char: location.start_char,
1188
+ end_line: lineno,
1189
+ end_char: char_pos + value.length - 1
1190
+ )
1191
+ )
1192
+
1193
+ @comments << embdoc
1194
+ @embdoc = nil
1195
+
1196
+ embdoc
1197
+ end
1198
+
1199
+ # :call-seq:
1200
+ # on_embexpr_beg: (String value) -> EmbExprBeg
1201
+ def on_embexpr_beg(value)
1202
+ node =
1203
+ EmbExprBeg.new(
1204
+ value: value,
1205
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1206
+ )
1207
+
1208
+ tokens << node
1209
+ node
1210
+ end
1211
+
1212
+ # :call-seq:
1213
+ # on_embexpr_end: (String value) -> EmbExprEnd
1214
+ def on_embexpr_end(value)
1215
+ node =
1216
+ EmbExprEnd.new(
1217
+ value: value,
1218
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1219
+ )
1220
+
1221
+ tokens << node
1222
+ node
1223
+ end
1224
+
1225
+ # :call-seq:
1226
+ # on_embvar: (String value) -> EmbVar
1227
+ def on_embvar(value)
1228
+ node =
1229
+ EmbVar.new(
1230
+ value: value,
1231
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1232
+ )
1233
+
1234
+ tokens << node
1235
+ node
1236
+ end
1237
+
1238
+ # :call-seq:
1239
+ # on_ensure: (Statements statements) -> Ensure
1240
+ def on_ensure(statements)
1241
+ keyword = find_token(Kw, "ensure")
1242
+
1243
+ # We don't want to consume the :@kw event, because that would break
1244
+ # def..ensure..end chains.
1245
+ ending = find_token(Kw, "end", consume: false)
1246
+ statements.bind(
1247
+ find_next_statement_start(keyword.location.end_char),
1248
+ ending.location.start_char
1249
+ )
1250
+
1251
+ Ensure.new(
1252
+ keyword: keyword,
1253
+ statements: statements,
1254
+ location: keyword.location.to(ending.location)
1255
+ )
1256
+ end
1257
+
1258
+ # The handler for this event accepts no parameters (though in previous
1259
+ # versions of Ruby it accepted a string literal with a value of ",").
1260
+ #
1261
+ # :call-seq:
1262
+ # on_excessed_comma: () -> ExcessedComma
1263
+ def on_excessed_comma(*)
1264
+ comma = find_token(Comma)
1265
+
1266
+ ExcessedComma.new(value: comma.value, location: comma.location)
1267
+ end
1268
+
1269
+ # :call-seq:
1270
+ # on_fcall: ((Const | Ident) value) -> FCall
1271
+ def on_fcall(value)
1272
+ FCall.new(value: value, arguments: nil, location: value.location)
1273
+ end
1274
+
1275
+ # :call-seq:
1276
+ # on_field: (
1277
+ # untyped parent,
1278
+ # (:"::" | Op | Period) operator
1279
+ # (Const | Ident) name
1280
+ # ) -> Field
1281
+ def on_field(parent, operator, name)
1282
+ Field.new(
1283
+ parent: parent,
1284
+ operator: operator,
1285
+ name: name,
1286
+ location: parent.location.to(name.location)
1287
+ )
1288
+ end
1289
+
1290
+ # :call-seq:
1291
+ # on_float: (String value) -> FloatLiteral
1292
+ def on_float(value)
1293
+ FloatLiteral.new(
1294
+ value: value,
1295
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1296
+ )
1297
+ end
1298
+
1299
+ # :call-seq:
1300
+ # on_fndptn: (
1301
+ # (nil | untyped) constant,
1302
+ # VarField left,
1303
+ # Array[untyped] values,
1304
+ # VarField right
1305
+ # ) -> FndPtn
1306
+ def on_fndptn(constant, left, values, right)
1307
+ beginning = constant || find_token(LBracket)
1308
+ ending = find_token(RBracket)
1309
+
1310
+ FndPtn.new(
1311
+ constant: constant,
1312
+ left: left,
1313
+ values: values,
1314
+ right: right,
1315
+ location: beginning.location.to(ending.location)
1316
+ )
1317
+ end
1318
+
1319
+ # :call-seq:
1320
+ # on_for: (
1321
+ # (MLHS | VarField) value,
1322
+ # untyped collection,
1323
+ # Statements statements
1324
+ # ) -> For
1325
+ def on_for(index, collection, statements)
1326
+ beginning = find_token(Kw, "for")
1327
+ in_keyword = find_token(Kw, "in")
1328
+ ending = find_token(Kw, "end")
1329
+
1330
+ # Consume the do keyword if it exists so that it doesn't get confused for
1331
+ # some other block
1332
+ keyword = find_token(Kw, "do", consume: false)
1333
+ if keyword && keyword.location.start_char > collection.location.end_char &&
1334
+ keyword.location.end_char < ending.location.start_char
1335
+ tokens.delete(keyword)
1336
+ end
1337
+
1338
+ statements.bind(
1339
+ (keyword || collection).location.end_char,
1340
+ ending.location.start_char
1341
+ )
1342
+
1343
+ if index.is_a?(MLHS)
1344
+ comma_range = index.location.end_char...in_keyword.location.start_char
1345
+ index.comma = true if source[comma_range].strip.start_with?(",")
1346
+ end
1347
+
1348
+ For.new(
1349
+ index: index,
1350
+ collection: collection,
1351
+ statements: statements,
1352
+ location: beginning.location.to(ending.location)
1353
+ )
1354
+ end
1355
+
1356
+ # :call-seq:
1357
+ # on_gvar: (String value) -> GVar
1358
+ def on_gvar(value)
1359
+ GVar.new(
1360
+ value: value,
1361
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1362
+ )
1363
+ end
1364
+
1365
+ # :call-seq:
1366
+ # on_hash: ((nil | Array[AssocNew | AssocSplat]) assocs) -> HashLiteral
1367
+ def on_hash(assocs)
1368
+ lbrace = find_token(LBrace)
1369
+ rbrace = find_token(RBrace)
1370
+
1371
+ HashLiteral.new(
1372
+ lbrace: lbrace,
1373
+ assocs: assocs || [],
1374
+ location: lbrace.location.to(rbrace.location)
1375
+ )
1376
+ end
1377
+
1378
+ # :call-seq:
1379
+ # on_heredoc_beg: (String value) -> HeredocBeg
1380
+ def on_heredoc_beg(value)
1381
+ location =
1382
+ Location.token(line: lineno, char: char_pos, size: value.size + 1)
1383
+
1384
+ # Here we're going to artificially create an extra node type so that if
1385
+ # there are comments after the declaration of a heredoc, they get printed.
1386
+ beginning = HeredocBeg.new(value: value, location: location)
1387
+ @heredocs << Heredoc.new(beginning: beginning, location: location)
1388
+
1389
+ beginning
1390
+ end
1391
+
1392
+ # :call-seq:
1393
+ # on_heredoc_dedent: (StringContent string, Integer width) -> Heredoc
1394
+ def on_heredoc_dedent(string, width)
1395
+ heredoc = @heredocs[-1]
1396
+
1397
+ @heredocs[-1] = Heredoc.new(
1398
+ beginning: heredoc.beginning,
1399
+ ending: heredoc.ending,
1400
+ parts: string.parts,
1401
+ location: heredoc.location
1402
+ )
1403
+ end
1404
+
1405
+ # :call-seq:
1406
+ # on_heredoc_end: (String value) -> Heredoc
1407
+ def on_heredoc_end(value)
1408
+ heredoc = @heredocs[-1]
1409
+
1410
+ @heredocs[-1] = Heredoc.new(
1411
+ beginning: heredoc.beginning,
1412
+ ending: value.chomp,
1413
+ parts: heredoc.parts,
1414
+ location:
1415
+ Location.new(
1416
+ start_line: heredoc.location.start_line,
1417
+ start_char: heredoc.location.start_char,
1418
+ end_line: lineno,
1419
+ end_char: char_pos
1420
+ )
1421
+ )
1422
+ end
1423
+
1424
+ # :call-seq:
1425
+ # on_hshptn: (
1426
+ # (nil | untyped) constant,
1427
+ # Array[[Label, untyped]] keywords,
1428
+ # (nil | VarField) keyword_rest
1429
+ # ) -> HshPtn
1430
+ def on_hshptn(constant, keywords, keyword_rest)
1431
+ parts = [constant, keywords, keyword_rest].flatten(2).compact
1432
+
1433
+ HshPtn.new(
1434
+ constant: constant,
1435
+ keywords: keywords,
1436
+ keyword_rest: keyword_rest,
1437
+ location: parts[0].location.to(parts[-1].location)
1438
+ )
1439
+ end
1440
+
1441
+ # :call-seq:
1442
+ # on_ident: (String value) -> Ident
1443
+ def on_ident(value)
1444
+ Ident.new(
1445
+ value: value,
1446
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1447
+ )
1448
+ end
1449
+
1450
+ # :call-seq:
1451
+ # on_if: (
1452
+ # untyped predicate,
1453
+ # Statements statements,
1454
+ # (nil | Elsif | Else) consequent
1455
+ # ) -> If
1456
+ def on_if(predicate, statements, consequent)
1457
+ beginning = find_token(Kw, "if")
1458
+ ending = consequent || find_token(Kw, "end")
1459
+
1460
+ statements.bind(predicate.location.end_char, ending.location.start_char)
1461
+
1462
+ If.new(
1463
+ predicate: predicate,
1464
+ statements: statements,
1465
+ consequent: consequent,
1466
+ location: beginning.location.to(ending.location)
1467
+ )
1468
+ end
1469
+
1470
+ # :call-seq:
1471
+ # on_ifop: (untyped predicate, untyped truthy, untyped falsy) -> IfOp
1472
+ def on_ifop(predicate, truthy, falsy)
1473
+ IfOp.new(
1474
+ predicate: predicate,
1475
+ truthy: truthy,
1476
+ falsy: falsy,
1477
+ location: predicate.location.to(falsy.location)
1478
+ )
1479
+ end
1480
+
1481
+ # :call-seq:
1482
+ # on_if_mod: (untyped predicate, untyped statement) -> IfMod
1483
+ def on_if_mod(predicate, statement)
1484
+ find_token(Kw, "if")
1485
+
1486
+ IfMod.new(
1487
+ statement: statement,
1488
+ predicate: predicate,
1489
+ location: statement.location.to(predicate.location)
1490
+ )
1491
+ end
1492
+
1493
+ # def on_ignored_nl(value)
1494
+ # value
1495
+ # end
1496
+
1497
+ # def on_ignored_sp(value)
1498
+ # value
1499
+ # end
1500
+
1501
+ # :call-seq:
1502
+ # on_imaginary: (String value) -> Imaginary
1503
+ def on_imaginary(value)
1504
+ Imaginary.new(
1505
+ value: value,
1506
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1507
+ )
1508
+ end
1509
+
1510
+ # :call-seq:
1511
+ # on_in: (RAssign pattern, nil statements, nil consequent) -> RAssign
1512
+ # | (
1513
+ # untyped pattern,
1514
+ # Statements statements,
1515
+ # (nil | In | Else) consequent
1516
+ # ) -> In
1517
+ def on_in(pattern, statements, consequent)
1518
+ # Here we have a rightward assignment
1519
+ return pattern unless statements
1520
+
1521
+ beginning = find_token(Kw, "in")
1522
+ ending = consequent || find_token(Kw, "end")
1523
+
1524
+ statements_start = pattern
1525
+ if token = find_token(Kw, "then", consume: false)
1526
+ tokens.delete(token)
1527
+ statements_start = token
1528
+ end
1529
+
1530
+ statements.bind(
1531
+ find_next_statement_start(statements_start.location.end_char),
1532
+ ending.location.start_char
1533
+ )
1534
+
1535
+ In.new(
1536
+ pattern: pattern,
1537
+ statements: statements,
1538
+ consequent: consequent,
1539
+ location: beginning.location.to(ending.location)
1540
+ )
1541
+ end
1542
+
1543
+ # :call-seq:
1544
+ # on_int: (String value) -> Int
1545
+ def on_int(value)
1546
+ Int.new(
1547
+ value: value,
1548
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1549
+ )
1550
+ end
1551
+
1552
+ # :call-seq:
1553
+ # on_ivar: (String value) -> IVar
1554
+ def on_ivar(value)
1555
+ IVar.new(
1556
+ value: value,
1557
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1558
+ )
1559
+ end
1560
+
1561
+ # :call-seq:
1562
+ # on_kw: (String value) -> Kw
1563
+ def on_kw(value)
1564
+ node =
1565
+ Kw.new(
1566
+ value: value,
1567
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1568
+ )
1569
+
1570
+ tokens << node
1571
+ node
1572
+ end
1573
+
1574
+ # :call-seq:
1575
+ # on_kwrest_param: ((nil | Ident) name) -> KwRestParam
1576
+ def on_kwrest_param(name)
1577
+ location = find_token(Op, "**").location
1578
+ location = location.to(name.location) if name
1579
+
1580
+ KwRestParam.new(name: name, location: location)
1581
+ end
1582
+
1583
+ # :call-seq:
1584
+ # on_label: (String value) -> Label
1585
+ def on_label(value)
1586
+ Label.new(
1587
+ value: value,
1588
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1589
+ )
1590
+ end
1591
+
1592
+ # :call-seq:
1593
+ # on_label_end: (String value) -> LabelEnd
1594
+ def on_label_end(value)
1595
+ node =
1596
+ LabelEnd.new(
1597
+ value: value,
1598
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1599
+ )
1600
+
1601
+ tokens << node
1602
+ node
1603
+ end
1604
+
1605
+ # :call-seq:
1606
+ # on_lambda: (
1607
+ # (Params | Paren) params,
1608
+ # (BodyStmt | Statements) statements
1609
+ # ) -> Lambda
1610
+ def on_lambda(params, statements)
1611
+ beginning = find_token(TLambda)
1612
+
1613
+ if tokens.any? { |token|
1614
+ token.is_a?(TLamBeg) &&
1615
+ token.location.start_char > beginning.location.start_char
1616
+ }
1617
+ opening = find_token(TLamBeg)
1618
+ closing = find_token(RBrace)
1619
+ else
1620
+ opening = find_token(Kw, "do")
1621
+ closing = find_token(Kw, "end")
1622
+ end
1623
+
1624
+ statements.bind(opening.location.end_char, closing.location.start_char)
1625
+
1626
+ Lambda.new(
1627
+ params: params,
1628
+ statements: statements,
1629
+ location: beginning.location.to(closing.location)
1630
+ )
1631
+ end
1632
+
1633
+ # :call-seq:
1634
+ # on_lbrace: (String value) -> LBrace
1635
+ def on_lbrace(value)
1636
+ node =
1637
+ LBrace.new(
1638
+ value: value,
1639
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1640
+ )
1641
+
1642
+ tokens << node
1643
+ node
1644
+ end
1645
+
1646
+ # :call-seq:
1647
+ # on_lbracket: (String value) -> LBracket
1648
+ def on_lbracket(value)
1649
+ node =
1650
+ LBracket.new(
1651
+ value: value,
1652
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1653
+ )
1654
+
1655
+ tokens << node
1656
+ node
1657
+ end
1658
+
1659
+ # :call-seq:
1660
+ # on_lparen: (String value) -> LParen
1661
+ def on_lparen(value)
1662
+ node =
1663
+ LParen.new(
1664
+ value: value,
1665
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1666
+ )
1667
+
1668
+ tokens << node
1669
+ node
1670
+ end
1671
+
1672
+ # def on_magic_comment(key, value)
1673
+ # [key, value]
1674
+ # end
1675
+
1676
+ # :call-seq:
1677
+ # on_massign: ((MLHS | MLHSParen) target, untyped value) -> MAssign
1678
+ def on_massign(target, value)
1679
+ comma_range = target.location.end_char...value.location.start_char
1680
+ target.comma = true if source[comma_range].strip.start_with?(",")
1681
+
1682
+ MAssign.new(
1683
+ target: target,
1684
+ value: value,
1685
+ location: target.location.to(value.location)
1686
+ )
1687
+ end
1688
+
1689
+ # :call-seq:
1690
+ # on_method_add_arg: (
1691
+ # (Call | FCall) call,
1692
+ # (ArgParen | Args) arguments
1693
+ # ) -> Call | FCall
1694
+ def on_method_add_arg(call, arguments)
1695
+ location = call.location
1696
+ location = location.to(arguments.location) if arguments.is_a?(ArgParen)
1697
+
1698
+ if call.is_a?(FCall)
1699
+ FCall.new(value: call.value, arguments: arguments, location: location)
1700
+ else
1701
+ Call.new(
1702
+ receiver: call.receiver,
1703
+ operator: call.operator,
1704
+ message: call.message,
1705
+ arguments: arguments,
1706
+ location: location
1707
+ )
1708
+ end
1709
+ end
1710
+
1711
+ # :call-seq:
1712
+ # on_method_add_block: (
1713
+ # (Call | Command | CommandCall | FCall) call,
1714
+ # (BraceBlock | DoBlock) block
1715
+ # ) -> MethodAddBlock
1716
+ def on_method_add_block(call, block)
1717
+ MethodAddBlock.new(
1718
+ call: call,
1719
+ block: block,
1720
+ location: call.location.to(block.location)
1721
+ )
1722
+ end
1723
+
1724
+ # :call-seq:
1725
+ # on_mlhs_add: (
1726
+ # MLHS mlhs,
1727
+ # (ARefField | Field | Ident | MLHSParen | VarField) part
1728
+ # ) -> MLHS
1729
+ def on_mlhs_add(mlhs, part)
1730
+ location =
1731
+ mlhs.parts.empty? ? part.location : mlhs.location.to(part.location)
1732
+
1733
+ MLHS.new(parts: mlhs.parts << part, location: location)
1734
+ end
1735
+
1736
+ # :call-seq:
1737
+ # on_mlhs_add_post: (MLHS left, MLHS right) -> MLHS
1738
+ def on_mlhs_add_post(left, right)
1739
+ MLHS.new(
1740
+ parts: left.parts + right.parts,
1741
+ location: left.location.to(right.location)
1742
+ )
1743
+ end
1744
+
1745
+ # :call-seq:
1746
+ # on_mlhs_add_star: (
1747
+ # MLHS mlhs,
1748
+ # (nil | ARefField | Field | Ident | VarField) part
1749
+ # ) -> MLHS
1750
+ def on_mlhs_add_star(mlhs, part)
1751
+ beginning = find_token(Op, "*")
1752
+ ending = part || beginning
1753
+
1754
+ location = beginning.location.to(ending.location)
1755
+ arg_star = ArgStar.new(value: part, location: location)
1756
+
1757
+ location = mlhs.location.to(location) unless mlhs.parts.empty?
1758
+ MLHS.new(parts: mlhs.parts << arg_star, location: location)
1759
+ end
1760
+
1761
+ # :call-seq:
1762
+ # on_mlhs_new: () -> MLHS
1763
+ def on_mlhs_new
1764
+ MLHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
1765
+ end
1766
+
1767
+ # :call-seq:
1768
+ # on_mlhs_paren: ((MLHS | MLHSParen) contents) -> MLHSParen
1769
+ def on_mlhs_paren(contents)
1770
+ lparen = find_token(LParen)
1771
+ rparen = find_token(RParen)
1772
+
1773
+ comma_range = lparen.location.end_char...rparen.location.start_char
1774
+ contents.comma = true if source[comma_range].strip.end_with?(",")
1775
+
1776
+ MLHSParen.new(
1777
+ contents: contents,
1778
+ location: lparen.location.to(rparen.location)
1779
+ )
1780
+ end
1781
+
1782
+ # :call-seq:
1783
+ # on_module: (
1784
+ # (ConstPathRef | ConstRef | TopConstRef) constant,
1785
+ # BodyStmt bodystmt
1786
+ # ) -> ModuleDeclaration
1787
+ def on_module(constant, bodystmt)
1788
+ beginning = find_token(Kw, "module")
1789
+ ending = find_token(Kw, "end")
1790
+
1791
+ bodystmt.bind(
1792
+ find_next_statement_start(constant.location.end_char),
1793
+ ending.location.start_char
1794
+ )
1795
+
1796
+ ModuleDeclaration.new(
1797
+ constant: constant,
1798
+ bodystmt: bodystmt,
1799
+ location: beginning.location.to(ending.location)
1800
+ )
1801
+ end
1802
+
1803
+ # :call-seq:
1804
+ # on_mrhs_new: () -> MRHS
1805
+ def on_mrhs_new
1806
+ MRHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
1807
+ end
1808
+
1809
+ # :call-seq:
1810
+ # on_mrhs_add: (MRHS mrhs, untyped part) -> MRHS
1811
+ def on_mrhs_add(mrhs, part)
1812
+ location =
1813
+ if mrhs.parts.empty?
1814
+ mrhs.location
1815
+ else
1816
+ mrhs.location.to(part.location)
1817
+ end
1818
+
1819
+ MRHS.new(parts: mrhs.parts << part, location: location)
1820
+ end
1821
+
1822
+ # :call-seq:
1823
+ # on_mrhs_add_star: (MRHS mrhs, untyped value) -> MRHS
1824
+ def on_mrhs_add_star(mrhs, value)
1825
+ beginning = find_token(Op, "*")
1826
+ ending = value || beginning
1827
+
1828
+ arg_star =
1829
+ ArgStar.new(
1830
+ value: value,
1831
+ location: beginning.location.to(ending.location)
1832
+ )
1833
+
1834
+ location =
1835
+ if mrhs.parts.empty?
1836
+ arg_star.location
1837
+ else
1838
+ mrhs.location.to(arg_star.location)
1839
+ end
1840
+
1841
+ MRHS.new(parts: mrhs.parts << arg_star, location: location)
1842
+ end
1843
+
1844
+ # :call-seq:
1845
+ # on_mrhs_new_from_args: (Args arguments) -> MRHS
1846
+ def on_mrhs_new_from_args(arguments)
1847
+ MRHS.new(parts: arguments.parts, location: arguments.location)
1848
+ end
1849
+
1850
+ # :call-seq:
1851
+ # on_next: (Args arguments) -> Next
1852
+ def on_next(arguments)
1853
+ keyword = find_token(Kw, "next")
1854
+
1855
+ location = keyword.location
1856
+ location = location.to(arguments.location) if arguments.parts.any?
1857
+
1858
+ Next.new(arguments: arguments, location: location)
1859
+ end
1860
+
1861
+ # def on_nl(value)
1862
+ # value
1863
+ # end
1864
+
1865
+ # def on_nokw_param(value)
1866
+ # value
1867
+ # end
1868
+
1869
+ # :call-seq:
1870
+ # on_op: (String value) -> Op
1871
+ def on_op(value)
1872
+ node =
1873
+ Op.new(
1874
+ value: value,
1875
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1876
+ )
1877
+
1878
+ tokens << node
1879
+ node
1880
+ end
1881
+
1882
+ # :call-seq:
1883
+ # on_opassign: (
1884
+ # (ARefField | ConstPathField | Field | TopConstField | VarField) target,
1885
+ # Op operator,
1886
+ # untyped value
1887
+ # ) -> OpAssign
1888
+ def on_opassign(target, operator, value)
1889
+ OpAssign.new(
1890
+ target: target,
1891
+ operator: operator,
1892
+ value: value,
1893
+ location: target.location.to(value.location)
1894
+ )
1895
+ end
1896
+
1897
+ # def on_operator_ambiguous(value)
1898
+ # value
1899
+ # end
1900
+
1901
+ # :call-seq:
1902
+ # on_params: (
1903
+ # (nil | Array[Ident]) requireds,
1904
+ # (nil | Array[[Ident, untyped]]) optionals,
1905
+ # (nil | ArgsForward | ExcessedComma | RestParam) rest,
1906
+ # (nil | Array[Ident]) posts,
1907
+ # (nil | Array[[Ident, nil | untyped]]) keywords,
1908
+ # (nil | :nil | ArgsForward | KwRestParam) keyword_rest,
1909
+ # (nil | :& | BlockArg) block
1910
+ # ) -> Params
1911
+ def on_params(
1912
+ requireds,
1913
+ optionals,
1914
+ rest,
1915
+ posts,
1916
+ keywords,
1917
+ keyword_rest,
1918
+ block
1919
+ )
1920
+ parts = [
1921
+ *requireds,
1922
+ *optionals&.flatten(1),
1923
+ rest,
1924
+ *posts,
1925
+ *keywords&.flat_map { |(key, value)| [key, value || nil] },
1926
+ (keyword_rest if keyword_rest != :nil),
1927
+ (block if block != :&)
1928
+ ].compact
1929
+
1930
+ location =
1931
+ if parts.any?
1932
+ parts[0].location.to(parts[-1].location)
1933
+ else
1934
+ Location.fixed(line: lineno, char: char_pos)
1935
+ end
1936
+
1937
+ Params.new(
1938
+ requireds: requireds || [],
1939
+ optionals: optionals || [],
1940
+ rest: rest,
1941
+ posts: posts || [],
1942
+ keywords: keywords || [],
1943
+ keyword_rest: keyword_rest,
1944
+ block: (block if block != :&),
1945
+ location: location
1946
+ )
1947
+ end
1948
+
1949
+ # :call-seq:
1950
+ # on_paren: (untyped contents) -> Paren
1951
+ def on_paren(contents)
1952
+ lparen = find_token(LParen)
1953
+ rparen = find_token(RParen)
1954
+
1955
+ if contents && contents.is_a?(Params)
1956
+ location = contents.location
1957
+ location =
1958
+ Location.new(
1959
+ start_line: location.start_line,
1960
+ start_char: find_next_statement_start(lparen.location.end_char),
1961
+ end_line: location.end_line,
1962
+ end_char: rparen.location.start_char
1963
+ )
1964
+
1965
+ contents =
1966
+ Params.new(
1967
+ requireds: contents.requireds,
1968
+ optionals: contents.optionals,
1969
+ rest: contents.rest,
1970
+ posts: contents.posts,
1971
+ keywords: contents.keywords,
1972
+ keyword_rest: contents.keyword_rest,
1973
+ block: contents.block,
1974
+ location: location
1975
+ )
1976
+ end
1977
+
1978
+ Paren.new(
1979
+ lparen: lparen,
1980
+ contents: contents || nil,
1981
+ location: lparen.location.to(rparen.location)
1982
+ )
1983
+ end
1984
+
1985
+ # If we encounter a parse error, just immediately bail out so that our
1986
+ # runner can catch it.
1987
+ def on_parse_error(error, *)
1988
+ raise ParseError.new(error, lineno, column)
1989
+ end
1990
+ alias on_alias_error on_parse_error
1991
+ alias on_assign_error on_parse_error
1992
+ alias on_class_name_error on_parse_error
1993
+ alias on_param_error on_parse_error
1994
+
1995
+ # :call-seq:
1996
+ # on_period: (String value) -> Period
1997
+ def on_period(value)
1998
+ Period.new(
1999
+ value: value,
2000
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2001
+ )
2002
+ end
2003
+
2004
+ # :call-seq:
2005
+ # on_program: (Statements statements) -> Program
2006
+ def on_program(statements)
2007
+ location =
2008
+ Location.new(
2009
+ start_line: 1,
2010
+ start_char: 0,
2011
+ end_line: lines.length,
2012
+ end_char: source.length
2013
+ )
2014
+
2015
+ statements.body << @__end__ if @__end__
2016
+ statements.bind(0, source.length)
2017
+
2018
+ program = Program.new(statements: statements, location: location)
2019
+ attach_comments(program, @comments)
2020
+
2021
+ program
2022
+ end
2023
+
2024
+ # Attaches comments to the nodes in the tree that most closely correspond to
2025
+ # the location of the comments.
2026
+ def attach_comments(program, comments)
2027
+ comments.each do |comment|
2028
+ preceding, enclosing, following = nearest_nodes(program, comment)
2029
+
2030
+ if comment.inline?
2031
+ if preceding
2032
+ preceding.comments << comment
2033
+ comment.trailing!
2034
+ elsif following
2035
+ following.comments << comment
2036
+ comment.leading!
2037
+ elsif enclosing
2038
+ enclosing.comments << comment
2039
+ else
2040
+ program.comments << comment
2041
+ end
2042
+ else
2043
+ # If a comment exists on its own line, prefer a leading comment.
2044
+ if following
2045
+ following.comments << comment
2046
+ comment.leading!
2047
+ elsif preceding
2048
+ preceding.comments << comment
2049
+ comment.trailing!
2050
+ elsif enclosing
2051
+ enclosing.comments << comment
2052
+ else
2053
+ program.comments << comment
2054
+ end
2055
+ end
2056
+ end
2057
+ end
2058
+
2059
+ # Responsible for finding the nearest nodes to the given comment within the
2060
+ # context of the given encapsulating node.
2061
+ def nearest_nodes(node, comment)
2062
+ comment_start = comment.location.start_char
2063
+ comment_end = comment.location.end_char
2064
+
2065
+ child_nodes = node.child_nodes.compact
2066
+ preceding = nil
2067
+ following = nil
2068
+
2069
+ left = 0
2070
+ right = child_nodes.length
2071
+
2072
+ # This is a custom binary search that finds the nearest nodes to the given
2073
+ # comment. When it finds a node that completely encapsulates the comment,
2074
+ # it recursed downward into the tree.
2075
+ while left < right
2076
+ middle = (left + right) / 2
2077
+ child = child_nodes[middle]
2078
+
2079
+ node_start = child.location.start_char
2080
+ node_end = child.location.end_char
2081
+
2082
+ if node_start <= comment_start && comment_end <= node_end
2083
+ # The comment is completely contained by this child node. Abandon the
2084
+ # binary search at this level.
2085
+ return nearest_nodes(child, comment)
2086
+ end
2087
+
2088
+ if node_end <= comment_start
2089
+ # This child node falls completely before the comment. Because we will
2090
+ # never consider this node or any nodes before it again, this node
2091
+ # must be the closest preceding node we have encountered so far.
2092
+ preceding = child
2093
+ left = middle + 1
2094
+ next
2095
+ end
2096
+
2097
+ if comment_end <= node_start
2098
+ # This child node falls completely after the comment. Because we will
2099
+ # never consider this node or any nodes after it again, this node must
2100
+ # be the closest following node we have encountered so far.
2101
+ following = child
2102
+ right = middle
2103
+ next
2104
+ end
2105
+
2106
+ # This should only happen if there is a bug in this parser.
2107
+ raise "Comment location overlaps with node location"
2108
+ end
2109
+
2110
+ [preceding, node, following]
2111
+ end
2112
+
2113
+ # :call-seq:
2114
+ # on_qsymbols_add: (QSymbols qsymbols, TStringContent element) -> QSymbols
2115
+ def on_qsymbols_add(qsymbols, element)
2116
+ QSymbols.new(
2117
+ beginning: qsymbols.beginning,
2118
+ elements: qsymbols.elements << element,
2119
+ location: qsymbols.location.to(element.location)
2120
+ )
2121
+ end
2122
+
2123
+ # :call-seq:
2124
+ # on_qsymbols_beg: (String value) -> QSymbolsBeg
2125
+ def on_qsymbols_beg(value)
2126
+ node =
2127
+ QSymbolsBeg.new(
2128
+ value: value,
2129
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2130
+ )
2131
+
2132
+ tokens << node
2133
+ node
2134
+ end
2135
+
2136
+ # :call-seq:
2137
+ # on_qsymbols_new: () -> QSymbols
2138
+ def on_qsymbols_new
2139
+ beginning = find_token(QSymbolsBeg)
2140
+
2141
+ QSymbols.new(
2142
+ beginning: beginning,
2143
+ elements: [],
2144
+ location: beginning.location
2145
+ )
2146
+ end
2147
+
2148
+ # :call-seq:
2149
+ # on_qwords_add: (QWords qwords, TStringContent element) -> QWords
2150
+ def on_qwords_add(qwords, element)
2151
+ QWords.new(
2152
+ beginning: qwords.beginning,
2153
+ elements: qwords.elements << element,
2154
+ location: qwords.location.to(element.location)
2155
+ )
2156
+ end
2157
+
2158
+ # :call-seq:
2159
+ # on_qwords_beg: (String value) -> QWordsBeg
2160
+ def on_qwords_beg(value)
2161
+ node =
2162
+ QWordsBeg.new(
2163
+ value: value,
2164
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2165
+ )
2166
+
2167
+ tokens << node
2168
+ node
2169
+ end
2170
+
2171
+ # :call-seq:
2172
+ # on_qwords_new: () -> QWords
2173
+ def on_qwords_new
2174
+ beginning = find_token(QWordsBeg)
2175
+
2176
+ QWords.new(beginning: beginning, elements: [], location: beginning.location)
2177
+ end
2178
+
2179
+ # :call-seq:
2180
+ # on_rational: (String value) -> RationalLiteral
2181
+ def on_rational(value)
2182
+ RationalLiteral.new(
2183
+ value: value,
2184
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2185
+ )
2186
+ end
2187
+
2188
+ # :call-seq:
2189
+ # on_rbrace: (String value) -> RBrace
2190
+ def on_rbrace(value)
2191
+ node =
2192
+ RBrace.new(
2193
+ value: value,
2194
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2195
+ )
2196
+
2197
+ tokens << node
2198
+ node
2199
+ end
2200
+
2201
+ # :call-seq:
2202
+ # on_rbracket: (String value) -> RBracket
2203
+ def on_rbracket(value)
2204
+ node =
2205
+ RBracket.new(
2206
+ value: value,
2207
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2208
+ )
2209
+
2210
+ tokens << node
2211
+ node
2212
+ end
2213
+
2214
+ # :call-seq:
2215
+ # on_redo: () -> Redo
2216
+ def on_redo
2217
+ keyword = find_token(Kw, "redo")
2218
+
2219
+ Redo.new(value: keyword.value, location: keyword.location)
2220
+ end
2221
+
2222
+ # :call-seq:
2223
+ # on_regexp_add: (
2224
+ # RegexpContent regexp_content,
2225
+ # (StringDVar | StringEmbExpr | TStringContent) part
2226
+ # ) -> RegexpContent
2227
+ def on_regexp_add(regexp_content, part)
2228
+ RegexpContent.new(
2229
+ beginning: regexp_content.beginning,
2230
+ parts: regexp_content.parts << part,
2231
+ location: regexp_content.location.to(part.location)
2232
+ )
2233
+ end
2234
+
2235
+ # :call-seq:
2236
+ # on_regexp_beg: (String value) -> RegexpBeg
2237
+ def on_regexp_beg(value)
2238
+ node =
2239
+ RegexpBeg.new(
2240
+ value: value,
2241
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2242
+ )
2243
+
2244
+ tokens << node
2245
+ node
2246
+ end
2247
+
2248
+ # :call-seq:
2249
+ # on_regexp_end: (String value) -> RegexpEnd
2250
+ def on_regexp_end(value)
2251
+ RegexpEnd.new(
2252
+ value: value,
2253
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2254
+ )
2255
+ end
2256
+
2257
+ # :call-seq:
2258
+ # on_regexp_literal: (
2259
+ # RegexpContent regexp_content,
2260
+ # RegexpEnd ending
2261
+ # ) -> RegexpLiteral
2262
+ def on_regexp_literal(regexp_content, ending)
2263
+ RegexpLiteral.new(
2264
+ beginning: regexp_content.beginning,
2265
+ ending: ending.value,
2266
+ parts: regexp_content.parts,
2267
+ location: regexp_content.location.to(ending.location)
2268
+ )
2269
+ end
2270
+
2271
+ # :call-seq:
2272
+ # on_regexp_new: () -> RegexpContent
2273
+ def on_regexp_new
2274
+ regexp_beg = find_token(RegexpBeg)
2275
+
2276
+ RegexpContent.new(
2277
+ beginning: regexp_beg.value,
2278
+ parts: [],
2279
+ location: regexp_beg.location
2280
+ )
2281
+ end
2282
+
2283
+ # :call-seq:
2284
+ # on_rescue: (
2285
+ # (nil | [untyped] | MRHS | MRHSAddStar) exceptions,
2286
+ # (nil | Field | VarField) variable,
2287
+ # Statements statements,
2288
+ # (nil | Rescue) consequent
2289
+ # ) -> Rescue
2290
+ def on_rescue(exceptions, variable, statements, consequent)
2291
+ keyword = find_token(Kw, "rescue")
2292
+ exceptions = exceptions[0] if exceptions.is_a?(Array)
2293
+
2294
+ last_node = variable || exceptions || keyword
2295
+ statements.bind(
2296
+ find_next_statement_start(last_node.location.end_char),
2297
+ char_pos
2298
+ )
2299
+
2300
+ # We add an additional inner node here that ripper doesn't provide so that
2301
+ # we have a nice place to attach inline comments. But we only need it if
2302
+ # we have an exception or a variable that we're rescuing.
2303
+ rescue_ex =
2304
+ if exceptions || variable
2305
+ RescueEx.new(
2306
+ exceptions: exceptions,
2307
+ variable: variable,
2308
+ location:
2309
+ Location.new(
2310
+ start_line: keyword.location.start_line,
2311
+ start_char: keyword.location.end_char + 1,
2312
+ end_line: last_node.location.end_line,
2313
+ end_char: last_node.location.end_char
2314
+ )
2315
+ )
2316
+ end
2317
+
2318
+ Rescue.new(
2319
+ exception: rescue_ex,
2320
+ statements: statements,
2321
+ consequent: consequent,
2322
+ location:
2323
+ Location.new(
2324
+ start_line: keyword.location.start_line,
2325
+ start_char: keyword.location.start_char,
2326
+ end_line: lineno,
2327
+ end_char: char_pos
2328
+ )
2329
+ )
2330
+ end
2331
+
2332
+ # :call-seq:
2333
+ # on_rescue_mod: (untyped statement, untyped value) -> RescueMod
2334
+ def on_rescue_mod(statement, value)
2335
+ find_token(Kw, "rescue")
2336
+
2337
+ RescueMod.new(
2338
+ statement: statement,
2339
+ value: value,
2340
+ location: statement.location.to(value.location)
2341
+ )
2342
+ end
2343
+
2344
+ # :call-seq:
2345
+ # on_rest_param: ((nil | Ident) name) -> RestParam
2346
+ def on_rest_param(name)
2347
+ location = find_token(Op, "*").location
2348
+ location = location.to(name.location) if name
2349
+
2350
+ RestParam.new(name: name, location: location)
2351
+ end
2352
+
2353
+ # :call-seq:
2354
+ # on_retry: () -> Retry
2355
+ def on_retry
2356
+ keyword = find_token(Kw, "retry")
2357
+
2358
+ Retry.new(value: keyword.value, location: keyword.location)
2359
+ end
2360
+
2361
+ # :call-seq:
2362
+ # on_return: (Args arguments) -> Return
2363
+ def on_return(arguments)
2364
+ keyword = find_token(Kw, "return")
2365
+
2366
+ Return.new(
2367
+ arguments: arguments,
2368
+ location: keyword.location.to(arguments.location)
2369
+ )
2370
+ end
2371
+
2372
+ # :call-seq:
2373
+ # on_return0: () -> Return0
2374
+ def on_return0
2375
+ keyword = find_token(Kw, "return")
2376
+
2377
+ Return0.new(value: keyword.value, location: keyword.location)
2378
+ end
2379
+
2380
+ # :call-seq:
2381
+ # on_rparen: (String value) -> RParen
2382
+ def on_rparen(value)
2383
+ node =
2384
+ RParen.new(
2385
+ value: value,
2386
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2387
+ )
2388
+
2389
+ tokens << node
2390
+ node
2391
+ end
2392
+
2393
+ # :call-seq:
2394
+ # on_sclass: (untyped target, BodyStmt bodystmt) -> SClass
2395
+ def on_sclass(target, bodystmt)
2396
+ beginning = find_token(Kw, "class")
2397
+ ending = find_token(Kw, "end")
2398
+
2399
+ bodystmt.bind(
2400
+ find_next_statement_start(target.location.end_char),
2401
+ ending.location.start_char
2402
+ )
2403
+
2404
+ SClass.new(
2405
+ target: target,
2406
+ bodystmt: bodystmt,
2407
+ location: beginning.location.to(ending.location)
2408
+ )
2409
+ end
2410
+
2411
+ # def on_semicolon(value)
2412
+ # value
2413
+ # end
2414
+
2415
+ # def on_sp(value)
2416
+ # value
2417
+ # end
2418
+
2419
+ # stmts_add is a parser event that represents a single statement inside a
2420
+ # list of statements within any lexical block. It accepts as arguments the
2421
+ # parent stmts node as well as an stmt which can be any expression in
2422
+ # Ruby.
2423
+ def on_stmts_add(statements, statement)
2424
+ location =
2425
+ if statements.body.empty?
2426
+ statement.location
2427
+ else
2428
+ statements.location.to(statement.location)
2429
+ end
2430
+
2431
+ Statements.new(self, body: statements.body << statement, location: location)
2432
+ end
2433
+
2434
+ # :call-seq:
2435
+ # on_stmts_new: () -> Statements
2436
+ def on_stmts_new
2437
+ Statements.new(
2438
+ self,
2439
+ body: [],
2440
+ location: Location.fixed(line: lineno, char: char_pos)
2441
+ )
2442
+ end
2443
+
2444
+ # :call-seq:
2445
+ # on_string_add: (
2446
+ # String string,
2447
+ # (StringEmbExpr | StringDVar | TStringContent) part
2448
+ # ) -> StringContent
2449
+ def on_string_add(string, part)
2450
+ location =
2451
+ string.parts.any? ? string.location.to(part.location) : part.location
2452
+
2453
+ StringContent.new(parts: string.parts << part, location: location)
2454
+ end
2455
+
2456
+ # :call-seq:
2457
+ # on_string_concat: (
2458
+ # (StringConcat | StringLiteral) left,
2459
+ # StringLiteral right
2460
+ # ) -> StringConcat
2461
+ def on_string_concat(left, right)
2462
+ StringConcat.new(
2463
+ left: left,
2464
+ right: right,
2465
+ location: left.location.to(right.location)
2466
+ )
2467
+ end
2468
+
2469
+ # :call-seq:
2470
+ # on_string_content: () -> StringContent
2471
+ def on_string_content
2472
+ StringContent.new(
2473
+ parts: [],
2474
+ location: Location.fixed(line: lineno, char: char_pos)
2475
+ )
2476
+ end
2477
+
2478
+ # :call-seq:
2479
+ # on_string_dvar: ((Backref | VarRef) variable) -> StringDVar
2480
+ def on_string_dvar(variable)
2481
+ embvar = find_token(EmbVar)
2482
+
2483
+ StringDVar.new(
2484
+ variable: variable,
2485
+ location: embvar.location.to(variable.location)
2486
+ )
2487
+ end
2488
+
2489
+ # :call-seq:
2490
+ # on_string_embexpr: (Statements statements) -> StringEmbExpr
2491
+ def on_string_embexpr(statements)
2492
+ embexpr_beg = find_token(EmbExprBeg)
2493
+ embexpr_end = find_token(EmbExprEnd)
2494
+
2495
+ statements.bind(
2496
+ embexpr_beg.location.end_char,
2497
+ embexpr_end.location.start_char
2498
+ )
2499
+
2500
+ location =
2501
+ Location.new(
2502
+ start_line: embexpr_beg.location.start_line,
2503
+ start_char: embexpr_beg.location.start_char,
2504
+ end_line: [
2505
+ embexpr_end.location.end_line,
2506
+ statements.location.end_line
2507
+ ].max,
2508
+ end_char: embexpr_end.location.end_char
2509
+ )
2510
+
2511
+ StringEmbExpr.new(statements: statements, location: location)
2512
+ end
2513
+
2514
+ # :call-seq:
2515
+ # on_string_literal: (String string) -> Heredoc | StringLiteral
2516
+ def on_string_literal(string)
2517
+ heredoc = @heredocs[-1]
2518
+
2519
+ if heredoc && heredoc.ending
2520
+ heredoc = @heredocs.pop
2521
+
2522
+ Heredoc.new(
2523
+ beginning: heredoc.beginning,
2524
+ ending: heredoc.ending,
2525
+ parts: string.parts,
2526
+ location: heredoc.location
2527
+ )
2528
+ else
2529
+ tstring_beg = find_token(TStringBeg)
2530
+ tstring_end = find_token(TStringEnd, location: tstring_beg.location)
2531
+
2532
+ location =
2533
+ Location.new(
2534
+ start_line: tstring_beg.location.start_line,
2535
+ start_char: tstring_beg.location.start_char,
2536
+ end_line: [
2537
+ tstring_end.location.end_line,
2538
+ string.location.end_line
2539
+ ].max,
2540
+ end_char: tstring_end.location.end_char
2541
+ )
2542
+
2543
+ StringLiteral.new(
2544
+ parts: string.parts,
2545
+ quote: tstring_beg.value,
2546
+ location: location
2547
+ )
2548
+ end
2549
+ end
2550
+
2551
+ # :call-seq:
2552
+ # on_super: ((ArgParen | Args) arguments) -> Super
2553
+ def on_super(arguments)
2554
+ keyword = find_token(Kw, "super")
2555
+
2556
+ Super.new(
2557
+ arguments: arguments,
2558
+ location: keyword.location.to(arguments.location)
2559
+ )
2560
+ end
2561
+
2562
+ # symbeg is a token that represents the beginning of a symbol literal. In
2563
+ # most cases it will contain just ":" as in the value, but if its a dynamic
2564
+ # symbol being defined it will contain ":'" or ":\"".
2565
+ def on_symbeg(value)
2566
+ node =
2567
+ SymBeg.new(
2568
+ value: value,
2569
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2570
+ )
2571
+
2572
+ tokens << node
2573
+ node
2574
+ end
2575
+
2576
+ # :call-seq:
2577
+ # on_symbol: (
2578
+ # (Backtick | Const | CVar | GVar | Ident | IVar | Kw | Op) value
2579
+ # ) -> SymbolContent
2580
+ def on_symbol(value)
2581
+ tokens.delete(value)
2582
+
2583
+ SymbolContent.new(value: value, location: value.location)
2584
+ end
2585
+
2586
+ # :call-seq:
2587
+ # on_symbol_literal: (
2588
+ # (
2589
+ # Backtick | Const | CVar | GVar | Ident |
2590
+ # IVar | Kw | Op | SymbolContent
2591
+ # ) value
2592
+ # ) -> SymbolLiteral
2593
+ def on_symbol_literal(value)
2594
+ if value.is_a?(SymbolContent)
2595
+ symbeg = find_token(SymBeg)
2596
+
2597
+ SymbolLiteral.new(
2598
+ value: value.value,
2599
+ location: symbeg.location.to(value.location)
2600
+ )
2601
+ else
2602
+ tokens.delete(value)
2603
+ SymbolLiteral.new(value: value, location: value.location)
2604
+ end
2605
+ end
2606
+
2607
+ # :call-seq:
2608
+ # on_symbols_add: (Symbols symbols, Word word) -> Symbols
2609
+ def on_symbols_add(symbols, word)
2610
+ Symbols.new(
2611
+ beginning: symbols.beginning,
2612
+ elements: symbols.elements << word,
2613
+ location: symbols.location.to(word.location)
2614
+ )
2615
+ end
2616
+
2617
+ # :call-seq:
2618
+ # on_symbols_beg: (String value) -> SymbolsBeg
2619
+ def on_symbols_beg(value)
2620
+ node =
2621
+ SymbolsBeg.new(
2622
+ value: value,
2623
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2624
+ )
2625
+
2626
+ tokens << node
2627
+ node
2628
+ end
2629
+
2630
+ # :call-seq:
2631
+ # on_symbols_new: () -> Symbols
2632
+ def on_symbols_new
2633
+ beginning = find_token(SymbolsBeg)
2634
+
2635
+ Symbols.new(
2636
+ beginning: beginning,
2637
+ elements: [],
2638
+ location: beginning.location
2639
+ )
2640
+ end
2641
+
2642
+ # :call-seq:
2643
+ # on_tlambda: (String value) -> TLambda
2644
+ def on_tlambda(value)
2645
+ node =
2646
+ TLambda.new(
2647
+ value: value,
2648
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2649
+ )
2650
+
2651
+ tokens << node
2652
+ node
2653
+ end
2654
+
2655
+ # :call-seq:
2656
+ # on_tlambeg: (String value) -> TLamBeg
2657
+ def on_tlambeg(value)
2658
+ node =
2659
+ TLamBeg.new(
2660
+ value: value,
2661
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2662
+ )
2663
+
2664
+ tokens << node
2665
+ node
2666
+ end
2667
+
2668
+ # :call-seq:
2669
+ # on_top_const_field: (Const constant) -> TopConstRef
2670
+ def on_top_const_field(constant)
2671
+ operator = find_colon2_before(constant)
2672
+
2673
+ TopConstField.new(
2674
+ constant: constant,
2675
+ location: operator.location.to(constant.location)
2676
+ )
2677
+ end
2678
+
2679
+ # :call-seq:
2680
+ # on_top_const_ref: (Const constant) -> TopConstRef
2681
+ def on_top_const_ref(constant)
2682
+ operator = find_colon2_before(constant)
2683
+
2684
+ TopConstRef.new(
2685
+ constant: constant,
2686
+ location: operator.location.to(constant.location)
2687
+ )
2688
+ end
2689
+
2690
+ # :call-seq:
2691
+ # on_tstring_beg: (String value) -> TStringBeg
2692
+ def on_tstring_beg(value)
2693
+ node =
2694
+ TStringBeg.new(
2695
+ value: value,
2696
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2697
+ )
2698
+
2699
+ tokens << node
2700
+ node
2701
+ end
2702
+
2703
+ # :call-seq:
2704
+ # on_tstring_content: (String value) -> TStringContent
2705
+ def on_tstring_content(value)
2706
+ TStringContent.new(
2707
+ value: value,
2708
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2709
+ )
2710
+ end
2711
+
2712
+ # :call-seq:
2713
+ # on_tstring_end: (String value) -> TStringEnd
2714
+ def on_tstring_end(value)
2715
+ node =
2716
+ TStringEnd.new(
2717
+ value: value,
2718
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2719
+ )
2720
+
2721
+ tokens << node
2722
+ node
2723
+ end
2724
+
2725
+ # :call-seq:
2726
+ # on_unary: (:not operator, untyped statement) -> Not
2727
+ # | (Symbol operator, untyped statement) -> Unary
2728
+ def on_unary(operator, statement)
2729
+ if operator == :not
2730
+ # We have somewhat special handling of the not operator since if it has
2731
+ # parentheses they don't get reported as a paren node for some reason.
2732
+
2733
+ beginning = find_token(Kw, "not")
2734
+ ending = statement
2735
+
2736
+ range = beginning.location.end_char...statement.location.start_char
2737
+ paren = source[range].include?("(")
2738
+
2739
+ if paren
2740
+ find_token(LParen)
2741
+ ending = find_token(RParen)
2742
+ end
2743
+
2744
+ Not.new(
2745
+ statement: statement,
2746
+ parentheses: paren,
2747
+ location: beginning.location.to(ending.location)
2748
+ )
2749
+ else
2750
+ # Special case instead of using find_token here. It turns out that
2751
+ # if you have a range that goes from a negative number to a negative
2752
+ # number then you can end up with a .. or a ... that's higher in the
2753
+ # stack. So we need to explicitly disallow those operators.
2754
+ index =
2755
+ tokens.rindex do |token|
2756
+ token.is_a?(Op) &&
2757
+ token.location.start_char < statement.location.start_char &&
2758
+ !%w[.. ...].include?(token.value)
2759
+ end
2760
+
2761
+ beginning = tokens.delete_at(index)
2762
+
2763
+ Unary.new(
2764
+ operator: operator[0], # :+@ -> "+"
2765
+ statement: statement,
2766
+ location: beginning.location.to(statement.location)
2767
+ )
2768
+ end
2769
+ end
2770
+
2771
+ # :call-seq:
2772
+ # on_undef: (Array[DynaSymbol | SymbolLiteral] symbols) -> Undef
2773
+ def on_undef(symbols)
2774
+ keyword = find_token(Kw, "undef")
2775
+
2776
+ Undef.new(
2777
+ symbols: symbols,
2778
+ location: keyword.location.to(symbols.last.location)
2779
+ )
2780
+ end
2781
+
2782
+ # :call-seq:
2783
+ # on_unless: (
2784
+ # untyped predicate,
2785
+ # Statements statements,
2786
+ # ((nil | Elsif | Else) consequent)
2787
+ # ) -> Unless
2788
+ def on_unless(predicate, statements, consequent)
2789
+ beginning = find_token(Kw, "unless")
2790
+ ending = consequent || find_token(Kw, "end")
2791
+
2792
+ statements.bind(predicate.location.end_char, ending.location.start_char)
2793
+
2794
+ Unless.new(
2795
+ predicate: predicate,
2796
+ statements: statements,
2797
+ consequent: consequent,
2798
+ location: beginning.location.to(ending.location)
2799
+ )
2800
+ end
2801
+
2802
+ # :call-seq:
2803
+ # on_unless_mod: (untyped predicate, untyped statement) -> UnlessMod
2804
+ def on_unless_mod(predicate, statement)
2805
+ find_token(Kw, "unless")
2806
+
2807
+ UnlessMod.new(
2808
+ statement: statement,
2809
+ predicate: predicate,
2810
+ location: statement.location.to(predicate.location)
2811
+ )
2812
+ end
2813
+
2814
+ # :call-seq:
2815
+ # on_until: (untyped predicate, Statements statements) -> Until
2816
+ def on_until(predicate, statements)
2817
+ beginning = find_token(Kw, "until")
2818
+ ending = find_token(Kw, "end")
2819
+
2820
+ # Consume the do keyword if it exists so that it doesn't get confused for
2821
+ # some other block
2822
+ keyword = find_token(Kw, "do", consume: false)
2823
+ if keyword && keyword.location.start_char > predicate.location.end_char &&
2824
+ keyword.location.end_char < ending.location.start_char
2825
+ tokens.delete(keyword)
2826
+ end
2827
+
2828
+ # Update the Statements location information
2829
+ statements.bind(predicate.location.end_char, ending.location.start_char)
2830
+
2831
+ Until.new(
2832
+ predicate: predicate,
2833
+ statements: statements,
2834
+ location: beginning.location.to(ending.location)
2835
+ )
2836
+ end
2837
+
2838
+ # :call-seq:
2839
+ # on_until_mod: (untyped predicate, untyped statement) -> UntilMod
2840
+ def on_until_mod(predicate, statement)
2841
+ find_token(Kw, "until")
2842
+
2843
+ UntilMod.new(
2844
+ statement: statement,
2845
+ predicate: predicate,
2846
+ location: statement.location.to(predicate.location)
2847
+ )
2848
+ end
2849
+
2850
+ # :call-seq:
2851
+ # on_var_alias: (GVar left, (Backref | GVar) right) -> VarAlias
2852
+ def on_var_alias(left, right)
2853
+ keyword = find_token(Kw, "alias")
2854
+
2855
+ VarAlias.new(
2856
+ left: left,
2857
+ right: right,
2858
+ location: keyword.location.to(right.location)
2859
+ )
2860
+ end
2861
+
2862
+ # :call-seq:
2863
+ # on_var_field: (
2864
+ # (nil | Const | CVar | GVar | Ident | IVar) value
2865
+ # ) -> VarField
2866
+ def on_var_field(value)
2867
+ location =
2868
+ if value
2869
+ value.location
2870
+ else
2871
+ # You can hit this pattern if you're assigning to a splat using
2872
+ # pattern matching syntax in Ruby 2.7+
2873
+ Location.fixed(line: lineno, char: char_pos)
2874
+ end
2875
+
2876
+ VarField.new(value: value, location: location)
2877
+ end
2878
+
2879
+ # :call-seq:
2880
+ # on_var_ref: ((Const | CVar | GVar | Ident | IVar | Kw) value) -> VarRef
2881
+ def on_var_ref(value)
2882
+ pin = find_token(Op, "^", consume: false)
2883
+
2884
+ if pin && pin.location.start_char == value.location.start_char - 1
2885
+ tokens.delete(pin)
2886
+ PinnedVarRef.new(value: value, location: pin.location.to(value.location))
2887
+ else
2888
+ VarRef.new(value: value, location: value.location)
2889
+ end
2890
+ end
2891
+
2892
+ # :call-seq:
2893
+ # on_vcall: (Ident ident) -> VCall
2894
+ def on_vcall(ident)
2895
+ VCall.new(value: ident, location: ident.location)
2896
+ end
2897
+
2898
+ # :call-seq:
2899
+ # on_void_stmt: () -> VoidStmt
2900
+ def on_void_stmt
2901
+ VoidStmt.new(location: Location.fixed(line: lineno, char: char_pos))
2902
+ end
2903
+
2904
+ # :call-seq:
2905
+ # on_when: (
2906
+ # Args arguments,
2907
+ # Statements statements,
2908
+ # (nil | Else | When) consequent
2909
+ # ) -> When
2910
+ def on_when(arguments, statements, consequent)
2911
+ beginning = find_token(Kw, "when")
2912
+ ending = consequent || find_token(Kw, "end")
2913
+
2914
+ statements_start = arguments
2915
+ if token = find_token(Kw, "then", consume: false)
2916
+ tokens.delete(token)
2917
+ statements_start = token
2918
+ end
2919
+
2920
+ statements.bind(
2921
+ find_next_statement_start(statements_start.location.end_char),
2922
+ ending.location.start_char
2923
+ )
2924
+
2925
+ When.new(
2926
+ arguments: arguments,
2927
+ statements: statements,
2928
+ consequent: consequent,
2929
+ location: beginning.location.to(ending.location)
2930
+ )
2931
+ end
2932
+
2933
+ # :call-seq:
2934
+ # on_while: (untyped predicate, Statements statements) -> While
2935
+ def on_while(predicate, statements)
2936
+ beginning = find_token(Kw, "while")
2937
+ ending = find_token(Kw, "end")
2938
+
2939
+ # Consume the do keyword if it exists so that it doesn't get confused for
2940
+ # some other block
2941
+ keyword = find_token(Kw, "do", consume: false)
2942
+ if keyword && keyword.location.start_char > predicate.location.end_char &&
2943
+ keyword.location.end_char < ending.location.start_char
2944
+ tokens.delete(keyword)
2945
+ end
2946
+
2947
+ # Update the Statements location information
2948
+ statements.bind(predicate.location.end_char, ending.location.start_char)
2949
+
2950
+ While.new(
2951
+ predicate: predicate,
2952
+ statements: statements,
2953
+ location: beginning.location.to(ending.location)
2954
+ )
2955
+ end
2956
+
2957
+ # :call-seq:
2958
+ # on_while_mod: (untyped predicate, untyped statement) -> WhileMod
2959
+ def on_while_mod(predicate, statement)
2960
+ find_token(Kw, "while")
2961
+
2962
+ WhileMod.new(
2963
+ statement: statement,
2964
+ predicate: predicate,
2965
+ location: statement.location.to(predicate.location)
2966
+ )
2967
+ end
2968
+
2969
+ # :call-seq:
2970
+ # on_word_add: (
2971
+ # Word word,
2972
+ # (StringEmbExpr | StringDVar | TStringContent) part
2973
+ # ) -> Word
2974
+ def on_word_add(word, part)
2975
+ location =
2976
+ word.parts.empty? ? part.location : word.location.to(part.location)
2977
+
2978
+ Word.new(parts: word.parts << part, location: location)
2979
+ end
2980
+
2981
+ # :call-seq:
2982
+ # on_word_new: () -> Word
2983
+ def on_word_new
2984
+ Word.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
2985
+ end
2986
+
2987
+ # :call-seq:
2988
+ # on_words_add: (Words words, Word word) -> Words
2989
+ def on_words_add(words, word)
2990
+ Words.new(
2991
+ beginning: words.beginning,
2992
+ elements: words.elements << word,
2993
+ location: words.location.to(word.location)
2994
+ )
2995
+ end
2996
+
2997
+ # :call-seq:
2998
+ # on_words_beg: (String value) -> WordsBeg
2999
+ def on_words_beg(value)
3000
+ node =
3001
+ WordsBeg.new(
3002
+ value: value,
3003
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
3004
+ )
3005
+
3006
+ tokens << node
3007
+ node
3008
+ end
3009
+
3010
+ # :call-seq:
3011
+ # on_words_new: () -> Words
3012
+ def on_words_new
3013
+ beginning = find_token(WordsBeg)
3014
+
3015
+ Words.new(beginning: beginning, elements: [], location: beginning.location)
3016
+ end
3017
+
3018
+ # def on_words_sep(value)
3019
+ # value
3020
+ # end
3021
+
3022
+ # :call-seq:
3023
+ # on_xstring_add: (
3024
+ # XString xstring,
3025
+ # (StringEmbExpr | StringDVar | TStringContent) part
3026
+ # ) -> XString
3027
+ def on_xstring_add(xstring, part)
3028
+ XString.new(
3029
+ parts: xstring.parts << part,
3030
+ location: xstring.location.to(part.location)
3031
+ )
3032
+ end
3033
+
3034
+ # :call-seq:
3035
+ # on_xstring_new: () -> XString
3036
+ def on_xstring_new
3037
+ heredoc = @heredocs[-1]
3038
+
3039
+ location =
3040
+ if heredoc && heredoc.beginning.value.include?("`")
3041
+ heredoc.location
3042
+ else
3043
+ find_token(Backtick).location
3044
+ end
3045
+
3046
+ XString.new(parts: [], location: location)
3047
+ end
3048
+
3049
+ # :call-seq:
3050
+ # on_xstring_literal: (XString xstring) -> Heredoc | XStringLiteral
3051
+ def on_xstring_literal(xstring)
3052
+ heredoc = @heredocs[-1]
3053
+
3054
+ if heredoc && heredoc.beginning.value.include?("`")
3055
+ Heredoc.new(
3056
+ beginning: heredoc.beginning,
3057
+ ending: heredoc.ending,
3058
+ parts: xstring.parts,
3059
+ location: heredoc.location
3060
+ )
3061
+ else
3062
+ ending = find_token(TStringEnd, location: xstring.location)
3063
+
3064
+ XStringLiteral.new(
3065
+ parts: xstring.parts,
3066
+ location: xstring.location.to(ending.location)
3067
+ )
3068
+ end
3069
+ end
3070
+
3071
+ # :call-seq:
3072
+ # on_yield: ((Args | Paren) arguments) -> Yield
3073
+ def on_yield(arguments)
3074
+ keyword = find_token(Kw, "yield")
3075
+
3076
+ Yield.new(
3077
+ arguments: arguments,
3078
+ location: keyword.location.to(arguments.location)
3079
+ )
3080
+ end
3081
+
3082
+ # :call-seq:
3083
+ # on_yield0: () -> Yield0
3084
+ def on_yield0
3085
+ keyword = find_token(Kw, "yield")
3086
+
3087
+ Yield0.new(value: keyword.value, location: keyword.location)
3088
+ end
3089
+
3090
+ # :call-seq:
3091
+ # on_zsuper: () -> ZSuper
3092
+ def on_zsuper
3093
+ keyword = find_token(Kw, "super")
3094
+
3095
+ ZSuper.new(value: keyword.value, location: keyword.location)
3096
+ end
3097
+ end
3098
+ end