syntax_tree 1.2.0 → 2.1.0

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