prettier 0.20.0 → 1.0.0.pre.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +118 -4
  3. data/CONTRIBUTING.md +8 -6
  4. data/README.md +67 -60
  5. data/node_modules/prettier/bin-prettier.js +12317 -50112
  6. data/node_modules/prettier/index.js +33352 -27419
  7. data/node_modules/prettier/third-party.js +5678 -7676
  8. data/package.json +4 -4
  9. data/src/embed.js +27 -8
  10. data/src/nodes.js +6 -2
  11. data/src/nodes/alias.js +65 -24
  12. data/src/nodes/aref.js +55 -0
  13. data/src/nodes/args.js +55 -47
  14. data/src/nodes/arrays.js +150 -137
  15. data/src/nodes/assign.js +32 -32
  16. data/src/nodes/blocks.js +8 -3
  17. data/src/nodes/calls.js +129 -70
  18. data/src/nodes/case.js +11 -7
  19. data/src/nodes/class.js +74 -0
  20. data/src/nodes/commands.js +36 -31
  21. data/src/nodes/conditionals.js +48 -46
  22. data/src/nodes/constants.js +39 -21
  23. data/src/nodes/flow.js +45 -17
  24. data/src/nodes/hashes.js +126 -112
  25. data/src/nodes/heredocs.js +34 -0
  26. data/src/nodes/hooks.js +36 -7
  27. data/src/nodes/ints.js +27 -20
  28. data/src/nodes/lambdas.js +69 -52
  29. data/src/nodes/loops.js +19 -29
  30. data/src/nodes/massign.js +87 -65
  31. data/src/nodes/methods.js +48 -73
  32. data/src/nodes/operators.js +70 -39
  33. data/src/nodes/params.js +26 -16
  34. data/src/nodes/patterns.js +108 -33
  35. data/src/nodes/regexp.js +38 -14
  36. data/src/nodes/rescue.js +72 -59
  37. data/src/nodes/statements.js +86 -44
  38. data/src/nodes/strings.js +94 -90
  39. data/src/nodes/super.js +35 -0
  40. data/src/nodes/undef.js +42 -0
  41. data/src/parser.js +71 -0
  42. data/src/parser.rb +2554 -0
  43. data/src/printer.js +90 -0
  44. data/src/ruby.js +20 -61
  45. data/src/toProc.js +4 -4
  46. data/src/utils.js +24 -88
  47. data/src/utils/inlineEnsureParens.js +42 -0
  48. data/src/utils/isEmptyStmts.js +7 -0
  49. data/src/utils/literalLineNoBreak.js +7 -0
  50. metadata +15 -20
  51. data/src/haml.js +0 -21
  52. data/src/haml/embed.js +0 -58
  53. data/src/haml/nodes/comment.js +0 -27
  54. data/src/haml/nodes/doctype.js +0 -32
  55. data/src/haml/nodes/filter.js +0 -16
  56. data/src/haml/nodes/hamlComment.js +0 -21
  57. data/src/haml/nodes/script.js +0 -29
  58. data/src/haml/nodes/silentScript.js +0 -59
  59. data/src/haml/nodes/tag.js +0 -157
  60. data/src/haml/parse.js +0 -18
  61. data/src/haml/parse.rb +0 -64
  62. data/src/haml/print.js +0 -38
  63. data/src/nodes/scopes.js +0 -61
  64. data/src/parse.js +0 -37
  65. data/src/print.js +0 -23
  66. data/src/ripper.rb +0 -811
@@ -0,0 +1,35 @@
1
+ const { align, concat, group, join, line } = require("../prettier");
2
+ const { literal } = require("../utils");
3
+
4
+ function printSuper(path, opts, print) {
5
+ const args = path.getValue().body[0];
6
+
7
+ if (args.type === "arg_paren") {
8
+ // In case there are explicitly no arguments but they are using parens,
9
+ // we assume they are attempting to override the initializer and pass no
10
+ // arguments up.
11
+ if (args.body[0] === null) {
12
+ return "super()";
13
+ }
14
+
15
+ return concat(["super", path.call(print, "body", 0)]);
16
+ }
17
+
18
+ const keyword = "super ";
19
+ const argsDocs = path.call(print, "body", 0);
20
+
21
+ return group(
22
+ concat([
23
+ keyword,
24
+ align(keyword.length, group(join(concat([",", line]), argsDocs)))
25
+ ])
26
+ );
27
+ }
28
+
29
+ // Version of super without any parens or args.
30
+ const printZSuper = literal("super");
31
+
32
+ module.exports = {
33
+ super: printSuper,
34
+ zsuper: printZSuper
35
+ };
@@ -0,0 +1,42 @@
1
+ const {
2
+ addTrailingComment,
3
+ align,
4
+ concat,
5
+ group,
6
+ join,
7
+ line
8
+ } = require("../prettier");
9
+
10
+ function printUndefSymbol(path, opts, print) {
11
+ const node = path.getValue();
12
+
13
+ // Since we're going to descend into the symbol literal to grab out the ident
14
+ // node, then we need to make sure we copy over any comments as well,
15
+ // otherwise we could accidentally skip printing them.
16
+ if (node.comments) {
17
+ node.comments.forEach((comment) => {
18
+ addTrailingComment(node.body[0], comment);
19
+ });
20
+ }
21
+
22
+ return path.call(print, "body", 0);
23
+ }
24
+
25
+ function printUndef(path, opts, print) {
26
+ const keyword = "undef ";
27
+ const argNodes = path.map(
28
+ (symbolPath) => printUndefSymbol(symbolPath, opts, print),
29
+ "body"
30
+ );
31
+
32
+ return group(
33
+ concat([
34
+ keyword,
35
+ align(keyword.length, join(concat([",", line]), argNodes))
36
+ ])
37
+ );
38
+ }
39
+
40
+ module.exports = {
41
+ undef: printUndef
42
+ };
@@ -0,0 +1,71 @@
1
+ const { spawnSync } = require("child_process");
2
+ const path = require("path");
3
+
4
+ // In order to properly parse ruby code, we need to tell the ruby process to
5
+ // parse using UTF-8. Unfortunately, the way that you accomplish this looks
6
+ // differently depending on your platform. This object below represents all of
7
+ // the possible values of process.platform per:
8
+ // https://nodejs.org/api/process.html#process_process_platform
9
+ const LANG = {
10
+ aix: "C.UTF-8",
11
+ darwin: "en_US.UTF-8",
12
+ freebsd: "C.UTF-8",
13
+ linux: "C.UTF-8",
14
+ openbsd: "C.UTF-8",
15
+ sunos: "C.UTF-8",
16
+ win32: ".UTF-8"
17
+ }[process.platform];
18
+
19
+ // This function is responsible for taking an input string of text and returning
20
+ // to prettier a JavaScript object that is the equivalent AST that represents
21
+ // the code stored in that string. We accomplish this by spawning a new Ruby
22
+ // process of parser.rb and reading JSON off STDOUT.
23
+ function parse(text, _parsers, _opts) {
24
+ const child = spawnSync(
25
+ "ruby",
26
+ ["--disable-gems", path.join(__dirname, "./parser.rb")],
27
+ {
28
+ env: Object.assign({}, process.env, { LANG }),
29
+ input: text,
30
+ maxBuffer: 10 * 1024 * 1024 // 10MB
31
+ }
32
+ );
33
+
34
+ const error = child.stderr.toString();
35
+ if (error) {
36
+ throw new Error(error);
37
+ }
38
+
39
+ const response = child.stdout.toString();
40
+ return JSON.parse(response);
41
+ }
42
+
43
+ const pragmaPattern = /#\s*@(prettier|format)/;
44
+
45
+ // This function handles checking whether or not the source string has the
46
+ // pragma for prettier. This is an optional workflow for incremental adoption.
47
+ function hasPragma(text) {
48
+ return pragmaPattern.test(text);
49
+ }
50
+
51
+ // This function is critical for comments and cursor support, and is responsible
52
+ // for returning the index of the character within the source string that is the
53
+ // beginning of the given node.
54
+ function locStart(node) {
55
+ return node.char_start;
56
+ }
57
+
58
+ // This function is critical for comments and cursor support, and is responsible
59
+ // for returning the index of the character within the source string that is the
60
+ // ending of the given node.
61
+ function locEnd(node) {
62
+ return node.char_end;
63
+ }
64
+
65
+ module.exports = {
66
+ parse,
67
+ astFormat: "ruby",
68
+ hasPragma,
69
+ locStart,
70
+ locEnd
71
+ };
@@ -0,0 +1,2554 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # We implement our own version checking here instead of using Gem::Version so
4
+ # that we can use the --disable-gems flag.
5
+ RUBY_MAJOR, RUBY_MINOR, RUBY_PATCH, * = RUBY_VERSION.split('.').map(&:to_i)
6
+
7
+ if (RUBY_MAJOR < 2) || ((RUBY_MAJOR == 2) && (RUBY_MINOR < 5))
8
+ warn(
9
+ "Ruby version #{RUBY_VERSION} not supported. " \
10
+ 'Please upgrade to 2.5.0 or above.'
11
+ )
12
+
13
+ exit 1
14
+ end
15
+
16
+ require 'delegate'
17
+ require 'json' unless defined?(JSON)
18
+ require 'ripper'
19
+
20
+ module Prettier; end
21
+
22
+ class Prettier::Parser < Ripper
23
+ attr_reader :source, :lines, :scanner_events, :line_counts
24
+
25
+ def initialize(source, *args)
26
+ super(source, *args)
27
+
28
+ @source = source
29
+ @lines = source.split("\n")
30
+
31
+ @comments = []
32
+ @embdoc = nil
33
+ @__end__ = nil
34
+
35
+ @heredocs = []
36
+
37
+ @scanner_events = []
38
+ @line_counts = [0]
39
+
40
+ @source.lines.each { |line| @line_counts << @line_counts.last + line.size }
41
+ end
42
+
43
+ private
44
+
45
+ # This represents the current place in the source string that we've gotten to
46
+ # so far. We have a memoized line_counts object that we can use to get the
47
+ # number of characters that we've had to go through to get to the beginning of
48
+ # this line, then we add the number of columns into this line that we've gone
49
+ # through.
50
+ def char_pos
51
+ line_counts[lineno - 1] + column
52
+ end
53
+
54
+ # As we build up a list of scanner events, we'll periodically need to go
55
+ # backwards and find the ones that we've already hit in order to determine the
56
+ # location information for nodes that use them. For example, if you have a
57
+ # module node then you'll look backward for a @module scanner event to
58
+ # determine your start location.
59
+ #
60
+ # This works with nesting since we're deleting scanner events from the list
61
+ # once they've been used up. For example if you had nested module declarations
62
+ # then the innermost declaration would grab the last @module event (which
63
+ # would happen to be the innermost keyword). Then the outer one would only be
64
+ # able to grab the first one. In this way all of the scanner events act as
65
+ # their own stack.
66
+ def find_scanner_event(type, body = :any)
67
+ index =
68
+ scanner_events.rindex do |scanner_event|
69
+ scanner_event[:type] == type &&
70
+ (body == :any || (scanner_event[:body] == body))
71
+ end
72
+
73
+ scanner_events.delete_at(index)
74
+ end
75
+
76
+ # Scanner events occur when the lexer hits a new token, like a keyword or an
77
+ # end. These nodes always contain just one argument which is a string
78
+ # representing the content. For the most part these can just be printed
79
+ # directly, which very few exceptions.
80
+ defined = %i[
81
+ comment
82
+ embdoc
83
+ embdoc_beg
84
+ embdoc_end
85
+ heredoc_beg
86
+ heredoc_end
87
+ ignored_nl
88
+ ]
89
+
90
+ (SCANNER_EVENTS - defined).each do |event|
91
+ define_method(:"on_#{event}") do |value|
92
+ char_end = char_pos + value.size
93
+ node = {
94
+ type: :"@#{event}",
95
+ body: value,
96
+ start: lineno,
97
+ end: lineno,
98
+ char_start: char_pos,
99
+ char_end: char_end
100
+ }
101
+
102
+ scanner_events << node
103
+ node
104
+ end
105
+ end
106
+
107
+ # We keep track of each comment as it comes in and then eventually add
108
+ # them to the top of the generated AST so that prettier can start adding
109
+ # them back into the final representation. Comments come in including
110
+ # their starting pound sign and the newline at the end, so we also chop
111
+ # those off.
112
+ #
113
+ # If there is an encoding magic comment at the top of the file, ripper
114
+ # will actually change into that encoding for the storage of the string.
115
+ # This will break everything, so we need to force the encoding back into
116
+ # UTF-8 so that the JSON library won't break.
117
+ def on_comment(value)
118
+ @comments << {
119
+ type: :@comment,
120
+ value: value[1..-1].chomp.force_encoding('UTF-8'),
121
+ start: lineno,
122
+ end: lineno,
123
+ char_start: char_pos,
124
+ char_end: char_pos + value.length - 1
125
+ }
126
+ end
127
+
128
+ # ignored_nl is a special kind of scanner event that passes nil as the value,
129
+ # so we can't do our normal tracking of value.size. Instead of adding a
130
+ # condition to the main SCANNER_EVENTS loop above, we'll just explicitly
131
+ # define the method here. You can trigger the ignored_nl event with the
132
+ # following snippet:
133
+ #
134
+ # foo.bar
135
+ # .baz
136
+ #
137
+ def on_ignored_nl(value)
138
+ {
139
+ type: :ignored_nl,
140
+ body: nil,
141
+ start: lineno,
142
+ end: lineno,
143
+ char_start: char_pos,
144
+ char_end: char_pos
145
+ }
146
+ end
147
+
148
+ prepend(
149
+ Module.new do
150
+ private
151
+
152
+ # Handles __END__ syntax, which allows individual scripts to keep content
153
+ # after the main ruby code that can be read through DATA. It looks like:
154
+ #
155
+ # foo.bar
156
+ #
157
+ # __END__
158
+ # some other content that isn't normally read by ripper
159
+ def on___end__(*)
160
+ @__end__ = super(lines[lineno..-1].join("\n"))
161
+ end
162
+
163
+ # Like comments, we need to force the encoding here so JSON doesn't break.
164
+ def on_ident(value)
165
+ super(value.force_encoding('UTF-8'))
166
+ end
167
+
168
+ # Like comments, we need to force the encoding here so JSON doesn't break.
169
+ def on_tstring_content(value)
170
+ super(value.force_encoding('UTF-8'))
171
+ end
172
+ end
173
+ )
174
+
175
+ # A BEGIN node is a parser event that represents the use of the BEGIN
176
+ # keyword, which hooks into the lifecycle of the interpreter. It's a bit
177
+ # of a legacy from the stream operating days, and gets its inspiration
178
+ # from tools like awk. Whatever is inside the "block" will get executed
179
+ # when the program starts. The syntax looks like the following:
180
+ #
181
+ # BEGIN {
182
+ # # execute stuff here
183
+ # }
184
+ #
185
+ def on_BEGIN(stmts)
186
+ beging = find_scanner_event(:@lbrace)
187
+ ending = find_scanner_event(:@rbrace)
188
+
189
+ stmts.bind(
190
+ find_next_statement_start(beging[:char_end]),
191
+ ending[:char_start]
192
+ )
193
+
194
+ find_scanner_event(:@kw, 'BEGIN').merge!(
195
+ type: :BEGIN,
196
+ body: [beging, stmts],
197
+ end: ending[:end],
198
+ char_end: ending[:char_end]
199
+ )
200
+ end
201
+
202
+ # A END node is a parser event that represents the use of the END keyword,
203
+ # which hooks into the lifecycle of the interpreter. It's a bit of a
204
+ # legacy from the stream operating days, and gets its inspiration from
205
+ # tools like awk. Whatever is inside the "block" will get executed when
206
+ # the program ends. The syntax looks like the following:
207
+ #
208
+ # END {
209
+ # # execute stuff here
210
+ # }
211
+ #
212
+ def on_END(stmts)
213
+ beging = find_scanner_event(:@lbrace)
214
+ ending = find_scanner_event(:@rbrace)
215
+
216
+ stmts.bind(
217
+ find_next_statement_start(beging[:char_end]),
218
+ ending[:char_start]
219
+ )
220
+
221
+ find_scanner_event(:@kw, 'END').merge!(
222
+ type: :END,
223
+ body: [beging, stmts],
224
+ end: ending[:end],
225
+ char_end: ending[:char_end]
226
+ )
227
+ end
228
+
229
+ # alias is a parser event that represents when you're using the alias
230
+ # keyword with regular arguments. This can be either symbol literals or
231
+ # bare words. You can optionally use parentheses with this keyword, so we
232
+ # either track the location information based on those or the final
233
+ # argument to the alias method.
234
+ def on_alias(left, right)
235
+ beging = find_scanner_event(:@kw, 'alias')
236
+
237
+ paren = source[beging[:char_end]...left[:char_start]].include?('(')
238
+ ending = paren ? find_scanner_event(:@rparen) : right
239
+
240
+ {
241
+ type: :alias,
242
+ body: [left, right],
243
+ start: beging[:start],
244
+ char_start: beging[:char_start],
245
+ end: ending[:end],
246
+ char_end: ending[:char_end]
247
+ }
248
+ end
249
+
250
+ # aref nodes are when you're pulling a value out of a collection at a
251
+ # specific index. Put another way, it's any time you're calling the method
252
+ # #[]. As an example:
253
+ #
254
+ # foo[index]
255
+ #
256
+ # The nodes usually contains two children, the collection and the index.
257
+ # In some cases, you don't necessarily have the second child node, because
258
+ # you can call procs with a pretty esoteric syntax. In the following
259
+ # example, you wouldn't have a second child, and "foo" would be the first
260
+ # child:
261
+ #
262
+ # foo[]
263
+ #
264
+ def on_aref(collection, index)
265
+ find_scanner_event(:@lbracket)
266
+ ending = find_scanner_event(:@rbracket)
267
+
268
+ {
269
+ type: :aref,
270
+ body: [collection, index],
271
+ start: collection[:start],
272
+ char_start: collection[:char_start],
273
+ end: ending[:end],
274
+ char_end: ending[:char_end]
275
+ }
276
+ end
277
+
278
+ # aref_field is a parser event that is very similar to aref except that it
279
+ # is being used inside of an assignment.
280
+ def on_aref_field(collection, index)
281
+ find_scanner_event(:@lbracket)
282
+ ending = find_scanner_event(:@rbracket)
283
+
284
+ {
285
+ type: :aref_field,
286
+ body: [collection, index],
287
+ start: collection[:start],
288
+ char_start: collection[:char_start],
289
+ end: ending[:end],
290
+ char_end: ending[:char_end]
291
+ }
292
+ end
293
+
294
+ # args_new is a parser event that represents the beginning of a list of
295
+ # arguments to any method call or an array. It can be followed by any
296
+ # number of args_add events, which we'll append onto an array body.
297
+ def on_args_new
298
+ {
299
+ type: :args,
300
+ body: [],
301
+ start: lineno,
302
+ char_start: char_pos,
303
+ end: lineno,
304
+ char_end: char_pos
305
+ }
306
+ end
307
+
308
+ # args_add is a parser event that represents a single argument inside a
309
+ # list of arguments to any method call or an array. It accepts as
310
+ # arguments the parent args node as well as an arg which can be anything
311
+ # that could be passed as an argument.
312
+ def on_args_add(args, arg)
313
+ if args[:body].empty?
314
+ arg.merge(type: :args, body: [arg])
315
+ else
316
+ args.merge!(
317
+ body: args[:body] << arg, end: arg[:end], char_end: arg[:char_end]
318
+ )
319
+ end
320
+ end
321
+
322
+ # args_add_block is a parser event that represents a list of arguments and
323
+ # potentially a block argument. If no block is passed, then the second
324
+ # argument will be false.
325
+ def on_args_add_block(args, block)
326
+ ending = block || args
327
+
328
+ args.merge(
329
+ type: :args_add_block,
330
+ body: [args, block],
331
+ end: ending[:end],
332
+ char_end: ending[:char_end]
333
+ )
334
+ end
335
+
336
+ # args_add_star is a parser event that represents adding a splat of values
337
+ # to a list of arguments. If accepts as arguments the parent args node as
338
+ # well as the part that is being splatted.
339
+ def on_args_add_star(args, part)
340
+ beging = find_scanner_event(:@op, '*')
341
+ ending = part || beging
342
+
343
+ {
344
+ type: :args_add_star,
345
+ body: [args, part],
346
+ start: beging[:start],
347
+ char_start: beging[:char_start],
348
+ end: ending[:end],
349
+ char_end: ending[:char_end]
350
+ }
351
+ end
352
+
353
+ # args_forward is a parser event that represents forwarding all kinds of
354
+ # arguments onto another method call.
355
+ def on_args_forward
356
+ find_scanner_event(:@op, '...').merge!(type: :args_forward)
357
+ end
358
+
359
+ # arg_paren is a parser event that represents wrapping arguments to a
360
+ # method inside a set of parentheses.
361
+ def on_arg_paren(args)
362
+ beging = find_scanner_event(:@lparen)
363
+ rparen = find_scanner_event(:@rparen)
364
+
365
+ # If the arguments exceed the ending of the parentheses, then we know we
366
+ # have a heredoc in the arguments, and we need to use the bounds of the
367
+ # arguments to determine how large the arg_paren is.
368
+ ending = (args && args[:end] > rparen[:end]) ? args : rparen
369
+
370
+ {
371
+ type: :arg_paren,
372
+ body: [args],
373
+ start: beging[:start],
374
+ char_start: beging[:char_start],
375
+ end: ending[:end],
376
+ char_end: ending[:char_end]
377
+ }
378
+ end
379
+
380
+ # Array nodes can contain a myriad of subnodes because of the special
381
+ # array literal syntax like %w and %i. As a result, we may be looking for
382
+ # an left bracket, or we may be just looking at the children to get the
383
+ # bounds.
384
+ def on_array(contents)
385
+ if !contents || %i[args args_add_star].include?(contents[:type])
386
+ beging = find_scanner_event(:@lbracket)
387
+ ending = find_scanner_event(:@rbracket)
388
+
389
+ {
390
+ type: :array,
391
+ body: [contents],
392
+ start: beging[:start],
393
+ char_start: beging[:char_start],
394
+ end: ending[:end],
395
+ char_end: ending[:char_end]
396
+ }
397
+ else
398
+ ending = find_scanner_event(:@tstring_end)
399
+ contents[:char_end] = ending[:char_end]
400
+
401
+ ending.merge!(
402
+ type: :array,
403
+ body: [contents],
404
+ start: contents[:start],
405
+ char_start: contents[:char_start]
406
+ )
407
+ end
408
+ end
409
+
410
+ # aryptn is a parser event that represents matching against an array pattern
411
+ # using the Ruby 2.7+ pattern matching syntax.
412
+ def on_aryptn(const, preargs, splatarg, postargs)
413
+ pieces = [const, *preargs, splatarg, *postargs].compact
414
+
415
+ {
416
+ type: :aryptn,
417
+ body: [const, preargs, splatarg, postargs],
418
+ start: pieces[0][:start],
419
+ char_start: pieces[0][:char_start],
420
+ end: pieces[-1][:end],
421
+ char_end: pieces[-1][:char_end]
422
+ }
423
+ end
424
+
425
+ # assign is a parser event that represents assigning something to a
426
+ # variable or constant. It accepts as arguments the left side of the
427
+ # expression before the equals sign and the right side of the expression.
428
+ def on_assign(left, right)
429
+ left.merge(
430
+ type: :assign,
431
+ body: [left, right],
432
+ end: right[:end],
433
+ char_end: right[:char_end]
434
+ )
435
+ end
436
+
437
+ # assoc_new is a parser event that contains a key-value pair within a
438
+ # hash. It is a child event of either an assoclist_from_args or a
439
+ # bare_assoc_hash.
440
+ def on_assoc_new(key, value)
441
+ {
442
+ type: :assoc_new,
443
+ body: [key, value],
444
+ start: key[:start],
445
+ char_start: key[:char_start],
446
+ end: value[:end],
447
+ char_end: value[:char_end]
448
+ }
449
+ end
450
+
451
+ # assoc_splat is a parser event that represents splatting a value into a
452
+ # hash (either a hash literal or a bare hash in a method call).
453
+ def on_assoc_splat(contents)
454
+ find_scanner_event(:@op, '**').merge!(
455
+ type: :assoc_splat,
456
+ body: [contents],
457
+ end: contents[:end],
458
+ char_end: contents[:char_end]
459
+ )
460
+ end
461
+
462
+ # assoclist_from_args is a parser event that contains a list of all of the
463
+ # associations inside of a hash literal. Its parent node is always a hash.
464
+ # It accepts as an argument an array of assoc events (either assoc_new or
465
+ # assoc_splat).
466
+ def on_assoclist_from_args(assocs)
467
+ {
468
+ type: :assoclist_from_args,
469
+ body: assocs,
470
+ start: assocs[0][:start],
471
+ char_start: assocs[0][:char_start],
472
+ end: assocs[-1][:end],
473
+ char_end: assocs[-1][:char_end]
474
+ }
475
+ end
476
+
477
+ # bare_assoc_hash is a parser event that represents a hash of contents
478
+ # being passed as a method argument (and therefore has omitted braces). It
479
+ # accepts as an argument an array of assoc events (either assoc_new or
480
+ # assoc_splat).
481
+ def on_bare_assoc_hash(assoc_news)
482
+ {
483
+ type: :bare_assoc_hash,
484
+ body: assoc_news,
485
+ start: assoc_news[0][:start],
486
+ char_start: assoc_news[0][:char_start],
487
+ end: assoc_news[-1][:end],
488
+ char_end: assoc_news[-1][:char_end]
489
+ }
490
+ end
491
+
492
+ # begin is a parser event that represents the beginning of a begin..end chain.
493
+ # It includes a bodystmt event that has all of the consequent clauses.
494
+ def on_begin(bodystmt)
495
+ beging = find_scanner_event(:@kw, 'begin')
496
+ char_end =
497
+ if bodystmt[:body][1..-1].any?
498
+ bodystmt[:char_end]
499
+ else
500
+ find_scanner_event(:@kw, 'end')[:char_end]
501
+ end
502
+
503
+ bodystmt.bind(beging[:char_end], char_end)
504
+
505
+ beging.merge!(
506
+ type: :begin,
507
+ body: [bodystmt],
508
+ end: bodystmt[:end],
509
+ char_end: bodystmt[:char_end]
510
+ )
511
+ end
512
+
513
+ # binary is a parser event that represents a binary operation between two
514
+ # values.
515
+ def on_binary(left, oper, right)
516
+ {
517
+ type: :binary,
518
+ body: [left, oper, right],
519
+ start: left[:start],
520
+ char_start: left[:char_start],
521
+ end: right[:end],
522
+ char_end: right[:char_end]
523
+ }
524
+ end
525
+
526
+ # block_var is a parser event that represents the parameters being passed to
527
+ # block. Effectively they're everything contained within the pipes.
528
+ def on_block_var(params, locals)
529
+ index =
530
+ scanner_events.rindex do |event|
531
+ event[:type] == :@op && %w[| ||].include?(event[:body]) &&
532
+ event[:char_start] < params[:char_start]
533
+ end
534
+
535
+ beging = scanner_events[index]
536
+ ending = scanner_events[-1]
537
+
538
+ {
539
+ type: :block_var,
540
+ body: [params, locals],
541
+ start: beging[:start],
542
+ char_start: beging[:char_start],
543
+ end: ending[:end],
544
+ char_end: ending[:char_end]
545
+ }
546
+ end
547
+
548
+ # blockarg is a parser event that represents defining a block variable on
549
+ # a method definition.
550
+ def on_blockarg(ident)
551
+ find_scanner_event(:@op, '&').merge!(
552
+ type: :blockarg,
553
+ body: [ident],
554
+ end: ident[:end],
555
+ char_end: ident[:char_end]
556
+ )
557
+ end
558
+
559
+ # bodystmt can't actually determine its bounds appropriately because it
560
+ # doesn't necessarily know where it started. So the parent node needs to
561
+ # report back down into this one where it goes.
562
+ class BodyStmt < SimpleDelegator
563
+ def bind(char_start, char_end)
564
+ merge!(char_start: char_start, char_end: char_end)
565
+ parts = self[:body]
566
+
567
+ # Here we're going to determine the bounds for the stmts
568
+ consequent = parts[1..-1].compact.first
569
+ self[:body][0].bind(
570
+ char_start,
571
+ consequent ? consequent[:char_start] : char_end
572
+ )
573
+
574
+ # Next we're going to determine the rescue clause if there is one
575
+ if parts[1]
576
+ consequent = parts[2..-1].compact.first
577
+ self[:body][1].bind_end(consequent ? consequent[:char_start] : char_end)
578
+ end
579
+ end
580
+ end
581
+
582
+ # bodystmt is a parser event that represents all of the possible combinations
583
+ # of clauses within the body of a method or block.
584
+ def on_bodystmt(stmts, rescued, ensured, elsed)
585
+ BodyStmt.new(
586
+ type: :bodystmt,
587
+ body: [stmts, rescued, ensured, elsed],
588
+ start: lineno,
589
+ char_start: char_pos,
590
+ end: lineno,
591
+ char_end: char_pos
592
+ )
593
+ end
594
+
595
+ # brace_block is a parser event that represents passing a block to a
596
+ # method call using the {..} operators. It accepts as arguments an
597
+ # optional block_var event that represents any parameters to the block as
598
+ # well as a stmts event that represents the statements inside the block.
599
+ def on_brace_block(block_var, stmts)
600
+ beging = find_scanner_event(:@lbrace)
601
+ ending = find_scanner_event(:@rbrace)
602
+
603
+ stmts.bind((block_var || beging)[:char_end], ending[:char_start])
604
+
605
+ {
606
+ type: :brace_block,
607
+ body: [block_var, stmts],
608
+ start: beging[:start],
609
+ char_start: beging[:char_start],
610
+ end: ending[:end],
611
+ char_end: ending[:char_end]
612
+ }
613
+ end
614
+
615
+ # break is a parser event that represents using the break keyword. It
616
+ # accepts as an argument an args or args_add_block event that contains all
617
+ # of the arguments being passed to the break.
618
+ def on_break(args_add_block)
619
+ beging = find_scanner_event(:@kw, 'break')
620
+
621
+ # You can hit this if you are passing no arguments to break but it has a
622
+ # comment right after it. In that case we can just use the location
623
+ # information straight from the keyword.
624
+ if args_add_block[:type] == :args
625
+ return beging.merge!(type: :break, body: [args_add_block])
626
+ end
627
+
628
+ beging.merge!(
629
+ type: :break,
630
+ body: [args_add_block],
631
+ end: args_add_block[:end],
632
+ char_end: args_add_block[:char_end]
633
+ )
634
+ end
635
+
636
+ # call is a parser event representing a method call with no arguments. It
637
+ # accepts as arguments the receiver of the method, the operator being used
638
+ # to send the method (., ::, or &.), and the value that is being sent to
639
+ # the receiver (which can be another nested call as well).
640
+ #
641
+ # There is one esoteric syntax that comes into play here as well. If the
642
+ # sending argument to this method is the symbol :call, then it represents
643
+ # calling a lambda in a very odd looking way, as in:
644
+ #
645
+ # foo.(1, 2, 3)
646
+ #
647
+ def on_call(receiver, oper, sending)
648
+ # Make sure we take the operator out of the scanner events so that it
649
+ # doesn't get confused for a unary operator later.
650
+ scanner_events.delete(oper)
651
+
652
+ ending = sending
653
+
654
+ if sending == :call
655
+ ending = oper
656
+
657
+ # Special handling here for Ruby <= 2.5 because the oper argument to this
658
+ # method wasn't a parser event here it was just a plain symbol.
659
+ ending = receiver if RUBY_MAJOR <= 2 && RUBY_MINOR <= 5
660
+ end
661
+
662
+ {
663
+ type: :call,
664
+ body: [receiver, oper, sending],
665
+ start: receiver[:start],
666
+ char_start: receiver[:char_start],
667
+ end: ending[:end],
668
+ char_end: ending[:char_end]
669
+ }
670
+ end
671
+
672
+ # case is a parser event that represents the beginning of a case chain.
673
+ # It accepts as arguments the switch of the case and the consequent
674
+ # clause.
675
+ def on_case(switch, consequent)
676
+ find_scanner_event(:@kw, 'case').merge!(
677
+ type: :case,
678
+ body: [switch, consequent],
679
+ end: consequent[:end],
680
+ char_end: consequent[:char_end]
681
+ )
682
+ end
683
+
684
+ # Finds the next position in the source string that begins a statement. This
685
+ # is used to bind statements lists and make sure they don't include a
686
+ # preceding comment. For example, we want the following comment to be attached
687
+ # to the class node and not the statement node:
688
+ #
689
+ # class Foo # :nodoc:
690
+ # ...
691
+ # end
692
+ #
693
+ # By finding the next non-space character, we can make sure that the bounds of
694
+ # the statement list are correct.
695
+ def find_next_statement_start(position)
696
+ remaining = source[position..-1]
697
+
698
+ if remaining.sub(/\A +/, '')[0] == '#'
699
+ return position + remaining.index("\n")
700
+ end
701
+
702
+ position
703
+ end
704
+
705
+ # class is a parser event that represents defining a class. It accepts as
706
+ # arguments the name of the class, the optional name of the superclass,
707
+ # and the bodystmt event that represents the statements evaluated within
708
+ # the context of the class.
709
+ def on_class(const, superclass, bodystmt)
710
+ beging = find_scanner_event(:@kw, 'class')
711
+ ending = find_scanner_event(:@kw, 'end')
712
+
713
+ bodystmt.bind(
714
+ find_next_statement_start((superclass || const)[:char_end]),
715
+ ending[:char_start]
716
+ )
717
+
718
+ {
719
+ type: :class,
720
+ body: [const, superclass, bodystmt],
721
+ start: beging[:start],
722
+ char_start: beging[:char_start],
723
+ end: ending[:end],
724
+ char_end: ending[:char_end]
725
+ }
726
+ end
727
+
728
+ # command is a parser event representing a method call with arguments and
729
+ # no parentheses. It accepts as arguments the name of the method and the
730
+ # arguments being passed to the method.
731
+ def on_command(ident, args)
732
+ {
733
+ type: :command,
734
+ body: [ident, args],
735
+ start: ident[:start],
736
+ char_start: ident[:char_start],
737
+ end: args[:end],
738
+ char_end: args[:char_end]
739
+ }
740
+ end
741
+
742
+ # command_call is a parser event representing a method call on an object
743
+ # with arguments and no parentheses. It accepts as arguments the receiver
744
+ # of the method, the operator being used to send the method, the name of
745
+ # the method, and the arguments being passed to the method.
746
+ def on_command_call(receiver, oper, ident, args)
747
+ ending = args || ident
748
+
749
+ {
750
+ type: :command_call,
751
+ body: [receiver, oper, ident, args],
752
+ start: receiver[:start],
753
+ char_start: receiver[:char_start],
754
+ end: ending[:end],
755
+ char_end: ending[:char_end]
756
+ }
757
+ end
758
+
759
+ # A const_path_field is a parser event that is always the child of some
760
+ # kind of assignment. It represents when you're assigning to a constant
761
+ # that is being referenced as a child of another variable. For example:
762
+ #
763
+ # foo::X = 1
764
+ #
765
+ def on_const_path_field(left, const)
766
+ {
767
+ type: :const_path_field,
768
+ body: [left, const],
769
+ start: left[:start],
770
+ char_start: left[:char_start],
771
+ end: const[:end],
772
+ char_end: const[:char_end]
773
+ }
774
+ end
775
+
776
+ # A const_path_ref is a parser event that is a very similar to
777
+ # const_path_field except that it is not involved in an assignment. It
778
+ # looks like the following example:
779
+ #
780
+ # foo::X
781
+ #
782
+ def on_const_path_ref(left, const)
783
+ {
784
+ type: :const_path_ref,
785
+ body: [left, const],
786
+ start: left[:start],
787
+ char_start: left[:char_start],
788
+ end: const[:end],
789
+ char_end: const[:char_end]
790
+ }
791
+ end
792
+
793
+ # A const_ref is a parser event that represents the name of the constant
794
+ # being used in a class or module declaration. In the following example it
795
+ # is the @const scanner event that has the contents of Foo.
796
+ #
797
+ # class Foo; end
798
+ #
799
+ def on_const_ref(const)
800
+ const.merge(type: :const_ref, body: [const])
801
+ end
802
+
803
+ # A def is a parser event that represents defining a regular method on the
804
+ # current self object. It accepts as arguments the ident (the name of the
805
+ # method being defined), the params (the parameter declaration for the
806
+ # method), and a bodystmt node which represents the statements inside the
807
+ # method. As an example, here are the parts that go into this:
808
+ #
809
+ # def foo(bar) do baz end
810
+ # │ │ │
811
+ # │ │ └> bodystmt
812
+ # │ └> params
813
+ # └> ident
814
+ #
815
+ def on_def(ident, params, bodystmt)
816
+ # Make sure to delete this scanner event in case you're defining something
817
+ # like def class which would lead to this being a kw and causing all kinds
818
+ # of trouble
819
+ scanner_events.delete(ident)
820
+
821
+ if params[:type] == :params && !params[:body].any?
822
+ location = ident[:char_end]
823
+ params.merge!(char_start: location, char_end: location)
824
+ end
825
+
826
+ beging = find_scanner_event(:@kw, 'def')
827
+ ending = find_scanner_event(:@kw, 'end')
828
+
829
+ bodystmt.bind(
830
+ find_next_statement_start(params[:char_end]),
831
+ ending[:char_start]
832
+ )
833
+
834
+ {
835
+ type: :def,
836
+ body: [ident, params, bodystmt],
837
+ start: beging[:start],
838
+ char_start: beging[:char_start],
839
+ end: ending[:end],
840
+ char_end: ending[:char_end]
841
+ }
842
+ end
843
+
844
+ # A defs is a parser event that represents defining a singleton method on
845
+ # an object. It accepts the same arguments as the def event, as well as
846
+ # the target and operator that on which this method is being defined. As
847
+ # an example, here are the parts that go into this:
848
+ #
849
+ # def foo.bar(baz) do baz end
850
+ # │ │ │ │ │
851
+ # │ │ │ │ │
852
+ # │ │ │ │ └> bodystmt
853
+ # │ │ │ └> params
854
+ # │ │ └> ident
855
+ # │ └> oper
856
+ # └> target
857
+ #
858
+ def on_defs(target, oper, ident, params, bodystmt)
859
+ # Make sure to delete this scanner event in case you're defining something
860
+ # like def class which would lead to this being a kw and causing all kinds
861
+ # of trouble
862
+ scanner_events.delete(ident)
863
+
864
+ if params[:type] == :params && !params[:body].any?
865
+ location = ident[:char_end]
866
+ params.merge!(char_start: location, char_end: location)
867
+ end
868
+
869
+ beging = find_scanner_event(:@kw, 'def')
870
+ ending = find_scanner_event(:@kw, 'end')
871
+
872
+ bodystmt.bind(
873
+ find_next_statement_start(params[:char_end]),
874
+ ending[:char_start]
875
+ )
876
+
877
+ {
878
+ type: :defs,
879
+ body: [target, oper, ident, params, bodystmt],
880
+ start: beging[:start],
881
+ char_start: beging[:char_start],
882
+ end: ending[:end],
883
+ char_end: ending[:char_end]
884
+ }
885
+ end
886
+
887
+ # A defined node represents the rather unique defined? operator. It can be
888
+ # used with and without parentheses. If they're present, we use them to
889
+ # determine our bounds, otherwise we use the value that's being passed to
890
+ # the operator.
891
+ def on_defined(value)
892
+ beging = find_scanner_event(:@kw, 'defined?')
893
+
894
+ paren = source[beging[:char_end]...value[:char_start]].include?('(')
895
+ ending = paren ? find_scanner_event(:@rparen) : value
896
+
897
+ beging.merge!(
898
+ type: :defined,
899
+ body: [value],
900
+ end: ending[:end],
901
+ char_end: ending[:char_end]
902
+ )
903
+ end
904
+
905
+ # do_block is a parser event that represents passing a block to a method
906
+ # call using the do..end keywords. It accepts as arguments an optional
907
+ # block_var event that represents any parameters to the block as well as
908
+ # a bodystmt event that represents the statements inside the block.
909
+ def on_do_block(block_var, bodystmt)
910
+ beging = find_scanner_event(:@kw, 'do')
911
+ ending = find_scanner_event(:@kw, 'end')
912
+
913
+ bodystmt.bind((block_var || beging)[:char_end], ending[:char_start])
914
+
915
+ {
916
+ type: :do_block,
917
+ body: [block_var, bodystmt],
918
+ start: beging[:start],
919
+ char_start: beging[:char_start],
920
+ end: ending[:end],
921
+ char_end: ending[:char_end]
922
+ }
923
+ end
924
+
925
+ # dot2 is a parser event that represents using the .. operator between two
926
+ # expressions. Usually this is to create a range object but sometimes it's to
927
+ # use the flip-flop operator.
928
+ def on_dot2(left, right)
929
+ operator = find_scanner_event(:@op, '..')
930
+
931
+ beging = left || operator
932
+ ending = right || operator
933
+
934
+ {
935
+ type: :dot2,
936
+ body: [left, right],
937
+ start: beging[:start],
938
+ char_start: beging[:char_start],
939
+ end: ending[:end],
940
+ char_end: ending[:char_end]
941
+ }
942
+ end
943
+
944
+ # dot3 is a parser event that represents using the ... operator between two
945
+ # expressions. Usually this is to create a range object but sometimes it's to
946
+ # use the flip-flop operator.
947
+ def on_dot3(left, right)
948
+ operator = find_scanner_event(:@op, '...')
949
+
950
+ beging = left || operator
951
+ ending = right || operator
952
+
953
+ {
954
+ type: :dot3,
955
+ body: [left, right],
956
+ start: beging[:start],
957
+ char_start: beging[:char_start],
958
+ end: ending[:end],
959
+ char_end: ending[:char_end]
960
+ }
961
+ end
962
+
963
+ # A dyna_symbol is a parser event that represents a symbol literal that
964
+ # uses quotes to interpolate its value. For example, if you had a variable
965
+ # foo and you wanted a symbol that contained its value, you would write:
966
+ #
967
+ # :"#{foo}"
968
+ #
969
+ # As such, they accept as one argument a string node, which is the same
970
+ # node that gets accepted into a string_literal (since we're basically
971
+ # talking about a string literal with a : character at the beginning).
972
+ #
973
+ # They can also come in another flavor which is a dynamic symbol as a hash
974
+ # key. This is kind of an interesting syntax which results in us having to
975
+ # look for a @label_end scanner event instead to get our bearings. That
976
+ # kind of code would look like:
977
+ #
978
+ # { "#{foo}": bar }
979
+ #
980
+ # which would be the same symbol as above.
981
+ def on_dyna_symbol(string)
982
+ if scanner_events.any? { |event| event[:type] == :@symbeg }
983
+ # A normal dynamic symbol
984
+ beging = find_scanner_event(:@symbeg)
985
+ ending = find_scanner_event(:@tstring_end)
986
+
987
+ beging.merge(
988
+ type: :dyna_symbol,
989
+ quote: beging[:body][1],
990
+ body: string[:body],
991
+ end: ending[:end],
992
+ char_end: ending[:char_end]
993
+ )
994
+ else
995
+ # A dynamic symbol as a hash key
996
+ beging = find_scanner_event(:@tstring_beg)
997
+ ending = find_scanner_event(:@label_end)
998
+
999
+ string.merge!(
1000
+ type: :dyna_symbol,
1001
+ quote: ending[:body][0],
1002
+ start: beging[:start],
1003
+ char_start: beging[:char_start],
1004
+ end: ending[:end],
1005
+ char_end: ending[:char_end]
1006
+ )
1007
+ end
1008
+ end
1009
+
1010
+ # else can either end with an end keyword (in which case we'll want to
1011
+ # consume that event) or it can end with an ensure keyword (in which case
1012
+ # we'll leave that to the ensure to handle).
1013
+ def find_else_ending
1014
+ index =
1015
+ scanner_events.rindex do |event|
1016
+ event[:type] == :@kw && %w[end ensure].include?(event[:body])
1017
+ end
1018
+
1019
+ event = scanner_events[index]
1020
+ event[:body] == 'end' ? scanner_events.delete_at(index) : event
1021
+ end
1022
+
1023
+ # else is a parser event that represents the end of a if, unless, or begin
1024
+ # chain. It accepts as an argument the statements that are contained
1025
+ # within the else clause.
1026
+ def on_else(stmts)
1027
+ beging = find_scanner_event(:@kw, 'else')
1028
+ ending = find_else_ending
1029
+
1030
+ stmts.bind(beging[:char_end], ending[:char_start])
1031
+
1032
+ {
1033
+ type: :else,
1034
+ body: [stmts],
1035
+ start: beging[:start],
1036
+ char_start: beging[:char_start],
1037
+ end: ending[:end],
1038
+ char_end: ending[:char_end]
1039
+ }
1040
+ end
1041
+
1042
+ # elsif is a parser event that represents another clause in an if chain.
1043
+ # It accepts as arguments the predicate of the else if, the statements
1044
+ # that are contained within the else if clause, and the optional
1045
+ # consequent clause.
1046
+ def on_elsif(predicate, stmts, consequent)
1047
+ beging = find_scanner_event(:@kw, 'elsif')
1048
+ ending = consequent || find_scanner_event(:@kw, 'end')
1049
+
1050
+ stmts.bind(predicate[:char_end], ending[:char_start])
1051
+
1052
+ {
1053
+ type: :elsif,
1054
+ body: [predicate, stmts, consequent],
1055
+ start: beging[:start],
1056
+ char_start: beging[:char_start],
1057
+ end: ending[:end],
1058
+ char_end: ending[:char_end]
1059
+ }
1060
+ end
1061
+
1062
+ # embdocs are long comments that are surrounded by =begin..=end. They
1063
+ # cannot be nested, so we don't need to worry about keeping a stack around
1064
+ # like we do with heredocs. Instead we can just track the current embdoc
1065
+ # and add to it as we get content. It always starts with this scanner
1066
+ # event, so here we'll initialize the current embdoc.
1067
+ def on_embdoc_beg(value)
1068
+ @embdoc = {
1069
+ type: :@embdoc,
1070
+ value: value,
1071
+ start: lineno,
1072
+ char_start: char_pos
1073
+ }
1074
+ end
1075
+
1076
+ # This is a scanner event that gets hit when we're inside an embdoc and
1077
+ # receive a new line of content. Here we are guaranteed to already have
1078
+ # initialized the @embdoc variable so we can just append the new line onto
1079
+ # the existing content.
1080
+ def on_embdoc(value)
1081
+ @embdoc[:value] << value
1082
+ end
1083
+
1084
+ # This is the final scanner event for embdocs. It receives the =end. Here
1085
+ # we can finalize the embdoc with its location information and the final
1086
+ # piece of the string. We then add it to the list of comments so that
1087
+ # prettier can place it into the final source string.
1088
+ def on_embdoc_end(value)
1089
+ @comments <<
1090
+ @embdoc.merge!(
1091
+ value: @embdoc[:value] << value.chomp,
1092
+ end: lineno,
1093
+ char_end: char_pos + value.length - 1
1094
+ )
1095
+
1096
+ @embdoc = nil
1097
+ end
1098
+
1099
+ # ensure is a parser event that represents the use of the ensure keyword
1100
+ # and its subsequent statements.
1101
+ def on_ensure(stmts)
1102
+ beging = find_scanner_event(:@kw, 'ensure')
1103
+
1104
+ # Specifically not using find_scanner_event here because we don't want to
1105
+ # consume the :@end event, because that would break def..ensure..end chains.
1106
+ index =
1107
+ scanner_events.rindex do |scanner_event|
1108
+ scanner_event[:type] == :@kw && scanner_event[:body] == 'end'
1109
+ end
1110
+
1111
+ ending = scanner_events[index]
1112
+ stmts.bind(
1113
+ find_next_statement_start(beging[:char_end]),
1114
+ ending[:char_start]
1115
+ )
1116
+
1117
+ {
1118
+ type: :ensure,
1119
+ body: [beging, stmts],
1120
+ start: beging[:start],
1121
+ char_start: beging[:char_start],
1122
+ end: ending[:end],
1123
+ char_end: ending[:char_end]
1124
+ }
1125
+ end
1126
+
1127
+ # An excessed_comma is a special kind of parser event that represents a comma
1128
+ # at the end of a list of parameters. It's a very strange node. It accepts a
1129
+ # different number of arguments depending on Ruby version, which is why we
1130
+ # have the anonymous splat there.
1131
+ def on_excessed_comma(*)
1132
+ find_scanner_event(:@comma).merge!(type: :excessed_comma)
1133
+ end
1134
+
1135
+ # An fcall is a parser event that represents the piece of a method call
1136
+ # that comes before any arguments (i.e., just the name of the method).
1137
+ def on_fcall(ident)
1138
+ ident.merge(type: :fcall, body: [ident])
1139
+ end
1140
+
1141
+ # A field is a parser event that is always the child of an assignment. It
1142
+ # accepts as arguments the left side of operation, the operator (. or ::),
1143
+ # and the right side of the operation. For example:
1144
+ #
1145
+ # foo.x = 1
1146
+ #
1147
+ def on_field(left, oper, right)
1148
+ {
1149
+ type: :field,
1150
+ body: [left, oper, right],
1151
+ start: left[:start],
1152
+ char_start: left[:char_start],
1153
+ end: right[:end],
1154
+ char_end: right[:char_end]
1155
+ }
1156
+ end
1157
+
1158
+ # for is a parser event that represents using the somewhat esoteric for
1159
+ # loop. It accepts as arguments an ident which is the iterating variable,
1160
+ # an enumerable for that which is being enumerated, and a stmts event that
1161
+ # represents the statements inside the for loop.
1162
+ def on_for(ident, enumerable, stmts)
1163
+ beging = find_scanner_event(:@kw, 'for')
1164
+ ending = find_scanner_event(:@kw, 'end')
1165
+
1166
+ stmts.bind(enumerable[:char_end], ending[:char_start])
1167
+
1168
+ {
1169
+ type: :for,
1170
+ body: [ident, enumerable, stmts],
1171
+ start: beging[:start],
1172
+ char_start: beging[:char_start],
1173
+ end: ending[:end],
1174
+ char_end: ending[:char_end]
1175
+ }
1176
+ end
1177
+
1178
+ # hash is a parser event that represents a hash literal. It accepts as an
1179
+ # argument an optional assoclist_from_args event which contains the
1180
+ # contents of the hash.
1181
+ def on_hash(assoclist_from_args)
1182
+ beging = find_scanner_event(:@lbrace)
1183
+ ending = find_scanner_event(:@rbrace)
1184
+
1185
+ if assoclist_from_args
1186
+ # Here we're going to expand out the location information for the assocs
1187
+ # node so that it can grab up any remaining comments inside the hash.
1188
+ assoclist_from_args.merge!(
1189
+ char_start: beging[:char_end], char_end: ending[:char_start]
1190
+ )
1191
+ end
1192
+
1193
+ {
1194
+ type: :hash,
1195
+ body: [assoclist_from_args],
1196
+ start: beging[:start],
1197
+ char_start: beging[:char_start],
1198
+ end: ending[:end],
1199
+ char_end: ending[:char_end]
1200
+ }
1201
+ end
1202
+
1203
+ # This is a scanner event that represents the beginning of the heredoc. It
1204
+ # includes the declaration (which we call beging here, which is just short
1205
+ # for beginning). The declaration looks something like <<-HERE or <<~HERE.
1206
+ # If the downcased version of the declaration actually matches an existing
1207
+ # prettier parser, we'll later attempt to print it using that parser and
1208
+ # printer through our embed function.
1209
+ def on_heredoc_beg(beging)
1210
+ location = {
1211
+ start: lineno,
1212
+ end: lineno,
1213
+ char_start: char_pos,
1214
+ char_end: char_pos + beging.length + 1
1215
+ }
1216
+
1217
+ # Here we're going to artificially create an extra node type so that if
1218
+ # there are comments after the declaration of a heredoc, they get printed.
1219
+ location
1220
+ .merge(
1221
+ type: :heredoc,
1222
+ beging: location.merge(type: :@heredoc_beg, body: beging)
1223
+ )
1224
+ .tap { |node| @heredocs << node }
1225
+ end
1226
+
1227
+ # This is a parser event that occurs when you're using a heredoc with a
1228
+ # tilde. These are considered `heredoc_dedent` nodes, whereas the hyphen
1229
+ # heredocs show up as string literals.
1230
+ def on_heredoc_dedent(string, _width)
1231
+ @heredocs[-1].merge!(body: string[:body])
1232
+ end
1233
+
1234
+ # This is a scanner event that represents the end of the heredoc.
1235
+ def on_heredoc_end(ending)
1236
+ @heredocs[-1].merge!(ending: ending.chomp, end: lineno, char_end: char_pos)
1237
+ end
1238
+
1239
+ # hshptn is a parser event that represents matching against a hash pattern
1240
+ # using the Ruby 2.7+ pattern matching syntax.
1241
+ def on_hshptn(const, kw, kwrest)
1242
+ pieces = [const, kw, kwrest].flatten(2).compact
1243
+
1244
+ {
1245
+ type: :hshptn,
1246
+ body: [const, kw, kwrest],
1247
+ start: pieces[0][:start],
1248
+ char_start: pieces[0][:char_start],
1249
+ end: pieces[-1][:end],
1250
+ char_end: pieces[-1][:char_end]
1251
+ }
1252
+ end
1253
+
1254
+ # if is a parser event that represents the first clause in an if chain.
1255
+ # It accepts as arguments the predicate of the if, the statements that are
1256
+ # contained within the if clause, and the optional consequent clause.
1257
+ def on_if(predicate, stmts, consequent)
1258
+ beging = find_scanner_event(:@kw, 'if')
1259
+ ending = consequent || find_scanner_event(:@kw, 'end')
1260
+
1261
+ stmts.bind(predicate[:char_end], ending[:char_start])
1262
+
1263
+ {
1264
+ type: :if,
1265
+ body: [predicate, stmts, consequent],
1266
+ start: beging[:start],
1267
+ char_start: beging[:char_start],
1268
+ end: ending[:end],
1269
+ char_end: ending[:char_end]
1270
+ }
1271
+ end
1272
+
1273
+ # ifop is a parser event that represents a ternary operator. It accepts as
1274
+ # arguments the predicate to the ternary, the truthy clause, and the falsy
1275
+ # clause.
1276
+ def on_ifop(predicate, truthy, falsy)
1277
+ predicate.merge(
1278
+ type: :ifop,
1279
+ body: [predicate, truthy, falsy],
1280
+ end: falsy[:end],
1281
+ char_end: falsy[:char_end]
1282
+ )
1283
+ end
1284
+
1285
+ # if_mod is a parser event that represents the modifier form of an if
1286
+ # statement. It accepts as arguments the predicate of the if and the
1287
+ # statement that are contained within the if clause.
1288
+ def on_if_mod(predicate, statement)
1289
+ find_scanner_event(:@kw, 'if')
1290
+
1291
+ {
1292
+ type: :if_mod,
1293
+ body: [predicate, statement],
1294
+ start: statement[:start],
1295
+ char_start: statement[:char_start],
1296
+ end: predicate[:end],
1297
+ char_end: predicate[:char_end]
1298
+ }
1299
+ end
1300
+
1301
+ # in is a parser event that represents using the in keyword within the
1302
+ # Ruby 2.7+ pattern matching syntax.
1303
+ def on_in(pattern, stmts, consequent)
1304
+ beging = find_scanner_event(:@kw, 'in')
1305
+ ending = consequent || find_scanner_event(:@kw, 'end')
1306
+
1307
+ stmts.bind(beging[:char_end], ending[:char_start])
1308
+
1309
+ beging.merge!(
1310
+ type: :in,
1311
+ body: [pattern, stmts, consequent],
1312
+ end: ending[:end],
1313
+ char_end: ending[:char_end]
1314
+ )
1315
+ end
1316
+
1317
+ # kwrest_param is a parser event that represents defining a parameter in a
1318
+ # method definition that accepts all remaining keyword parameters.
1319
+ def on_kwrest_param(ident)
1320
+ oper = find_scanner_event(:@op, '**')
1321
+ return oper.merge!(type: :kwrest_param, body: [nil]) unless ident
1322
+
1323
+ oper.merge!(
1324
+ type: :kwrest_param,
1325
+ body: [ident],
1326
+ end: ident[:end],
1327
+ char_end: ident[:char_end]
1328
+ )
1329
+ end
1330
+
1331
+ # lambda is a parser event that represents using a "stabby" lambda
1332
+ # literal. It accepts as arguments a params event that represents any
1333
+ # parameters to the lambda and a stmts event that represents the
1334
+ # statements inside the lambda.
1335
+ #
1336
+ # It can be wrapped in either {..} or do..end so we look for either of
1337
+ # those combinations to get our bounds.
1338
+ def on_lambda(params, stmts)
1339
+ beging = find_scanner_event(:@tlambda)
1340
+
1341
+ if scanner_events.any? { |event| event[:type] == :@tlambeg }
1342
+ opening = find_scanner_event(:@tlambeg)
1343
+ closing = find_scanner_event(:@rbrace)
1344
+ else
1345
+ opening = find_scanner_event(:@kw, 'do')
1346
+ closing = find_scanner_event(:@kw, 'end')
1347
+ end
1348
+
1349
+ stmts.bind(opening[:char_end], closing[:char_start])
1350
+
1351
+ {
1352
+ type: :lambda,
1353
+ body: [params, stmts],
1354
+ start: beging[:start],
1355
+ char_start: beging[:char_start],
1356
+ end: closing[:end],
1357
+ char_end: closing[:char_end]
1358
+ }
1359
+ end
1360
+
1361
+ # massign is a parser event that is a parent node of any kind of multiple
1362
+ # assignment. This includes splitting out variables on the left like:
1363
+ #
1364
+ # a, b, c = foo
1365
+ #
1366
+ # as well as splitting out variables on the right, as in:
1367
+ #
1368
+ # foo = a, b, c
1369
+ #
1370
+ # Both sides support splats, as well as variables following them. There's
1371
+ # also slightly odd behavior that you can achieve with the following:
1372
+ #
1373
+ # a, = foo
1374
+ #
1375
+ # In this case a would receive only the first value of the foo enumerable,
1376
+ # in which case we need to explicitly track the comma and add it onto the
1377
+ # child node.
1378
+ def on_massign(left, right)
1379
+ if source[left[:char_end]...right[:char_start]].strip.start_with?(',')
1380
+ left[:comma] = true
1381
+ end
1382
+
1383
+ {
1384
+ type: :massign,
1385
+ body: [left, right],
1386
+ start: left[:start],
1387
+ char_start: left[:char_start],
1388
+ end: right[:end],
1389
+ char_end: right[:char_end]
1390
+ }
1391
+ end
1392
+
1393
+ # method_add_arg is a parser event that represents a method call with
1394
+ # arguments and parentheses. It accepts as arguments the method being called
1395
+ # and the arg_paren event that contains the arguments to the method.
1396
+ def on_method_add_arg(fcall, arg_paren)
1397
+ # You can hit this if you are passing no arguments to a method that ends in
1398
+ # a question mark. Because it knows it has to be a method and not a local
1399
+ # variable. In that case we can just use the location information straight
1400
+ # from the fcall.
1401
+ if arg_paren[:type] == :args
1402
+ return fcall.merge(type: :method_add_arg, body: [fcall, arg_paren])
1403
+ end
1404
+
1405
+ {
1406
+ type: :method_add_arg,
1407
+ body: [fcall, arg_paren],
1408
+ start: fcall[:start],
1409
+ char_start: fcall[:char_start],
1410
+ end: arg_paren[:end],
1411
+ char_end: arg_paren[:char_end]
1412
+ }
1413
+ end
1414
+
1415
+ # method_add_block is a parser event that represents a method call with a
1416
+ # block argument. It accepts as arguments the method being called and the
1417
+ # block event.
1418
+ def on_method_add_block(method_add_arg, block)
1419
+ {
1420
+ type: :method_add_block,
1421
+ body: [method_add_arg, block],
1422
+ start: method_add_arg[:start],
1423
+ char_start: method_add_arg[:char_start],
1424
+ end: block[:end],
1425
+ char_end: block[:char_end]
1426
+ }
1427
+ end
1428
+
1429
+ # An mlhs_new is a parser event that represents the beginning of the left
1430
+ # side of a multiple assignment. It is followed by any number of mlhs_add
1431
+ # nodes that each represent another variable being assigned.
1432
+ def on_mlhs_new
1433
+ {
1434
+ type: :mlhs,
1435
+ body: [],
1436
+ start: lineno,
1437
+ char_start: char_pos,
1438
+ end: lineno,
1439
+ char_end: char_pos
1440
+ }
1441
+ end
1442
+
1443
+ # An mlhs_add is a parser event that represents adding another variable
1444
+ # onto a list of assignments. It accepts as arguments the parent mlhs node
1445
+ # as well as the part that is being added to the list.
1446
+ def on_mlhs_add(mlhs, part)
1447
+ if mlhs[:body].empty?
1448
+ part.merge(type: :mlhs, body: [part])
1449
+ else
1450
+ mlhs.merge!(
1451
+ body: mlhs[:body] << part, end: part[:end], char_end: part[:char_end]
1452
+ )
1453
+ end
1454
+ end
1455
+
1456
+ # An mlhs_add_post is a parser event that represents adding another set of
1457
+ # variables onto a list of assignments after a splat variable. It accepts
1458
+ # as arguments the previous mlhs_add_star node that represented the splat
1459
+ # as well another mlhs node that represents all of the variables after the
1460
+ # splat.
1461
+ def on_mlhs_add_post(mlhs_add_star, mlhs)
1462
+ mlhs_add_star.merge(
1463
+ type: :mlhs_add_post,
1464
+ body: [mlhs_add_star, mlhs],
1465
+ end: mlhs[:end],
1466
+ char_end: mlhs[:char_end]
1467
+ )
1468
+ end
1469
+
1470
+ # An mlhs_add_star is a parser event that represents a splatted variable
1471
+ # inside of a multiple assignment on the left hand side. It accepts as
1472
+ # arguments the parent mlhs node as well as the part that represents the
1473
+ # splatted variable.
1474
+ def on_mlhs_add_star(mlhs, part)
1475
+ beging = find_scanner_event(:@op, '*')
1476
+ ending = part || beging
1477
+
1478
+ {
1479
+ type: :mlhs_add_star,
1480
+ body: [mlhs, part],
1481
+ start: beging[:start],
1482
+ char_start: beging[:char_start],
1483
+ end: ending[:end],
1484
+ char_end: ending[:char_end]
1485
+ }
1486
+ end
1487
+
1488
+ # An mlhs_paren is a parser event that represents parentheses being used
1489
+ # to deconstruct values in a multiple assignment on the left hand side. It
1490
+ # accepts as arguments the contents of the inside of the parentheses,
1491
+ # which is another mlhs node.
1492
+ def on_mlhs_paren(contents)
1493
+ beging = find_scanner_event(:@lparen)
1494
+ ending = find_scanner_event(:@rparen)
1495
+
1496
+ if source[beging[:char_end]...ending[:char_start]].strip.end_with?(',')
1497
+ contents[:comma] = true
1498
+ end
1499
+
1500
+ {
1501
+ type: :mlhs_paren,
1502
+ body: [contents],
1503
+ start: beging[:start],
1504
+ char_start: beging[:char_start],
1505
+ end: ending[:end],
1506
+ char_end: ending[:char_end]
1507
+ }
1508
+ end
1509
+
1510
+ # module is a parser event that represents defining a module. It accepts
1511
+ # as arguments the name of the module and the bodystmt event that
1512
+ # represents the statements evaluated within the context of the module.
1513
+ def on_module(const, bodystmt)
1514
+ beging = find_scanner_event(:@kw, 'module')
1515
+ ending = find_scanner_event(:@kw, 'end')
1516
+
1517
+ bodystmt.bind(
1518
+ find_next_statement_start(const[:char_end]),
1519
+ ending[:char_start]
1520
+ )
1521
+
1522
+ {
1523
+ type: :module,
1524
+ body: [const, bodystmt],
1525
+ start: beging[:start],
1526
+ char_start: beging[:char_start],
1527
+ end: ending[:end],
1528
+ char_end: ending[:char_end]
1529
+ }
1530
+ end
1531
+
1532
+ # An mrhs_new is a parser event that represents the beginning of a list of
1533
+ # values that are being assigned within a multiple assignment node. It can
1534
+ # be followed by any number of mrhs_add nodes that we'll build up into an
1535
+ # array body.
1536
+ def on_mrhs_new
1537
+ {
1538
+ type: :mrhs,
1539
+ body: [],
1540
+ start: lineno,
1541
+ char_start: char_pos,
1542
+ end: lineno,
1543
+ char_end: char_pos
1544
+ }
1545
+ end
1546
+
1547
+ # An mrhs_add is a parser event that represents adding another value onto
1548
+ # a list on the right hand side of a multiple assignment.
1549
+ def on_mrhs_add(mrhs, part)
1550
+ if mrhs[:body].empty?
1551
+ part.merge(type: :mrhs, body: [part])
1552
+ else
1553
+ mrhs.merge!(
1554
+ body: mrhs[:body] << part, end: part[:end], char_end: part[:char_end]
1555
+ )
1556
+ end
1557
+ end
1558
+
1559
+ # An mrhs_add_star is a parser event that represents using the splat
1560
+ # operator to expand out a value on the right hand side of a multiple
1561
+ # assignment.
1562
+ def on_mrhs_add_star(mrhs, part)
1563
+ beging = find_scanner_event(:@op, '*')
1564
+ ending = part || beging
1565
+
1566
+ {
1567
+ type: :mrhs_add_star,
1568
+ body: [mrhs, part],
1569
+ start: beging[:start],
1570
+ char_start: beging[:char_start],
1571
+ end: ending[:end],
1572
+ char_end: ending[:char_end]
1573
+ }
1574
+ end
1575
+
1576
+ # An mrhs_new_from_args is a parser event that represents the shorthand
1577
+ # of a multiple assignment that allows you to assign values using just
1578
+ # commas as opposed to assigning from an array. For example, in the
1579
+ # following segment the right hand side of the assignment would trigger
1580
+ # this event:
1581
+ #
1582
+ # foo = 1, 2, 3
1583
+ #
1584
+ def on_mrhs_new_from_args(args)
1585
+ args.merge(type: :mrhs_new_from_args, body: [args])
1586
+ end
1587
+
1588
+ # next is a parser event that represents using the next keyword. It
1589
+ # accepts as an argument an args or args_add_block event that contains all
1590
+ # of the arguments being passed to the next.
1591
+ def on_next(args_add_block)
1592
+ find_scanner_event(:@kw, 'next').merge!(
1593
+ type: :next,
1594
+ body: [args_add_block],
1595
+ end: args_add_block[:end],
1596
+ char_end: args_add_block[:char_end]
1597
+ )
1598
+ end
1599
+
1600
+ # opassign is a parser event that represents assigning something to a
1601
+ # variable or constant using an operator like += or ||=. It accepts as
1602
+ # arguments the left side of the expression before the operator, the
1603
+ # operator itself, and the right side of the expression.
1604
+ def on_opassign(left, oper, right)
1605
+ left.merge(
1606
+ type: :opassign,
1607
+ body: [left, oper, right],
1608
+ end: right[:end],
1609
+ char_end: right[:char_end]
1610
+ )
1611
+ end
1612
+
1613
+ # params is a parser event that represents defining parameters on a
1614
+ # method. They have a somewhat interesting structure in that they are an
1615
+ # array of arrays where the position in the top-level array indicates the
1616
+ # type of param and the subarray is the list of parameters of that type.
1617
+ # We therefore have to flatten them down to get to the location.
1618
+ def on_params(*types)
1619
+ flattened = types.flatten(2).select { |type| type.is_a?(Hash) }
1620
+ location =
1621
+ if flattened.any?
1622
+ {
1623
+ start: flattened[0][:start],
1624
+ char_start: flattened[0][:char_start],
1625
+ end: flattened[-1][:end],
1626
+ char_end: flattened[-1][:char_end]
1627
+ }
1628
+ else
1629
+ { start: lineno, char_start: char_pos, end: lineno, char_end: char_pos }
1630
+ end
1631
+
1632
+ location.merge!(type: :params, body: types)
1633
+ end
1634
+
1635
+ # A paren is a parser event that represents using parentheses pretty much
1636
+ # anywhere in a Ruby program. It accepts as arguments the contents, which
1637
+ # can be either params or statements.
1638
+ def on_paren(contents)
1639
+ ending = find_scanner_event(:@rparen)
1640
+
1641
+ find_scanner_event(:@lparen).merge!(
1642
+ type: :paren,
1643
+ body: [contents],
1644
+ end: ending[:end],
1645
+ char_end: ending[:char_end]
1646
+ )
1647
+ end
1648
+
1649
+ # The program node is the very top of the AST. Here we'll attach all of
1650
+ # the comments that we've gathered up over the course of parsing the
1651
+ # source string. We'll also attach on the __END__ content if there was
1652
+ # some found at the end of the source string.
1653
+ def on_program(stmts)
1654
+ range = {
1655
+ start: 1,
1656
+ end: lines.length,
1657
+ char_start: 0,
1658
+ char_end: source.length
1659
+ }
1660
+
1661
+ stmts[:body] << @__end__ if @__end__
1662
+ stmts.bind(0, source.length)
1663
+
1664
+ range.merge(type: :program, body: [stmts], comments: @comments)
1665
+ end
1666
+
1667
+ # qsymbols_new is a parser event that represents the beginning of a symbol
1668
+ # literal array, like %i[one two three]. It can be followed by any number
1669
+ # of qsymbols_add events, which we'll append onto an array body.
1670
+ def on_qsymbols_new
1671
+ find_scanner_event(:@qsymbols_beg).merge!(type: :qsymbols, body: [])
1672
+ end
1673
+
1674
+ # qsymbols_add is a parser event that represents an element inside of a
1675
+ # symbol literal array like %i[one two three]. It accepts as arguments the
1676
+ # parent qsymbols node as well as a tstring_content scanner event
1677
+ # representing the bare words.
1678
+ def on_qsymbols_add(qsymbols, tstring_content)
1679
+ qsymbols.merge!(
1680
+ body: qsymbols[:body] << tstring_content,
1681
+ end: tstring_content[:end],
1682
+ char_end: tstring_content[:char_end]
1683
+ )
1684
+ end
1685
+
1686
+ # qwords_new is a parser event that represents the beginning of a string
1687
+ # literal array, like %w[one two three]. It can be followed by any number
1688
+ # of qwords_add events, which we'll append onto an array body.
1689
+ def on_qwords_new
1690
+ find_scanner_event(:@qwords_beg).merge!(type: :qwords, body: [])
1691
+ end
1692
+
1693
+ # qsymbols_add is a parser event that represents an element inside of a
1694
+ # symbol literal array like %i[one two three]. It accepts as arguments the
1695
+ # parent qsymbols node as well as a tstring_content scanner event
1696
+ # representing the bare words.
1697
+ def on_qwords_add(qwords, tstring_content)
1698
+ qwords.merge!(
1699
+ body: qwords[:body] << tstring_content,
1700
+ end: tstring_content[:end],
1701
+ char_end: tstring_content[:char_end]
1702
+ )
1703
+ end
1704
+
1705
+ # redo is a parser event that represents the bare redo keyword. It has no
1706
+ # body as it accepts no arguments.
1707
+ def on_redo
1708
+ find_scanner_event(:@kw, 'redo').merge!(type: :redo)
1709
+ end
1710
+
1711
+ # regexp_new is a parser event that represents the beginning of a regular
1712
+ # expression literal, like /foo/. It can be followed by any number of
1713
+ # regexp_add events, which we'll append onto an array body.
1714
+ def on_regexp_new
1715
+ find_scanner_event(:@regexp_beg).merge!(type: :regexp, body: [])
1716
+ end
1717
+
1718
+ # regexp_add is a parser event that represents a piece of a regular
1719
+ # body. It accepts as arguments the parent regexp node as well as a
1720
+ # tstring_content scanner event representing string content or a
1721
+ # string_embexpr parser event representing interpolated content.
1722
+ def on_regexp_add(regexp, piece)
1723
+ regexp.merge!(
1724
+ body: regexp[:body] << piece,
1725
+ end: regexp[:end],
1726
+ char_end: regexp[:char_end]
1727
+ )
1728
+ end
1729
+
1730
+ # regexp_literal is a parser event that represents a regular expression.
1731
+ # It accepts as arguments a regexp node which is a built-up array of
1732
+ # pieces that go into the regexp content, as well as the ending used to
1733
+ # close out the regexp which includes any modifiers.
1734
+ def on_regexp_literal(regexp, ending)
1735
+ regexp.merge!(
1736
+ type: :regexp_literal,
1737
+ ending: ending[:body],
1738
+ end: ending[:end],
1739
+ char_end: ending[:char_end]
1740
+ )
1741
+ end
1742
+
1743
+ # rescue is a special kind of node where you have a rescue chain but it
1744
+ # doesn't really have all of the information that it needs in order to
1745
+ # determine its ending. Therefore it relies on its parent bodystmt node to
1746
+ # report its ending to it.
1747
+ class Rescue < SimpleDelegator
1748
+ def bind_end(char_end)
1749
+ merge!(char_end: char_end)
1750
+
1751
+ stmts = self[:body][2]
1752
+ consequent = self[:body][3]
1753
+
1754
+ if consequent
1755
+ consequent.bind_end(char_end)
1756
+ stmts.bind_end(consequent[:char_start])
1757
+ else
1758
+ stmts.bind_end(char_end)
1759
+ end
1760
+ end
1761
+ end
1762
+
1763
+ # rescue is a parser event that represents the use of the rescue keyword
1764
+ # inside of a bodystmt.
1765
+ def on_rescue(exceptions, variable, stmts, consequent)
1766
+ beging = find_scanner_event(:@kw, 'rescue')
1767
+
1768
+ last_exception = exceptions.is_a?(Array) ? exceptions[-1] : exceptions
1769
+ last_node = variable || last_exception || beging
1770
+
1771
+ stmts.bind(find_next_statement_start(last_node[:char_end]), char_pos)
1772
+
1773
+ Rescue.new(
1774
+ beging.merge!(
1775
+ type: :rescue,
1776
+ body: [exceptions, variable, stmts, consequent],
1777
+ end: lineno,
1778
+ char_end: char_pos
1779
+ )
1780
+ )
1781
+ end
1782
+
1783
+ # rescue_mod represents the modifier form of a rescue clause. It accepts as
1784
+ # arguments the statement that may raise an error and the value that should
1785
+ # be used if it does.
1786
+ def on_rescue_mod(statement, rescued)
1787
+ find_scanner_event(:@kw, 'rescue')
1788
+
1789
+ {
1790
+ type: :rescue_mod,
1791
+ body: [statement, rescued],
1792
+ start: statement[:start],
1793
+ char_start: statement[:char_start],
1794
+ end: rescued[:end],
1795
+ char_end: rescued[:char_end]
1796
+ }
1797
+ end
1798
+
1799
+ # rest_param is a parser event that represents defining a parameter in a
1800
+ # method definition that accepts all remaining positional parameters. It
1801
+ # accepts as an argument an optional identifier for the parameter. If it
1802
+ # is omitted, then we're just using the plain operator.
1803
+ def on_rest_param(ident)
1804
+ oper = find_scanner_event(:@op, '*')
1805
+ return oper.merge!(type: :rest_param, body: [nil]) unless ident
1806
+
1807
+ oper.merge!(
1808
+ type: :rest_param,
1809
+ body: [ident],
1810
+ end: ident[:end],
1811
+ char_end: ident[:char_end]
1812
+ )
1813
+ end
1814
+
1815
+ # retry is a parser event that represents the bare retry keyword. It has
1816
+ # no body as it accepts no arguments.
1817
+ def on_retry
1818
+ find_scanner_event(:@kw, 'retry').merge!(type: :retry)
1819
+ end
1820
+
1821
+ # return is a parser event that represents using the return keyword with
1822
+ # arguments. It accepts as an argument an args_add_block event that
1823
+ # contains all of the arguments being passed.
1824
+ def on_return(args_add_block)
1825
+ find_scanner_event(:@kw, 'return').merge!(
1826
+ type: :return,
1827
+ body: [args_add_block],
1828
+ end: args_add_block[:end],
1829
+ char_end: args_add_block[:char_end]
1830
+ )
1831
+ end
1832
+
1833
+ # return0 is a parser event that represents the bare return keyword. It
1834
+ # has no body as it accepts no arguments. This is as opposed to the return
1835
+ # parser event, which is the version where you're returning one or more
1836
+ # values.
1837
+ def on_return0
1838
+ find_scanner_event(:@kw, 'return').merge!(type: :return0)
1839
+ end
1840
+
1841
+ # sclass is a parser event that represents a block of statements that
1842
+ # should be evaluated within the context of the singleton class of an
1843
+ # object. It's frequently used to define singleton methods. It looks like
1844
+ # the following example:
1845
+ #
1846
+ # class << self do foo end
1847
+ # │ │
1848
+ # │ └> bodystmt
1849
+ # └> target
1850
+ #
1851
+ def on_sclass(target, bodystmt)
1852
+ beging = find_scanner_event(:@kw, 'class')
1853
+ ending = find_scanner_event(:@kw, 'end')
1854
+
1855
+ bodystmt.bind(
1856
+ find_next_statement_start(target[:char_end]),
1857
+ ending[:char_start]
1858
+ )
1859
+
1860
+ {
1861
+ type: :sclass,
1862
+ body: [target, bodystmt],
1863
+ start: beging[:start],
1864
+ char_start: beging[:char_start],
1865
+ end: ending[:end],
1866
+ char_end: ending[:char_end]
1867
+ }
1868
+ end
1869
+
1870
+ # Everything that has a block of code inside of it has a list of statements.
1871
+ # Normally we would just track those as a node that has an array body, but we
1872
+ # have some special handling in order to handle empty statement lists. They
1873
+ # need to have the right location information, so all of the parent node of
1874
+ # stmts nodes will report back down the location information. We then
1875
+ # propagate that onto void_stmt nodes inside the stmts in order to make sure
1876
+ # all comments get printed appropriately.
1877
+ class Stmts < SimpleDelegator
1878
+ def bind(char_start, char_end)
1879
+ merge!(char_start: char_start, char_end: char_end)
1880
+
1881
+ if self[:body][0][:type] == :void_stmt
1882
+ self[:body][0].merge!(char_start: char_start, char_end: char_start)
1883
+ end
1884
+ end
1885
+
1886
+ def bind_end(char_end)
1887
+ merge!(char_end: char_end)
1888
+ end
1889
+
1890
+ def <<(statement)
1891
+ if self[:body].any?
1892
+ merge!(statement.slice(:end, :char_end))
1893
+ else
1894
+ merge!(statement.slice(:start, :end, :char_start, :char_end))
1895
+ end
1896
+
1897
+ self[:body] << statement
1898
+ self
1899
+ end
1900
+ end
1901
+
1902
+ # stmts_new is a parser event that represents the beginning of a list of
1903
+ # statements within any lexical block. It can be followed by any number of
1904
+ # stmts_add events, which we'll append onto an array body.
1905
+ def on_stmts_new
1906
+ Stmts.new(
1907
+ type: :stmts,
1908
+ body: [],
1909
+ start: lineno,
1910
+ end: lineno,
1911
+ char_start: char_pos,
1912
+ char_end: char_pos
1913
+ )
1914
+ end
1915
+
1916
+ # stmts_add is a parser event that represents a single statement inside a
1917
+ # list of statements within any lexical block. It accepts as arguments the
1918
+ # parent stmts node as well as an stmt which can be any expression in
1919
+ # Ruby.
1920
+ def on_stmts_add(stmts, stmt)
1921
+ stmts << stmt
1922
+ end
1923
+
1924
+ # string_concat is a parser event that represents concatenating two
1925
+ # strings together using a backward slash, as in the following example:
1926
+ #
1927
+ # 'foo' \
1928
+ # 'bar'
1929
+ #
1930
+ def on_string_concat(left, right)
1931
+ {
1932
+ type: :string_concat,
1933
+ body: [left, right],
1934
+ start: left[:start],
1935
+ char_start: left[:char_start],
1936
+ end: right[:end],
1937
+ char_end: right[:char_end]
1938
+ }
1939
+ end
1940
+
1941
+ # string_content is a parser event that represents the beginning of the
1942
+ # contents of a string, which will either be embedded inside of a
1943
+ # string_literal or a dyna_symbol node. It will have an array body so that
1944
+ # we can build up a list of @tstring_content, string_embexpr, and
1945
+ # string_dvar nodes.
1946
+ def on_string_content
1947
+ {
1948
+ type: :string,
1949
+ body: [],
1950
+ start: lineno,
1951
+ end: lineno,
1952
+ char_start: char_pos,
1953
+ char_end: char_pos
1954
+ }
1955
+ end
1956
+
1957
+ # string_add is a parser event that represents a piece of a string. It
1958
+ # could be plain @tstring_content, string_embexpr, or string_dvar nodes.
1959
+ # It accepts as arguments the parent string node as well as the additional
1960
+ # piece of the string.
1961
+ def on_string_add(string, piece)
1962
+ string.merge!(
1963
+ body: string[:body] << piece, end: piece[:end], char_end: piece[:char_end]
1964
+ )
1965
+ end
1966
+
1967
+ # string_dvar is a parser event that represents a very special kind of
1968
+ # interpolation into string. It allows you to take an instance variable,
1969
+ # class variable, or global variable and omit the braces when
1970
+ # interpolating. For example, if you wanted to interpolate the instance
1971
+ # variable @foo into a string, you could do "#@foo".
1972
+ def on_string_dvar(var_ref)
1973
+ find_scanner_event(:@embvar).merge!(
1974
+ type: :string_dvar,
1975
+ body: [var_ref],
1976
+ end: var_ref[:end],
1977
+ char_end: var_ref[:char_end]
1978
+ )
1979
+ end
1980
+
1981
+ # string_embexpr is a parser event that represents interpolated content.
1982
+ # It can go a bunch of different parent nodes, including regexp, strings,
1983
+ # xstrings, heredocs, dyna_symbols, etc. Basically it's anywhere you see
1984
+ # the #{} construct.
1985
+ def on_string_embexpr(stmts)
1986
+ beging = find_scanner_event(:@embexpr_beg)
1987
+ ending = find_scanner_event(:@embexpr_end)
1988
+
1989
+ stmts.bind(beging[:char_end], ending[:char_start])
1990
+
1991
+ {
1992
+ type: :string_embexpr,
1993
+ body: [stmts],
1994
+ start: beging[:start],
1995
+ char_start: beging[:char_start],
1996
+ end: ending[:end],
1997
+ char_end: ending[:char_end]
1998
+ }
1999
+ end
2000
+
2001
+ # String literals are either going to be a normal string or they're going
2002
+ # to be a heredoc if we've just closed a heredoc.
2003
+ def on_string_literal(string)
2004
+ heredoc = @heredocs[-1]
2005
+
2006
+ if heredoc && heredoc[:ending]
2007
+ @heredocs.pop.merge!(body: string[:body])
2008
+ else
2009
+ beging = find_scanner_event(:@tstring_beg)
2010
+ ending = find_scanner_event(:@tstring_end)
2011
+
2012
+ {
2013
+ type: :string_literal,
2014
+ body: string[:body],
2015
+ quote: beging[:body],
2016
+ start: beging[:start],
2017
+ char_start: beging[:char_start],
2018
+ end: ending[:end],
2019
+ char_end: ending[:char_end]
2020
+ }
2021
+ end
2022
+ end
2023
+
2024
+ # A super is a parser event that represents using the super keyword with
2025
+ # any number of arguments. It can optionally use parentheses (represented
2026
+ # by an arg_paren node) or just skip straight to the arguments (with an
2027
+ # args_add_block node).
2028
+ def on_super(contents)
2029
+ find_scanner_event(:@kw, 'super').merge!(
2030
+ type: :super,
2031
+ body: [contents],
2032
+ end: contents[:end],
2033
+ char_end: contents[:char_end]
2034
+ )
2035
+ end
2036
+
2037
+ # A symbol is a parser event that immediately descends from a symbol
2038
+ # literal and contains an ident representing the contents of the symbol.
2039
+ def on_symbol(ident)
2040
+ # What the heck is this here for you ask!? Turns out when Ripper is lexing
2041
+ # source text, it turns symbols into keywords if their contents match, which
2042
+ # will mess up the location information of all of our other nodes.
2043
+ #
2044
+ # So for example instead of { type: :@ident, body: "class" } you would
2045
+ # instead get { type: :@kw, body: "class" } which is all kinds of
2046
+ # problematic.
2047
+ #
2048
+ # In order to take care of this, we explicitly delete this scanner event
2049
+ # from the stack to make sure it doesn't screw things up.
2050
+ scanner_events.pop
2051
+
2052
+ ident.merge(type: :symbol, body: [ident])
2053
+ end
2054
+
2055
+ # A symbol_literal represents a symbol in the system with no interpolation
2056
+ # (as opposed to a dyna_symbol). As its only argument it accepts either a
2057
+ # symbol node (for most cases) or an ident node (in the case that we're
2058
+ # using bare words, as in an alias node like alias foo bar).
2059
+ def on_symbol_literal(contents)
2060
+ if scanner_events[-1] == contents
2061
+ contents.merge(type: :symbol_literal, body: [contents])
2062
+ else
2063
+ beging = find_scanner_event(:@symbeg)
2064
+ contents.merge!(type: :symbol_literal, char_start: beging[:char_start])
2065
+ end
2066
+ end
2067
+
2068
+ # symbols_new is a parser event that represents the beginning of a symbol
2069
+ # literal array that accepts interpolation, like %I[one #{two} three]. It
2070
+ # can be followed by any number of symbols_add events, which we'll append
2071
+ # onto an array body.
2072
+ def on_symbols_new
2073
+ find_scanner_event(:@symbols_beg).merge!(type: :symbols, body: [])
2074
+ end
2075
+
2076
+ # symbols_add is a parser event that represents an element inside of a
2077
+ # symbol literal array that accepts interpolation, like
2078
+ # %I[one #{two} three]. It accepts as arguments the parent symbols node as
2079
+ # well as a word_add parser event.
2080
+ def on_symbols_add(symbols, word_add)
2081
+ symbols.merge!(
2082
+ body: symbols[:body] << word_add,
2083
+ end: word_add[:end],
2084
+ char_end: word_add[:char_end]
2085
+ )
2086
+ end
2087
+
2088
+ # A helper function to find a :: operator for the next two nodes. We do
2089
+ # special handling instead of using find_scanner_event here because we
2090
+ # don't pop off all of the :: operators so you could end up getting the
2091
+ # wrong information if you have for instance ::X::Y::Z.
2092
+ def find_colon2_before(const)
2093
+ index =
2094
+ scanner_events.rindex do |event|
2095
+ event[:type] == :@op && event[:body] == '::' &&
2096
+ event[:char_start] < const[:char_start]
2097
+ end
2098
+
2099
+ scanner_events[index]
2100
+ end
2101
+
2102
+ # A top_const_field is a parser event that is always the child of some
2103
+ # kind of assignment. It represents when you're assigning to a constant
2104
+ # that is being referenced at the top level. For example:
2105
+ #
2106
+ # ::X = 1
2107
+ #
2108
+ def on_top_const_field(const)
2109
+ beging = find_colon2_before(const)
2110
+ const.merge(
2111
+ type: :top_const_field,
2112
+ body: [const],
2113
+ start: beging[:start],
2114
+ char_start: beging[:char_start]
2115
+ )
2116
+ end
2117
+
2118
+ # A top_const_ref is a parser event that is a very similar to
2119
+ # top_const_field except that it is not involved in an assignment. It
2120
+ # looks like the following example:
2121
+ #
2122
+ # ::X
2123
+ #
2124
+ def on_top_const_ref(const)
2125
+ beging = find_colon2_before(const)
2126
+ const.merge(
2127
+ type: :top_const_ref,
2128
+ body: [const],
2129
+ start: beging[:start],
2130
+ char_start: beging[:char_start]
2131
+ )
2132
+ end
2133
+
2134
+ # A unary node represents a unary method being called on an expression, as
2135
+ # in !, ~, or not. We have somewhat special handling of the not operator
2136
+ # since if it has parentheses they don't get reported as a paren node for
2137
+ # some reason.
2138
+ def on_unary(oper, value)
2139
+ if oper == :not
2140
+ node = find_scanner_event(:@kw, 'not')
2141
+
2142
+ paren = source[node[:char_end]...value[:char_start]].include?('(')
2143
+ ending = paren ? find_scanner_event(:@rparen) : value
2144
+
2145
+ node.merge!(
2146
+ type: :unary,
2147
+ oper: oper,
2148
+ body: [value],
2149
+ end: ending[:end],
2150
+ char_end: ending[:char_end],
2151
+ paren: paren
2152
+ )
2153
+ else
2154
+ # Special case instead of using find_scanner_event here. It turns out that
2155
+ # if you have a range that goes from a negative number to a negative
2156
+ # number then you can end up with a .. or a ... that's higher in the
2157
+ # stack. So we need to explicitly disallow those operators.
2158
+ index =
2159
+ scanner_events.rindex do |scanner_event|
2160
+ scanner_event[:type] == :@op &&
2161
+ !%w[.. ...].include?(scanner_event[:body])
2162
+ end
2163
+
2164
+ beging = scanner_events.delete_at(index)
2165
+ beging.merge!(
2166
+ type: :unary,
2167
+ oper: oper[0],
2168
+ body: [value],
2169
+ end: value[:end],
2170
+ char_end: value[:char_end]
2171
+ )
2172
+ end
2173
+ end
2174
+
2175
+ # undef nodes represent using the keyword undef. It accepts as an argument
2176
+ # an array of symbol_literal nodes that represent each message that the
2177
+ # user is attempting to undefine. We use the keyword to get the beginning
2178
+ # location and the last symbol to get the ending.
2179
+ def on_undef(symbol_literals)
2180
+ last = symbol_literals.last
2181
+
2182
+ find_scanner_event(:@kw, 'undef').merge!(
2183
+ type: :undef,
2184
+ body: symbol_literals,
2185
+ end: last[:end],
2186
+ char_end: last[:char_end]
2187
+ )
2188
+ end
2189
+
2190
+ # unless is a parser event that represents the first clause in an unless
2191
+ # chain. It accepts as arguments the predicate of the unless, the
2192
+ # statements that are contained within the unless clause, and the optional
2193
+ # consequent clause.
2194
+ def on_unless(predicate, stmts, consequent)
2195
+ beging = find_scanner_event(:@kw, 'unless')
2196
+ ending = consequent || find_scanner_event(:@kw, 'end')
2197
+
2198
+ stmts.bind(predicate[:char_end], ending[:char_start])
2199
+
2200
+ {
2201
+ type: :unless,
2202
+ body: [predicate, stmts, consequent],
2203
+ start: beging[:start],
2204
+ char_start: beging[:char_start],
2205
+ end: ending[:end],
2206
+ char_end: ending[:char_end]
2207
+ }
2208
+ end
2209
+
2210
+ # unless_mod is a parser event that represents the modifier form of an
2211
+ # unless statement. It accepts as arguments the predicate of the unless
2212
+ # and the statement that are contained within the unless clause.
2213
+ def on_unless_mod(predicate, statement)
2214
+ find_scanner_event(:@kw, 'unless')
2215
+
2216
+ {
2217
+ type: :unless_mod,
2218
+ body: [predicate, statement],
2219
+ start: statement[:start],
2220
+ char_start: statement[:char_start],
2221
+ end: predicate[:end],
2222
+ char_end: predicate[:char_end]
2223
+ }
2224
+ end
2225
+
2226
+ # until is a parser event that represents an until loop. It accepts as
2227
+ # arguments the predicate to the until and the statements that are
2228
+ # contained within the until clause.
2229
+ def on_until(predicate, stmts)
2230
+ beging = find_scanner_event(:@kw, 'until')
2231
+ ending = find_scanner_event(:@kw, 'end')
2232
+
2233
+ stmts.bind(predicate[:char_end], ending[:char_start])
2234
+
2235
+ {
2236
+ type: :until,
2237
+ body: [predicate, stmts],
2238
+ start: beging[:start],
2239
+ char_start: beging[:char_start],
2240
+ end: ending[:end],
2241
+ char_end: ending[:char_end]
2242
+ }
2243
+ end
2244
+
2245
+ # until_mod is a parser event that represents the modifier form of an
2246
+ # until loop. It accepts as arguments the predicate to the until and the
2247
+ # statement that is contained within the until loop.
2248
+ def on_until_mod(predicate, statement)
2249
+ find_scanner_event(:@kw, 'until')
2250
+
2251
+ {
2252
+ type: :until_mod,
2253
+ body: [predicate, statement],
2254
+ start: statement[:start],
2255
+ char_start: statement[:char_start],
2256
+ end: predicate[:end],
2257
+ char_end: predicate[:char_end]
2258
+ }
2259
+ end
2260
+
2261
+ # var_alias is a parser event that represents when you're using the alias
2262
+ # keyword with global variable arguments. You can optionally use
2263
+ # parentheses with this keyword, so we either track the location
2264
+ # information based on those or the final argument to the alias method.
2265
+ def on_var_alias(left, right)
2266
+ beging = find_scanner_event(:@kw, 'alias')
2267
+
2268
+ paren = source[beging[:char_end]...left[:char_start]].include?('(')
2269
+ ending = paren ? find_scanner_event(:@rparen) : right
2270
+
2271
+ {
2272
+ type: :var_alias,
2273
+ body: [left, right],
2274
+ start: beging[:start],
2275
+ char_start: beging[:char_start],
2276
+ end: ending[:end],
2277
+ char_end: ending[:char_end]
2278
+ }
2279
+ end
2280
+
2281
+ # var_ref is a parser event that represents using either a local variable,
2282
+ # a nil literal, a true or false literal, or a numbered block variable.
2283
+ def on_var_ref(contents)
2284
+ contents.merge(type: :var_ref, body: [contents])
2285
+ end
2286
+
2287
+ # var_field is a parser event that represents a variable that is being
2288
+ # assigned a value. As such, it is always a child of an assignment type
2289
+ # node. For example, in the following example foo is a var_field:
2290
+ #
2291
+ # foo = 1
2292
+ #
2293
+ def on_var_field(ident)
2294
+ if ident
2295
+ ident.merge(type: :var_field, body: [ident])
2296
+ else
2297
+ # You can hit this pattern if you're assigning to a splat using pattern
2298
+ # matching syntax in Ruby 2.7+
2299
+ { type: :var_field, body: [] }
2300
+ end
2301
+ end
2302
+
2303
+ # vcall nodes are any plain named thing with Ruby that could be either a
2304
+ # local variable or a method call. They accept as an argument the ident
2305
+ # scanner event that contains their content.
2306
+ #
2307
+ # Access controls like private, protected, and public are reported as
2308
+ # vcall nodes since they're technically method calls. We want to be able
2309
+ # add new lines around them as necessary, so here we're going to
2310
+ # explicitly track those as a different node type.
2311
+ def on_vcall(ident)
2312
+ @controls ||= %w[private protected public].freeze
2313
+
2314
+ body = ident[:body]
2315
+ type =
2316
+ if @controls.include?(body) && body == lines[lineno - 1].strip
2317
+ :access_ctrl
2318
+ else
2319
+ :vcall
2320
+ end
2321
+
2322
+ ident.merge(type: type, body: [ident])
2323
+ end
2324
+
2325
+ # void_stmt is a special kind of parser event that represents an empty lexical
2326
+ # block of code. It often will have comments attached to it, so it requires
2327
+ # some special handling.
2328
+ def on_void_stmt
2329
+ {
2330
+ type: :void_stmt,
2331
+ start: lineno,
2332
+ end: lineno,
2333
+ char_start: char_pos,
2334
+ char_end: char_pos
2335
+ }
2336
+ end
2337
+
2338
+ # when is a parser event that represents another clause in a case chain.
2339
+ # It accepts as arguments the predicate of the when, the statements that
2340
+ # are contained within the else if clause, and the optional consequent
2341
+ # clause.
2342
+ def on_when(predicate, stmts, consequent)
2343
+ beging = find_scanner_event(:@kw, 'when')
2344
+ ending = consequent || find_scanner_event(:@kw, 'end')
2345
+
2346
+ stmts.bind(predicate[:char_end], ending[:char_start])
2347
+
2348
+ {
2349
+ type: :when,
2350
+ body: [predicate, stmts, consequent],
2351
+ start: beging[:start],
2352
+ char_start: beging[:char_start],
2353
+ end: ending[:end],
2354
+ char_end: ending[:char_end]
2355
+ }
2356
+ end
2357
+
2358
+ # while is a parser event that represents a while loop. It accepts as
2359
+ # arguments the predicate to the while and the statements that are
2360
+ # contained within the while clause.
2361
+ def on_while(predicate, stmts)
2362
+ beging = find_scanner_event(:@kw, 'while')
2363
+ ending = find_scanner_event(:@kw, 'end')
2364
+
2365
+ stmts.bind(predicate[:char_end], ending[:char_start])
2366
+
2367
+ {
2368
+ type: :while,
2369
+ body: [predicate, stmts],
2370
+ start: beging[:start],
2371
+ char_start: beging[:char_start],
2372
+ end: ending[:end],
2373
+ char_end: ending[:char_end]
2374
+ }
2375
+ end
2376
+
2377
+ # while_mod is a parser event that represents the modifier form of an
2378
+ # while loop. It accepts as arguments the predicate to the while and the
2379
+ # statement that is contained within the while loop.
2380
+ def on_while_mod(predicate, statement)
2381
+ find_scanner_event(:@kw, 'while')
2382
+
2383
+ {
2384
+ type: :while_mod,
2385
+ body: [predicate, statement],
2386
+ start: statement[:start],
2387
+ char_start: statement[:char_start],
2388
+ end: predicate[:end],
2389
+ char_end: predicate[:char_end]
2390
+ }
2391
+ end
2392
+
2393
+ # word_new is a parser event that represents the beginning of a word
2394
+ # within a special array literal (either strings or symbols) that accepts
2395
+ # interpolation. For example, in the following array, there are three
2396
+ # word nodes:
2397
+ #
2398
+ # %W[one a#{two}a three]
2399
+ #
2400
+ # Each word inside that array is represented as its own node, which is in
2401
+ # terms of the parser a tree of word_new and word_add nodes. For our
2402
+ # purposes, we're going to report this as a word node and build up an
2403
+ # array body of our parts.
2404
+ def on_word_new
2405
+ { type: :word, body: [] }
2406
+ end
2407
+
2408
+ # word_add is a parser event that represents a piece of a word within a
2409
+ # special array literal that accepts interpolation. It accepts as
2410
+ # arguments the parent word node as well as the additional piece of the
2411
+ # word, which can be either a @tstring_content node for a plain string
2412
+ # piece or a string_embexpr for an interpolated piece.
2413
+ def on_word_add(word, piece)
2414
+ if word[:body].empty?
2415
+ # Here we're making sure we get the correct bounds by using the
2416
+ # location information from the first piece.
2417
+ piece.merge(type: :word, body: [piece])
2418
+ else
2419
+ word.merge!(
2420
+ body: word[:body] << piece, end: piece[:end], char_end: piece[:char_end]
2421
+ )
2422
+ end
2423
+ end
2424
+
2425
+ # words_new is a parser event that represents the beginning of a string
2426
+ # literal array that accepts interpolation, like %W[one #{two} three]. It
2427
+ # can be followed by any number of words_add events, which we'll append
2428
+ # onto an array body.
2429
+ def on_words_new
2430
+ find_scanner_event(:@words_beg).merge!(type: :words, body: [])
2431
+ end
2432
+
2433
+ # words_add is a parser event that represents an element inside of a
2434
+ # string literal array that accepts interpolation, like
2435
+ # %W[one #{two} three]. It accepts as arguments the parent words node as
2436
+ # well as a word_add parser event.
2437
+ def on_words_add(words, word_add)
2438
+ words.merge!(
2439
+ body: words[:body] << word_add,
2440
+ end: word_add[:end],
2441
+ char_end: word_add[:char_end]
2442
+ )
2443
+ end
2444
+
2445
+ # xstring_new is a parser event that represents the beginning of a string
2446
+ # of commands that gets sent out to the terminal, like `ls`. It can
2447
+ # optionally include interpolation much like a regular string, so we're
2448
+ # going to build up an array body.
2449
+ #
2450
+ # If the xstring actually starts with a heredoc declaration, then we're
2451
+ # going to let heredocs continue to do their thing and instead just use
2452
+ # its location information.
2453
+ def on_xstring_new
2454
+ heredoc = @heredocs[-1]
2455
+
2456
+ if heredoc && heredoc[:beging][3] = '`'
2457
+ heredoc.merge(type: :xstring, body: [])
2458
+ elsif RUBY_MAJOR <= 2 && RUBY_MINOR <= 5 && RUBY_PATCH < 7
2459
+ { type: :xstring, body: [] }
2460
+ else
2461
+ find_scanner_event(:@backtick).merge!(type: :xstring, body: [])
2462
+ end
2463
+ end
2464
+
2465
+ # xstring_add is a parser event that represents a piece of a string of
2466
+ # commands that gets sent out to the terminal, like `ls`. It accepts two
2467
+ # arguments, the parent xstring node as well as the piece that is being
2468
+ # added to the string. Because it supports interpolation this is either a
2469
+ # tstring_content scanner event representing bare string content or a
2470
+ # string_embexpr representing interpolated content.
2471
+ def on_xstring_add(xstring, piece)
2472
+ xstring.merge!(
2473
+ body: xstring[:body] << piece,
2474
+ end: piece[:end],
2475
+ char_end: piece[:char_end]
2476
+ )
2477
+ end
2478
+
2479
+ # xstring_literal is a parser event that represents a string of commands
2480
+ # that gets sent to the terminal, like `ls`. It accepts as its only
2481
+ # argument an xstring node that is a built up array representation of all
2482
+ # of the parts of the string (including the plain string content and the
2483
+ # interpolated content).
2484
+ #
2485
+ # They can also use heredocs to present themselves, as in the example:
2486
+ #
2487
+ # <<-`SHELL`
2488
+ # ls
2489
+ # SHELL
2490
+ #
2491
+ # In this case we need to change the node type to be a heredoc instead of
2492
+ # an xstring_literal in order to get the right formatting.
2493
+ def on_xstring_literal(xstring)
2494
+ heredoc = @heredocs[-1]
2495
+
2496
+ if heredoc && heredoc[:beging][3] = '`'
2497
+ heredoc.merge!(body: xstring[:body])
2498
+ else
2499
+ ending = find_scanner_event(:@tstring_end)
2500
+ xstring.merge!(
2501
+ type: :xstring_literal, end: ending[:end], char_end: ending[:char_end]
2502
+ )
2503
+ end
2504
+ end
2505
+
2506
+ # yield is a parser event that represents using the yield keyword with
2507
+ # arguments. It accepts as an argument an args_add_block event that
2508
+ # contains all of the arguments being passed.
2509
+ def on_yield(args_add_block)
2510
+ find_scanner_event(:@kw, 'yield').merge!(
2511
+ type: :yield,
2512
+ body: [args_add_block],
2513
+ end: args_add_block[:end],
2514
+ char_end: args_add_block[:char_end]
2515
+ )
2516
+ end
2517
+
2518
+ # yield0 is a parser event that represents the bare yield keyword. It has
2519
+ # no body as it accepts no arguments. This is as opposed to the yield
2520
+ # parser event, which is the version where you're yielding one or more
2521
+ # values.
2522
+ def on_yield0
2523
+ find_scanner_event(:@kw, 'yield').merge!(type: :yield0)
2524
+ end
2525
+
2526
+ # zsuper is a parser event that represents the bare super keyword. It has
2527
+ # no body as it accepts no arguments. This is as opposed to the super
2528
+ # parser event, which is the version where you're calling super with one
2529
+ # or more values.
2530
+ def on_zsuper
2531
+ find_scanner_event(:@kw, 'super').merge!(type: :zsuper)
2532
+ end
2533
+ end
2534
+
2535
+ # If this is the main file we're executing, then most likely this is being
2536
+ # executed from the parser.js spawn. In that case, read the ruby source from
2537
+ # stdin and report back the AST over stdout.
2538
+
2539
+ if $0 == __FILE__
2540
+ builder = Prettier::Parser.new($stdin.read)
2541
+ response = builder.parse
2542
+
2543
+ if !response || builder.error?
2544
+ warn(
2545
+ '@prettier/plugin-ruby encountered an error when attempting to parse ' \
2546
+ 'the ruby source. This usually means there was a syntax error in the ' \
2547
+ 'file in question. You can verify by running `ruby -i [path/to/file]`.'
2548
+ )
2549
+
2550
+ exit 1
2551
+ end
2552
+
2553
+ puts JSON.fast_generate(response)
2554
+ end