rufo 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 39d02ca2465a942ceb6c85baccb46c58ef602ccd
4
- data.tar.gz: 6d8c42eac82fbb8ed50569d5c1ff36d0d7bc726b
3
+ metadata.gz: 4254f11d5e797cb76d947f8e5ae0e9fddd0fc281
4
+ data.tar.gz: b3937d7d503a7faf716e6deebf2f228feae26a36
5
5
  SHA512:
6
- metadata.gz: 577fe64ea118fc7402e3380cc40f235a0e7639a92b7e27ede1a192648497713c27fbd4b249744b291ca47cd3ecde9b66eb0bfb359d0c2f12c7ef3ad7a924a30f
7
- data.tar.gz: 95282a406c703e54f1e91c3913887b5aed847f853663bc7c080f058de965ecf5b71d6dfb7ca16a07623947434c2bcbbb409988640cb3b862af5576e9512a758d
6
+ metadata.gz: 2b7110b0fc44a623f5c473b6be8b6a80bb7dede05a551a5fa8b1408f264c18281ceba1078ff991f0cda1deffa15b92369676c42254414af9ac46f3b2f3f4a07a
7
+ data.tar.gz: 80d832ba0b8798af6de39fe9adeaa10c6ab0c4f09350c5b3205684e2e34b658d54cbb64f9723240833f46eaa111c44da87c165a46ab80da823e69a99be8a221d
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /*.gem
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rufo
2
2
 
3
- *Ru*by *fo*rmatter (in development)
3
+ **Ru**by **fo**rmatter (in development)
4
4
 
5
5
  ## Installation
6
6
 
data/exe/rufo CHANGED
@@ -1,9 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require "rufo"
3
3
 
4
- filename = ARGV[0]
5
- code = File.read(filename)
6
-
7
- result = Rufo.format(code)
8
- File.write(filename, result)
9
-
4
+ Rufo::Command.run
@@ -1,9 +1,13 @@
1
1
  require "rufo/version"
2
2
 
3
3
  module Rufo
4
- def self.format(code)
5
- Formatter.format(code)
4
+ class Bug < Exception; end
5
+ class SyntaxError < Exception; end
6
+
7
+ def self.format(code, **options)
8
+ Formatter.format(code, **options)
6
9
  end
7
10
  end
8
11
 
12
+ require_relative "rufo/command"
9
13
  require_relative "rufo/formatter"
@@ -0,0 +1,27 @@
1
+ module Rufo::Command
2
+ def self.run
3
+ if ARGV.empty?
4
+ format_stdin
5
+ else
6
+ format_file ARGV[0]
7
+ end
8
+ rescue Rufo::Bug => ex
9
+ STDERR.puts "You've found a bug! Please report it to https://github.com/asterite/rufo/issues with code that triggers it"
10
+ STDERR.puts
11
+ raise ex
12
+ rescue Rufo::SyntaxError
13
+ STDERR.puts "Error: the given text is not a valid ruby program (it has syntax errors)"
14
+ end
15
+
16
+ def self.format_stdin
17
+ code = STDIN.read
18
+ result = Rufo.format(code)
19
+ print result
20
+ end
21
+
22
+ def self.format_file(filename)
23
+ code = File.read(filename)
24
+ result = Rufo.format(code)
25
+ File.write(filename, result)
26
+ end
27
+ end
@@ -1,58 +1,128 @@
1
1
  require "ripper"
2
2
 
3
3
  class Rufo::Formatter
4
- def self.format(code)
5
- formatter = new(code)
4
+ def self.format(code, **options)
5
+ formatter = new(code, **options)
6
6
  formatter.format
7
7
  formatter.result
8
8
  end
9
9
 
10
- def initialize(code)
10
+ # The indent size (default: 2)
11
+ attr_accessor :indent_size
12
+
13
+ # Whether to align successive comments (default: true)
14
+ attr_accessor :align_comments
15
+
16
+ # Whether to convert multiline `{ ... }` block
17
+ # to `do ... end` (default: true)
18
+ attr_accessor :convert_brace_to_do
19
+
20
+ # Whether to align successive assignments (default: true)
21
+ attr_accessor :align_assignments
22
+
23
+ # Whether to align successive hash keys (default: true)
24
+ attr_accessor :align_hash_keys
25
+
26
+ def initialize(code, **options)
11
27
  @code = code
12
28
  @tokens = Ripper.lex(code).reverse!
13
29
  @sexp = Ripper.sexp(code)
30
+
31
+ unless @sexp
32
+ raise ::Rufo::SyntaxError.new
33
+ end
34
+
35
+ @indent = 0
36
+ @line = 0
37
+ @column = 0
38
+ @last_was_newline = false
14
39
  @output = ""
40
+
41
+ # The column of a `obj.method` call, so we can align
42
+ # calls to that dot
43
+ @dot_column = nil
44
+
45
+ # Heredocs list, associated with calls ([call, heredoc, tilde])
46
+ @heredocs = []
47
+
48
+ # Current call, to be able to associate it to heredocs
49
+ @current_call = nil
50
+
51
+ # The current heredoc being printed
52
+ @current_heredoc = nil
53
+
54
+ # The current hash or call or method that has hash-like parameters
55
+ @current_hash = nil
56
+
57
+ # Position of comments that occur at the end of a line
58
+ @comments_positions = []
59
+
60
+ # Position of assignments
61
+ @assignments_positions = []
62
+
63
+ # Hash keys positions
64
+ @hash_keys_positions = []
65
+
66
+ # Settings
67
+ @indent_size = options.fetch(:indent_size, 2)
68
+ @align_comments = options.fetch(:align_comments, true)
69
+ @convert_brace_to_do = options.fetch(:convert_brace_to_do, true)
70
+ @align_assignments = options.fetch(:align_assignments, true)
71
+ @align_hash_keys = options.fetch(:align_hash_keys, true)
15
72
  end
16
73
 
17
74
  def format
18
75
  visit @sexp
19
- write_line
76
+ write_line unless @last_was_newline
77
+ do_align_comments if @align_comments
78
+ do_align_assignments if @align_assignments
79
+ do_align_hash_keys if @align_hash_keys
20
80
  end
21
81
 
22
82
  def visit(node)
83
+ unless node.is_a?(Array)
84
+ bug "unexpected node: #{node} at #{current_token}"
85
+ end
86
+
23
87
  case node.first
24
88
  when :program
89
+ # Topmost node
90
+ #
25
91
  # [:program, exps]
26
92
  visit_exps node[1]
27
93
  when :void_stmt
94
+ # Empty statement
95
+ #
28
96
  # [:void_stmt]
29
- check :on_eof
97
+ skip_space_or_newline
30
98
  when :@int
99
+ # Number literal
100
+ #
31
101
  # [:@int, "123", [1, 0]]
32
102
  consume_token :on_int
33
103
  when :string_literal
34
- # [:string_literal, [:string_content, exps]]
35
- consume_token :on_tstring_beg
36
- visit_exps(node[1][1..-1])
37
- consume_token :on_tstring_end
104
+ visit_string_literal node
38
105
  when :@tstring_content
39
106
  # [:@tstring_content, "hello ", [1, 1]]
40
- consume_token :on_tstring_content
107
+ heredoc, tilde = @current_heredoc
108
+ column = node[2][0]
109
+
110
+ # For heredocs with tilde we sometimes need to align the contents
111
+ if heredoc && tilde && @last_was_newline
112
+ write_indent(next_indent)
113
+ check :on_tstring_content
114
+ consume_token_value(current_token_value)
115
+ next_token
116
+ else
117
+ consume_token :on_tstring_content
118
+ end
41
119
  when :string_embexpr
42
- # [:string_embexpr, exps]
43
- consume_token :on_embexpr_beg
44
- skip_space
45
- visit_exps node[1]
46
- consume_token :on_embexpr_end
120
+ # String interpolation piece ( #{exp} )
121
+ visit_string_interpolation node
47
122
  when :symbol_literal
48
- # [:symbol_literal, [:symbol, [:@ident, "foo", [1, 1]]]]
49
- consume_token :on_symbeg
50
- visit_exps node[1][1..-1]
123
+ visit_symbol_literal(node)
51
124
  when :dyna_symbol
52
- # [:dyna_symbol, exps]
53
- consume_token :on_symbeg
54
- visit_exps node[1]
55
- consume_token :on_tstring_end
125
+ visit_quoted_symbol_literal(node)
56
126
  when :@ident
57
127
  consume_token :on_ident
58
128
  when :var_ref
@@ -64,137 +134,2262 @@ class Rufo::Formatter
64
134
  when :@kw
65
135
  # [:@kw, "nil", [1, 0]]
66
136
  consume_token :on_kw
137
+ when :@ivar
138
+ # [:@ivar, "@foo", [1, 0]]
139
+ consume_token :on_ivar
140
+ when :@const
141
+ # [:@const, "FOO", [1, 0]]
142
+ consume_token :on_const
143
+ when :const_ref
144
+ # [:const_ref, [:@const, "Foo", [1, 8]]]
145
+ visit node[1]
146
+ when :top_const_ref
147
+ # [:top_const_ref, [:@const, "Foo", [1, 2]]]
148
+ consume_op "::"
149
+ skip_space_or_newline
150
+ visit node[1]
151
+ when :const_path_ref
152
+ visit_path(node)
67
153
  when :assign
68
- # [:assign, target, value]
154
+ visit_assign(node)
155
+ when :opassign
156
+ visit_op_assign(node)
157
+ when :massign
158
+ visit_multiple_assign(node)
159
+ when :ifop
160
+ visit_ternary_if(node)
161
+ when :if_mod
162
+ visit_suffix(node, "if")
163
+ when :unless_mod
164
+ visit_suffix(node, "unless")
165
+ when :rescue_mod
166
+ visit_suffix(node, "rescue")
167
+ when :while_mod
168
+ visit_suffix(node, "while")
169
+ when :until_mod
170
+ visit_suffix(node, "until")
171
+ when :vcall
172
+ # [:vcall, exp]
69
173
  visit node[1]
70
- consume_space
71
- consume_op "="
72
- consume_space
73
- visit node[2]
174
+ when :fcall
175
+ # [:fcall, [:@ident, "foo", [1, 0]]]
176
+ visit node[1]
177
+ when :command
178
+ visit_command(node)
179
+ when :command_call
180
+ visit_command_call(node)
181
+ when :args_add_block
182
+ visit_call_args(node)
183
+ when :args_add_star
184
+ visit_args_add_star(node)
185
+ when :bare_assoc_hash
186
+ # **x, **y, ...
187
+ #
188
+ # [:bare_assoc_hash, exps]
189
+ visit_comma_separated_list node[1]
190
+ when :method_add_arg
191
+ visit_call_without_receiver(node)
192
+ when :method_add_block
193
+ visit_call_with_block(node)
194
+ when :call
195
+ visit_call_with_receiver(node)
196
+ when :brace_block
197
+ visit_brace_block(node)
198
+ when :do_block
199
+ visit_do_block(node)
200
+ when :block_var
201
+ visit_block_arguments(node)
202
+ when :begin
203
+ visit_begin(node)
204
+ when :bodystmt
205
+ visit_bodystmt(node)
206
+ when :if
207
+ visit_if(node)
208
+ when :unless
209
+ visit_unless(node)
210
+ when :while
211
+ visit_while(node)
212
+ when :until
213
+ visit_until(node)
214
+ when :case
215
+ visit_case(node)
216
+ when :when
217
+ visit_when(node)
218
+ when :unary
219
+ visit_unary(node)
220
+ when :binary
221
+ visit_binary(node)
222
+ when :class
223
+ visit_class(node)
224
+ when :module
225
+ visit_module(node)
226
+ when :mrhs_new_from_args
227
+ visit_mrhs_new_from_args(node)
228
+ when :mlhs_paren
229
+ visit_mlhs_paren(node)
230
+ when :def
231
+ visit_def(node)
232
+ when :defs
233
+ visit_def_with_receiver(node)
234
+ when :paren
235
+ visit_paren(node)
236
+ when :params
237
+ visit_params(node)
238
+ when :array
239
+ visit_array(node)
240
+ when :hash
241
+ visit_hash(node)
242
+ when :assoc_new
243
+ visit_hash_key_value(node)
244
+ when :assoc_splat
245
+ visit_splat_inside_hash(node)
246
+ when :@label
247
+ # [:@label, "foo:", [1, 3]]
248
+ write node[1]
249
+ next_token
250
+ when :dot2
251
+ visit_range(node, true)
252
+ when :dot3
253
+ visit_range(node, false)
254
+ when :regexp_literal
255
+ visit_regexp_literal(node)
256
+ when :aref
257
+ visit_array_access(node)
258
+ when :aref_field
259
+ visit_array_setter(node)
260
+ when :sclass
261
+ visit_sclass(node)
262
+ when :field
263
+ visit_setter(node)
264
+ when :return0
265
+ consume_keyword "return"
266
+ when :return
267
+ visit_return(node)
268
+ when :break
269
+ visit_break(node)
270
+ when :next
271
+ visit_next(node)
272
+ when :yield0
273
+ consume_keyword "yield"
274
+ when :yield
275
+ visit_yield(node)
276
+ when :@op
277
+ # [:@op, "*", [1, 1]]
278
+ write node[1]
279
+ next_token
280
+ when :lambda
281
+ visit_lambda(node)
282
+ when :zsuper
283
+ # [:zsuper]
284
+ consume_keyword "super"
285
+ when :super
286
+ visit_super(node)
74
287
  else
75
- raise "Unhandled node: #{node.first}"
288
+ bug "Unhandled node: #{node.first}"
76
289
  end
77
290
  end
78
291
 
79
- def visit_exps(exps)
292
+ def visit_exps(exps, with_indent = false, with_lines = true)
293
+ consume_end_of_line(true)
294
+
295
+ line_before_endline = nil
296
+
80
297
  exps.each_with_index do |exp, i|
298
+ exp_kind = exp[0]
299
+
300
+ # Skip voids to avoid extra indentation
301
+ if exp_kind == :void_stmt
302
+ next
303
+ end
304
+
305
+ if with_indent
306
+ # Don't indent if this exp is in the same line as the previous
307
+ # one (this happens when there's a semicolon between the exps)
308
+ unless line_before_endline && line_before_endline == @line
309
+ write_indent
310
+ end
311
+ end
312
+
81
313
  visit exp
82
- skip_space
83
314
 
84
- case current_token_kind
85
- when :on_semicolon
86
- next_token
87
- skip_space
88
- case current_token_kind
89
- when :on_ignored_nl
90
- consume_lines
91
- when :on_eof
92
- # Nothing
93
- else
94
- write ";"
95
- write " " unless last?(i, exps)
315
+ line_before_endline = @line
316
+
317
+ is_last = last?(i, exps)
318
+ if with_lines
319
+ consume_end_of_line(false, !is_last, !is_last)
320
+
321
+ # Make sure to put two lines before defs, class and others
322
+ if !is_last && (needs_two_lines?(exp_kind) || needs_two_lines?(exps[i + 1][0])) && @line <= line_before_endline + 1
323
+ write_line
96
324
  end
97
- when :on_nl
98
- consume_lines
325
+ else
326
+ skip_space_or_newline unless is_last
99
327
  end
328
+ end
329
+ end
100
330
 
101
- skip_space_or_newline
331
+ def needs_two_lines?(exp_kind)
332
+ case exp_kind
333
+ when :def, :class, :module
334
+ true
335
+ else
336
+ false
102
337
  end
103
338
  end
104
339
 
105
- def visit_string(node)
340
+ def visit_string_literal(node)
341
+ # [:string_literal, [:string_content, exps]]
342
+ heredoc = current_token_kind == :on_heredoc_beg
343
+ tilde = current_token_value.include?("~")
344
+
345
+ if heredoc
346
+ write current_token_value.rstrip
347
+ next_token
348
+ skip_space
349
+
350
+ # A comma after a heredoc means the heredoc contents
351
+ # come after an argument list, so put it in a list
352
+ # for later.
353
+ # The same happens if we already have a heredoc in
354
+ # the list, which means this will come after other
355
+ # heredocs.
356
+ if comma? || !@heredocs.empty?
357
+ @heredocs << [@current_call, node, tilde]
358
+ return
359
+ end
360
+ else
361
+ consume_token :on_tstring_beg
362
+ end
363
+
364
+ if heredoc
365
+ @current_heredoc = [node, tilde]
366
+ end
367
+
368
+ visit_string_literal_end(node)
369
+
370
+ @current_heredoc = nil if heredoc
106
371
  end
107
372
 
108
- def consume_lines
109
- written_lines = 0
373
+ def visit_string_literal_end(node)
374
+ visit_exps(node[1][1..-1], false, false)
110
375
 
111
- while true
112
- case current_token_kind
113
- when :on_nl, :on_ignored_nl
114
- write_line if written_lines < 2
115
- written_lines += 1
116
- next_token
117
- when :on_sp, :on_semicolon
118
- next_token
376
+ if current_token_kind == :on_heredoc_end
377
+ heredoc, tilde = @current_heredoc
378
+ if heredoc && tilde
379
+ write_indent
380
+ write current_token_value.strip
119
381
  else
120
- break
382
+ write current_token_value.rstrip
121
383
  end
384
+ next_token
385
+ else
386
+ consume_token :on_tstring_end
122
387
  end
123
388
  end
124
389
 
125
- def consume_space
126
- skip_space
127
- write " "
390
+ def visit_string_interpolation(node)
391
+ # [:string_embexpr, exps]
392
+ consume_token :on_embexpr_beg
393
+ skip_space_or_newline
394
+ visit_exps node[1], false, false
395
+ skip_space_or_newline
396
+ consume_token :on_embexpr_end
128
397
  end
129
398
 
130
- def skip_space
131
- while current_token_kind == :on_sp
132
- next_token
133
- end
399
+ def visit_symbol_literal(node)
400
+ # :foo
401
+ #
402
+ # [:symbol_literal, [:symbol, [:@ident, "foo", [1, 1]]]]
403
+ consume_token :on_symbeg
404
+ visit_exps node[1][1..-1], false, false
134
405
  end
135
406
 
136
- def skip_space_or_newline
137
- while true
138
- case current_token_kind
139
- when :on_sp, :on_nl, :on_semicolon
140
- next_token
141
- next
142
- else
143
- break
407
+ def visit_quoted_symbol_literal(node)
408
+ # :"foo"
409
+ #
410
+ # [:dyna_symbol, exps]
411
+ consume_token :on_symbeg
412
+ visit_exps node[1], false, false
413
+ consume_token :on_tstring_end
414
+ end
415
+
416
+ def visit_path(node)
417
+ # Foo::Bar
418
+ #
419
+ # [:const_path_ref,
420
+ # [:var_ref, [:@const, "Foo", [1, 0]]],
421
+ # [:@const, "Bar", [1, 5]]]
422
+ pieces = node[1..-1]
423
+ pieces.each_with_index do |piece, i|
424
+ visit piece
425
+ unless last?(i, pieces)
426
+ consume_op "::"
427
+ skip_space_or_newline
144
428
  end
145
429
  end
146
430
  end
147
431
 
148
- def write(value)
149
- @output << value
432
+ def visit_assign(node)
433
+ # target = value
434
+ #
435
+ # [:assign, target, value]
436
+ _, target, value = node
437
+
438
+ visit target
439
+ consume_space
440
+ track_assignment
441
+ consume_op "="
442
+ visit_assign_value value
150
443
  end
151
444
 
152
- def consume_token(kind)
153
- check kind
154
- write current_token_value
445
+ def visit_op_assign(node)
446
+ # target += value
447
+ #
448
+ # [:opassign, target, op, value]
449
+ _, target, op, value = node
450
+ visit target
451
+ consume_space
452
+
453
+ # [:@op, "+=", [1, 2]],
454
+ check :on_op
455
+
456
+ before = op[1][0...-1]
457
+ after = op[1][-1]
458
+
459
+ write before
460
+ track_assignment before.size
461
+ write after
155
462
  next_token
463
+
464
+ visit_assign_value value
156
465
  end
157
466
 
158
- def consume_op(value)
159
- check :on_op
160
- if current_token_value != value
161
- raise "Expected op #{value}, not #{current_token_value}"
467
+ def visit_multiple_assign(node)
468
+ # [:massign, lefts, right]
469
+ _, lefts, right = node
470
+
471
+ visit_comma_separated_list lefts
472
+ consume_space
473
+ track_assignment
474
+ consume_op "="
475
+ visit_assign_value right
476
+ end
477
+
478
+ def visit_assign_value(value)
479
+ skip_space
480
+ indent_after_space value, indentable_keyword?
481
+ end
482
+
483
+ def indentable_keyword?
484
+ return unless current_token_kind == :on_kw
485
+
486
+ case current_token_value
487
+ when "if", "unless", "case"
488
+ true
489
+ else
490
+ false
162
491
  end
163
- write current_token_value
164
- next_token
165
492
  end
166
493
 
167
- def write_line
168
- @output << "\n"
494
+ def track_assignment(offset = 0)
495
+ track_alignment @assignments_positions, offset
169
496
  end
170
497
 
171
- def check(kind)
172
- if current_token_kind != kind
173
- raise "Expected token #{kind}, not #{current_token_kind}"
498
+ def track_hash_key
499
+ return unless @current_hash
500
+
501
+ track_alignment @hash_keys_positions, 0, @current_hash.object_id
502
+ end
503
+
504
+ def track_alignment(target, offset = 0, id = nil)
505
+ last = target.last
506
+ if last && last[0] == @line
507
+ last << :ignore if last.size < 6
508
+ return
174
509
  end
510
+
511
+ target << [@line, @column, @indent, id, offset]
175
512
  end
176
513
 
177
- # [[1, 0], :on_int, "1"]
178
- def current_token
179
- @tokens.last
514
+ def visit_ternary_if(node)
515
+ # cond ? then : else
516
+ #
517
+ # [:ifop, cond, then_body, else_body]
518
+ _, cond, then_body, else_body = node
519
+
520
+ visit cond
521
+ consume_space
522
+ consume_op "?"
523
+
524
+ skip_space
525
+ if newline? || comment?
526
+ consume_end_of_line
527
+ write_indent(next_indent)
528
+ else
529
+ consume_space
530
+ end
531
+
532
+ visit then_body
533
+ consume_space
534
+ consume_op ":"
535
+
536
+ skip_space
537
+ if newline? || comment?
538
+ consume_end_of_line
539
+ write_indent(next_indent)
540
+ else
541
+ consume_space
542
+ end
543
+
544
+ visit else_body
180
545
  end
181
546
 
182
- def current_token_kind
183
- tok = current_token
184
- tok ? tok[1] : :on_eof
547
+ def visit_suffix(node, suffix)
548
+ # then if cond
549
+ # then unless cond
550
+ # exp rescue handler
551
+ #
552
+ # [:if_mod, cond, body]
553
+ _, cond, body = node
554
+
555
+ visit body
556
+ consume_space
557
+ consume_keyword(suffix)
558
+ consume_space
559
+ visit cond
185
560
  end
186
561
 
187
- def current_token_value
188
- tok = current_token
189
- tok ? tok[2] : ""
562
+ def visit_call_with_receiver(node)
563
+ # [:call, obj, :".", call]
564
+ _, obj, text, call = node
565
+
566
+ @dot_column = nil
567
+ visit obj
568
+
569
+ skip_space
570
+
571
+ if newline? || comment?
572
+ consume_end_of_line
573
+
574
+ write_indent(@dot_column || next_indent)
575
+ end
576
+
577
+ # Remember dot column
578
+ dot_column = @column
579
+ consume_call_dot
580
+
581
+ skip_space
582
+
583
+ if newline? || comment?
584
+ consume_end_of_line
585
+ write_indent(next_indent)
586
+ else
587
+ skip_space_or_newline
588
+ end
589
+
590
+ visit call
591
+
592
+ # Only set it after we visit the call after the dot,
593
+ # so we remember the outmost dot position
594
+ @dot_column = dot_column
190
595
  end
191
596
 
192
- def next_token
193
- @tokens.pop
597
+ def consume_call_dot
598
+ if current_token_kind == :on_op
599
+ consume_op "::"
600
+ else
601
+ check :on_period
602
+ next_token
603
+ write "."
604
+ end
194
605
  end
195
606
 
196
- def last?(i, array)
197
- i == array.size - 1
607
+ def visit_call_without_receiver(node)
608
+ # foo(arg1, ..., argN)
609
+ #
610
+ # [:method_add_arg,
611
+ # [:fcall, [:@ident, "foo", [1, 0]]],
612
+ # [:arg_paren, [:args_add_block, [[:@int, "1", [1, 6]]], false]]]
613
+ _, name, args = node
614
+
615
+ visit name
616
+
617
+ # Some times a call comes without parens (should probably come as command, but well...)
618
+ return if args.empty?
619
+
620
+ # Remember dot column so it's not affected by args
621
+ dot_column = @dot_column
622
+
623
+ visit_call_at_paren(node, args)
624
+
625
+ # Restore dot column so it's not affected by args
626
+ @dot_column = dot_column
627
+ end
628
+
629
+ def visit_call_at_paren(node, args)
630
+ consume_token :on_lparen
631
+
632
+ # If there's a trailing comma then comes [:arg_paren, args],
633
+ # which is a bit unexpected, so we fix it
634
+ if args[1].is_a?(Array) && args[1][0].is_a?(Array)
635
+ args_node = [:args_add_block, args[1], false]
636
+ else
637
+ args_node = args[1]
638
+ end
639
+
640
+ if args_node
641
+ skip_space
642
+
643
+ needs_trailing_newline = newline? || comment?
644
+
645
+ push_call(node) do
646
+ visit args_node
647
+ end
648
+
649
+ found_comma = comma?
650
+
651
+ if found_comma
652
+ if needs_trailing_newline
653
+ write ","
654
+ next_token
655
+ indent(next_indent) do
656
+ consume_end_of_line
657
+ end
658
+ write_indent
659
+ else
660
+ next_token
661
+ skip_space
662
+ end
663
+ end
664
+
665
+ if newline? || comment?
666
+ if needs_trailing_newline
667
+ indent(next_indent) do
668
+ consume_end_of_line
669
+ end
670
+ write_indent
671
+ else
672
+ skip_space_or_newline
673
+ end
674
+ else
675
+ if needs_trailing_newline && !found_comma
676
+ consume_end_of_line
677
+ write_indent
678
+ end
679
+ end
680
+ else
681
+ skip_space_or_newline
682
+ end
683
+
684
+ consume_token :on_rparen
685
+
686
+ check_heredocs_at_call_end(node)
687
+ end
688
+
689
+ def visit_command(node)
690
+ # foo arg1, ..., argN
691
+ #
692
+ # [:command, name, args]
693
+ _, name, args = node
694
+
695
+ push_call(node) do
696
+ visit name
697
+ consume_space
698
+ end
699
+
700
+ visit_command_end(node, args)
701
+ end
702
+
703
+ def visit_command_end(node, args)
704
+ push_call(node) do
705
+ indent(@column) do
706
+ visit args
707
+ end
708
+ end
709
+
710
+ check_heredocs_at_call_end(node)
711
+ end
712
+
713
+ def check_heredocs_at_call_end(node)
714
+ printed = false
715
+
716
+ until @heredocs.empty?
717
+ scope, heredoc, tilde = @heredocs.first
718
+ break unless scope.equal?(node)
719
+
720
+ # Need to print a line between consecutive heredoc ends
721
+ write_line if printed
722
+
723
+ @heredocs.shift
724
+ @current_heredoc = [heredoc, tilde]
725
+ visit_string_literal_end(heredoc)
726
+ @current_heredoc = nil
727
+ printed = true
728
+ end
729
+ end
730
+
731
+ def visit_command_call(node)
732
+ # [:command_call,
733
+ # receiver
734
+ # :".",
735
+ # name
736
+ # [:args_add_block, [[:@int, "1", [1, 8]]], block]]
737
+ _, receiver, dot, name, args = node
738
+
739
+ visit receiver
740
+ skip_space_or_newline
741
+
742
+ # Remember dot column
743
+ dot_column = @column
744
+ consume_call_dot
745
+
746
+ skip_space
747
+
748
+ if newline? || comment?
749
+ consume_end_of_line
750
+ write_indent(next_indent)
751
+ else
752
+ skip_space_or_newline
753
+ end
754
+
755
+ visit name
756
+ consume_space
757
+
758
+ indent(@column) do
759
+ visit args
760
+ end
761
+
762
+ # Only set it after we visit the call after the dot,
763
+ # so we remember the outmost dot position
764
+ @dot_column = dot_column
765
+ end
766
+
767
+ def visit_call_with_block(node)
768
+ # [:method_add_block, call, block]
769
+ _, call, block = node
770
+
771
+ visit call
772
+
773
+ consume_space
774
+ visit block
775
+ end
776
+
777
+ def visit_brace_block(node)
778
+ # [:brace_block, args, body]
779
+ _, args, body = node
780
+
781
+ # This is for the empty `{ }` block
782
+ if void_exps?(body)
783
+ consume_token :on_lbrace
784
+ consume_block_args args
785
+ consume_space
786
+ consume_token :on_rbrace
787
+ return
788
+ end
789
+
790
+ closing_brace_token = find_closing_brace_token
791
+
792
+ # If the whole block fits into a single line, use braces
793
+ if current_token[0][0] == closing_brace_token[0][0]
794
+ consume_token :on_lbrace
795
+
796
+ consume_block_args args
797
+
798
+ consume_space
799
+ visit_exps body, false, false
800
+ consume_space
801
+
802
+ consume_token :on_rbrace
803
+ return
804
+ end
805
+
806
+ # Otherwise, use `do` (if told so)
807
+ check :on_lbrace
808
+
809
+ if @convert_brace_to_do
810
+ write "do"
811
+ else
812
+ write "{"
813
+ end
814
+
815
+ next_token
816
+
817
+ consume_block_args args
818
+
819
+ indent_body body
820
+
821
+ write_indent
822
+
823
+ check :on_rbrace
824
+ next_token
825
+
826
+ if @convert_brace_to_do
827
+ write "end"
828
+ else
829
+ write "}"
830
+ end
831
+ end
832
+
833
+ def visit_do_block(node)
834
+ # [:brace_block, args, body]
835
+ _, args, body = node
836
+
837
+ consume_keyword "do"
838
+
839
+ consume_block_args args
840
+
841
+ indent_body body
842
+
843
+ write_indent
844
+ consume_keyword "end"
845
+ end
846
+
847
+ def consume_block_args(args)
848
+ if args
849
+ consume_space
850
+ # + 1 because of |...|
851
+ # ^
852
+ indent(@column + 1) do
853
+ visit args
854
+ end
855
+ end
856
+ end
857
+
858
+ def visit_block_arguments(node)
859
+ # [:block_var, params, ??]
860
+ _, params = node
861
+
862
+ consume_op "|"
863
+ skip_space_or_newline
864
+
865
+ visit params
866
+
867
+ skip_space_or_newline
868
+ consume_op "|"
869
+ end
870
+
871
+ def visit_call_args(node)
872
+ # [:args_add_block, args, block]
873
+ _, args, block_arg = node
874
+
875
+ if !args.empty? && args[0] == :args_add_star
876
+ # arg1, ..., *star
877
+ visit args
878
+ return
879
+ end
880
+
881
+ visit_comma_separated_list args, true
882
+
883
+ if block_arg
884
+ write_params_comma if comma?
885
+
886
+ consume_op "&"
887
+ visit block_arg
888
+ end
889
+ end
890
+
891
+ def visit_args_add_star(node)
892
+ # [:args_add_star, args, star, post_args]
893
+ _, args, star, *post_args = node
894
+
895
+ if !args.empty? && args[0] == :args_add_star
896
+ # arg1, ..., *star
897
+ visit args
898
+ else
899
+ visit_comma_separated_list args
900
+ end
901
+
902
+ skip_space
903
+
904
+ write_params_comma if comma?
905
+
906
+ consume_op "*"
907
+ skip_space_or_newline
908
+ visit star
909
+
910
+ if post_args && !post_args.empty?
911
+ write_params_comma
912
+ visit_comma_separated_list post_args
913
+ end
914
+ end
915
+
916
+ def visit_begin(node)
917
+ # begin
918
+ # body
919
+ # end
920
+ #
921
+ # [:begin, [:bodystmt, body, rescue_body, else_body, ensure_body]]
922
+ consume_keyword "begin"
923
+ visit node[1]
924
+ end
925
+
926
+ def visit_bodystmt(node)
927
+ # [:bodystmt, body, rescue_body, else_body, ensure_body]
928
+ _, body, rescue_body, else_body, ensure_body = node
929
+ indent_body body
930
+
931
+ if rescue_body
932
+ # [:rescue, type, name, body, nil]
933
+ _, type, name, body = rescue_body
934
+ write_indent
935
+ consume_keyword "rescue"
936
+ if type
937
+ skip_space
938
+ write_space " "
939
+ indent(@column) do
940
+ visit_rescue_types(type)
941
+ end
942
+ end
943
+
944
+ if name
945
+ skip_space
946
+ write_space " "
947
+ consume_op "=>"
948
+ skip_space
949
+ write_space " "
950
+ visit name
951
+ end
952
+
953
+ indent_body body
954
+ end
955
+
956
+ if else_body
957
+ # [:else, body]
958
+ write_indent
959
+ consume_keyword "else"
960
+ indent_body else_body[1]
961
+ end
962
+
963
+ if ensure_body
964
+ # [:ensure, body]
965
+ write_indent
966
+ consume_keyword "ensure"
967
+ indent_body ensure_body[1]
968
+ end
969
+
970
+ write_indent
971
+ consume_keyword "end"
972
+ end
973
+
974
+ def visit_rescue_types(node)
975
+ if node[0] == :mrhs_new_from_args
976
+ visit node
977
+ else
978
+ visit_exps node, false, false
979
+ end
980
+ end
981
+
982
+ def visit_mrhs_new_from_args(node)
983
+ # Multiple exception types
984
+ # [:mrhs_new_from_args, exps, final_exp]
985
+ nodes = [*node[1], node[2]]
986
+ visit_comma_separated_list(nodes)
987
+ end
988
+
989
+ def visit_mlhs_paren(node)
990
+ # [:mlhs_paren,
991
+ # [[:mlhs_paren, [:@ident, "x", [1, 12]]]]
992
+ # ]
993
+ _, args = node
994
+
995
+ # For some reason there's nested :mlhs_paren for
996
+ # a single parentheses. It seems when there's
997
+ # a nested array we need parens, otherwise we
998
+ # just output whatever's inside `args`.
999
+ if args.is_a?(Array) && args[0].is_a?(Array)
1000
+ check :on_lparen
1001
+ write "("
1002
+ next_token
1003
+ skip_space_or_newline
1004
+
1005
+ indent(@column) do
1006
+ visit_comma_separated_list args
1007
+ end
1008
+
1009
+ check :on_rparen
1010
+ write ")"
1011
+ next_token
1012
+ else
1013
+ visit args
1014
+ end
1015
+ end
1016
+
1017
+ def visit_comma_separated_list(nodes, inside_call = false)
1018
+ # This is `x, *y, z` on the left hand side of an assignment
1019
+ if nodes[0] == :mlhs_add_star
1020
+ visit_mlhs_add_star(nodes)
1021
+ return
1022
+ end
1023
+
1024
+ needs_indent = false
1025
+
1026
+ if inside_call
1027
+ if newline? || comment?
1028
+ needs_indent = true
1029
+ base_column = next_indent
1030
+ consume_end_of_line
1031
+ write_indent(base_column)
1032
+ else
1033
+ base_column = @column
1034
+ end
1035
+ end
1036
+
1037
+ nodes.each_with_index do |exp, i|
1038
+ maybe_indent(needs_indent, base_column) do
1039
+ if block_given?
1040
+ yield exp
1041
+ else
1042
+ visit exp
1043
+ end
1044
+ end
1045
+ skip_space
1046
+ unless last?(i, nodes)
1047
+ check :on_comma
1048
+ write ","
1049
+ next_token
1050
+ skip_space
1051
+
1052
+ if newline? || comment?
1053
+ indent(base_column || @indent) do
1054
+ consume_end_of_line(false, false, false)
1055
+ write_indent
1056
+ end
1057
+ else
1058
+ write_space " "
1059
+ skip_space_or_newline
1060
+ end
1061
+ end
1062
+ end
1063
+ end
1064
+
1065
+ def visit_mlhs_add_star(node)
1066
+ # [:mlhs_add_star, before, star, after]
1067
+ _, before, star, after = node
1068
+
1069
+ if before && !before.empty?
1070
+ visit_comma_separated_list before
1071
+ write_params_comma
1072
+ end
1073
+
1074
+ consume_op "*"
1075
+ skip_space_or_newline
1076
+
1077
+ visit star
1078
+
1079
+ if after && !after.empty?
1080
+ write_params_comma
1081
+ visit_comma_separated_list after
1082
+ end
1083
+ end
1084
+
1085
+ def visit_unary(node)
1086
+ # [:unary, :-@, [:vcall, [:@ident, "x", [1, 2]]]]
1087
+ check :on_op
1088
+ write current_token_value
1089
+ next_token
1090
+ skip_space_or_newline
1091
+ visit node[2]
1092
+ end
1093
+
1094
+ def visit_binary(node)
1095
+ # [:binary, left, op, right]
1096
+ _, left, op, right = node
1097
+
1098
+ visit left
1099
+ if space?
1100
+ needs_space = true
1101
+ else
1102
+ needs_space = op != :* && op != :/ && op != :**
1103
+ end
1104
+ skip_space
1105
+ write_space " " if needs_space
1106
+ check :on_op
1107
+ write current_token_value
1108
+ next_token
1109
+ indent_after_space right, false, needs_space
1110
+ end
1111
+
1112
+ def visit_class(node)
1113
+ # [:class,
1114
+ # name
1115
+ # superclass
1116
+ # [:bodystmt, body, nil, nil, nil]]
1117
+ _, name, superclass, body = node
1118
+
1119
+ consume_keyword "class"
1120
+ skip_space_or_newline
1121
+ write_space " "
1122
+ visit name
1123
+
1124
+ if superclass
1125
+ skip_space_or_newline
1126
+ write_space " "
1127
+ consume_op "<"
1128
+ skip_space_or_newline
1129
+ write_space " "
1130
+ visit superclass
1131
+ end
1132
+
1133
+ maybe_inline_body body
1134
+ end
1135
+
1136
+ def visit_module(node)
1137
+ # [:module,
1138
+ # name
1139
+ # [:bodystmt, body, nil, nil, nil]]
1140
+ _, name, body = node
1141
+
1142
+ consume_keyword "module"
1143
+ skip_space_or_newline
1144
+ write_space " "
1145
+ visit name
1146
+ maybe_inline_body body
1147
+ end
1148
+
1149
+ def maybe_inline_body(body)
1150
+ skip_space
1151
+ if semicolon? && empty_body?(body)
1152
+ next_token
1153
+ skip_space
1154
+ if newline?
1155
+ skip_space_or_newline
1156
+ visit body
1157
+ else
1158
+ write "; "
1159
+ skip_space_or_newline
1160
+ consume_keyword "end"
1161
+ end
1162
+ else
1163
+ visit body
1164
+ end
1165
+ end
1166
+
1167
+ def visit_def(node)
1168
+ # [:def,
1169
+ # [:@ident, "foo", [1, 6]],
1170
+ # [:params, nil, nil, nil, nil, nil, nil, nil],
1171
+ # [:bodystmt, [[:void_stmt]], nil, nil, nil]]
1172
+ _, name, params, body = node
1173
+
1174
+ consume_keyword "def"
1175
+ consume_space
1176
+
1177
+ push_hash(node) do
1178
+ visit_def_from_name name, params, body
1179
+ end
1180
+ end
1181
+
1182
+ def visit_def_with_receiver(node)
1183
+ # [:defs,
1184
+ # [:vcall, [:@ident, "foo", [1, 5]]],
1185
+ # [:@period, ".", [1, 8]],
1186
+ # [:@ident, "bar", [1, 9]],
1187
+ # [:params, nil, nil, nil, nil, nil, nil, nil],
1188
+ # [:bodystmt, [[:void_stmt]], nil, nil, nil]]
1189
+ _, receiver, period, name, params, body = node
1190
+
1191
+ consume_keyword "def"
1192
+ consume_space
1193
+ visit receiver
1194
+ skip_space_or_newline
1195
+
1196
+ check :on_period
1197
+ write "."
1198
+ next_token
1199
+ skip_space_or_newline
1200
+
1201
+ push_hash(node) do
1202
+ visit_def_from_name name, params, body
1203
+ end
1204
+ end
1205
+
1206
+ def visit_def_from_name(name, params, body)
1207
+ visit name
1208
+
1209
+ if params[0] == :paren
1210
+ params = params[1]
1211
+ end
1212
+
1213
+ skip_space
1214
+ if current_token_kind == :on_lparen
1215
+ next_token
1216
+ skip_space
1217
+ skip_semicolons
1218
+
1219
+ if empty_params?(params)
1220
+ skip_space_or_newline
1221
+ check :on_rparen
1222
+ next_token
1223
+ skip_space_or_newline
1224
+ else
1225
+ write "("
1226
+
1227
+ if newline? || comment?
1228
+ column = @column
1229
+ indent(column) do
1230
+ consume_end_of_line
1231
+ write_indent
1232
+ visit params
1233
+ end
1234
+ else
1235
+ indent(@column) do
1236
+ visit params
1237
+ end
1238
+ end
1239
+
1240
+ skip_space_or_newline
1241
+ check :on_rparen
1242
+ write ")"
1243
+ next_token
1244
+ end
1245
+ elsif !empty_params?(params)
1246
+ write "("
1247
+ visit params
1248
+ write ")"
1249
+ skip_space_or_newline
1250
+ end
1251
+
1252
+ visit body
1253
+ end
1254
+
1255
+ def empty_params?(node)
1256
+ _, a, b, c, d, e, f, g = node
1257
+ !a && !b && !c && !d && !e && !f && !g
1258
+ end
1259
+
1260
+ def visit_paren(node)
1261
+ # ( exps )
1262
+ #
1263
+ # [:paren, exps]
1264
+ check :on_lparen
1265
+ write "("
1266
+ next_token
1267
+ skip_space_or_newline
1268
+
1269
+ if node[1][0].is_a?(Symbol)
1270
+ visit node[1]
1271
+ else
1272
+ visit_exps node[1], false, false
1273
+ end
1274
+
1275
+ skip_space_or_newline
1276
+ check :on_rparen
1277
+ write ")"
1278
+ next_token
1279
+ end
1280
+
1281
+ def visit_params(node)
1282
+ # (def params)
1283
+ #
1284
+ # [:params, pre_rest_params, args_with_default, rest_param, post_rest_params, label_params, double_star_param, blockarg]
1285
+ _, pre_rest_params, args_with_default, rest_param, post_rest_params, label_params, double_star_param, blockarg = node
1286
+
1287
+ needs_comma = false
1288
+
1289
+ if pre_rest_params
1290
+ visit_comma_separated_list pre_rest_params
1291
+ needs_comma = true
1292
+ end
1293
+
1294
+ if args_with_default
1295
+ write_params_comma if needs_comma
1296
+ visit_comma_separated_list(args_with_default) do |arg, default|
1297
+ visit arg
1298
+ skip_space
1299
+ write_space " "
1300
+ consume_op "="
1301
+ skip_space_or_newline
1302
+ write_space " "
1303
+ visit default
1304
+ end
1305
+ needs_comma = true
1306
+ end
1307
+
1308
+ if rest_param
1309
+ # [:rest_param, [:@ident, "x", [1, 15]]]
1310
+ write_params_comma if needs_comma
1311
+ consume_op "*"
1312
+ skip_space_or_newline
1313
+ visit rest_param[1]
1314
+ needs_comma = true
1315
+ end
1316
+
1317
+ if post_rest_params
1318
+ write_params_comma if needs_comma
1319
+ visit_comma_separated_list post_rest_params
1320
+ needs_comma = true
1321
+ end
1322
+
1323
+ if label_params
1324
+ # [[label, value], ...]
1325
+ write_params_comma if needs_comma
1326
+ visit_comma_separated_list(label_params) do |label, value|
1327
+ # [:@label, "b:", [1, 20]]
1328
+ write label[1]
1329
+ next_token
1330
+ skip_space_or_newline
1331
+ if value
1332
+ consume_space
1333
+ track_hash_key
1334
+ visit value
1335
+ end
1336
+ end
1337
+ needs_comma = true
1338
+ end
1339
+
1340
+ if double_star_param
1341
+ write_params_comma if needs_comma
1342
+ consume_op "**"
1343
+ skip_space_or_newline
1344
+ visit double_star_param
1345
+ skip_space_or_newline
1346
+ needs_comma = true
1347
+ end
1348
+
1349
+ if blockarg
1350
+ # [:blockarg, [:@ident, "block", [1, 16]]]
1351
+ write_params_comma if needs_comma
1352
+ skip_space_or_newline
1353
+ consume_op "&"
1354
+ skip_space_or_newline
1355
+ visit blockarg[1]
1356
+ end
1357
+ end
1358
+
1359
+ def write_params_comma
1360
+ skip_space
1361
+ check :on_comma
1362
+ write ","
1363
+ next_token
1364
+ skip_space
1365
+
1366
+ if newline? || comment?
1367
+ consume_end_of_line
1368
+ write_indent
1369
+ else
1370
+ write_space " "
1371
+ skip_space_or_newline
1372
+ end
1373
+ end
1374
+
1375
+ def visit_array(node)
1376
+ # [:array, elements]
1377
+
1378
+ # Check if it's `%w(...)` or `%i(...)`
1379
+ if current_token_kind == :on_qwords_beg || current_token_kind == :on_qsymbols_beg
1380
+ visit_q_or_i_array(node)
1381
+ return
1382
+ end
1383
+
1384
+ _, elements = node
1385
+
1386
+ check :on_lbracket
1387
+ write "["
1388
+ next_token
1389
+
1390
+ if elements
1391
+ if elements[0].is_a?(Symbol)
1392
+ visit elements
1393
+ skip_space_or_newline
1394
+ else
1395
+ visit_literal_elements elements
1396
+ end
1397
+ else
1398
+ skip_space_or_newline
1399
+ end
1400
+
1401
+ check :on_rbracket
1402
+ write "]"
1403
+ next_token
1404
+ end
1405
+
1406
+ def visit_q_or_i_array(node)
1407
+ _, elements = node
1408
+
1409
+ write current_token_value.strip
1410
+
1411
+ # If there's a newline after `%w(`, write line and indent
1412
+ if current_token_value.include?("\n") && elements
1413
+ write_line
1414
+ write_indent(next_indent)
1415
+ end
1416
+
1417
+ next_token
1418
+
1419
+ if elements
1420
+ elements.each_with_index do |elem, i|
1421
+ # elem is [:@tstring_content, string, [1, 5]
1422
+ write elem[1].strip
1423
+ next_token
1424
+ unless last?(i, elements)
1425
+ check :on_words_sep
1426
+
1427
+ # On a newline, write line and indent
1428
+ if current_token_value.include?("\n")
1429
+ next_token
1430
+ write_line
1431
+ write_indent(next_indent)
1432
+ else
1433
+ next_token
1434
+ write_space " "
1435
+ end
1436
+ end
1437
+ end
1438
+ end
1439
+
1440
+ has_newline = false
1441
+
1442
+ while current_token_kind == :on_words_sep
1443
+ has_newline ||= current_token_value.include?("\n")
1444
+ next_token
1445
+ end
1446
+
1447
+ if has_newline
1448
+ write_line
1449
+ write_indent(next_indent)
1450
+ end
1451
+
1452
+ write ")"
1453
+ return
1454
+ end
1455
+
1456
+ def visit_hash(node)
1457
+ # [:hash, elements]
1458
+ _, elements = node
1459
+
1460
+ check :on_lbrace
1461
+ write "{"
1462
+ next_token
1463
+
1464
+ if elements
1465
+ # [:assoclist_from_args, elements]
1466
+ push_hash(node) do
1467
+ visit_literal_elements(elements[1])
1468
+ end
1469
+ else
1470
+ skip_space_or_newline
1471
+ end
1472
+
1473
+ check :on_rbrace
1474
+ write "}"
1475
+ next_token
1476
+ end
1477
+
1478
+ def visit_hash_key_value(node)
1479
+ # key => value
1480
+ #
1481
+ # [:assoc_new, key, value]
1482
+ _, key, value = node
1483
+
1484
+ visit key
1485
+
1486
+ skip_space_or_newline
1487
+ consume_space
1488
+
1489
+ track_hash_key
1490
+
1491
+ # Don't output `=>` for keys that are `label: value`
1492
+ unless key[0] == :@label
1493
+ consume_op "=>"
1494
+ skip_space_or_newline
1495
+ write_space " "
1496
+ end
1497
+
1498
+ visit value
1499
+ end
1500
+
1501
+ def visit_splat_inside_hash(node)
1502
+ # **exp
1503
+ #
1504
+ # [:assoc_splat, exp]
1505
+ consume_op "**"
1506
+ skip_space_or_newline
1507
+ visit node[1]
1508
+ end
1509
+
1510
+ def visit_range(node, inclusive)
1511
+ # [:dot2, left, right]
1512
+ _, left, right = node
1513
+
1514
+ visit left
1515
+ skip_space_or_newline
1516
+ consume_op(inclusive ? ".." : "...")
1517
+ skip_space_or_newline
1518
+ visit right
1519
+ end
1520
+
1521
+ def visit_regexp_literal(node)
1522
+ # [:regexp_literal, pieces, [:@regexp_end, "/", [1, 1]]]
1523
+ _, pieces = node
1524
+
1525
+ check :on_regexp_beg
1526
+ write current_token_value
1527
+ next_token
1528
+
1529
+ visit_exps pieces, false, false
1530
+
1531
+ check :on_regexp_end
1532
+ write current_token_value
1533
+ next_token
1534
+ end
1535
+
1536
+ def visit_array_access(node)
1537
+ # exp[arg1, ..., argN]
1538
+ #
1539
+ # [:aref, name, args]
1540
+ _, name, args = node
1541
+
1542
+ visit_array_getter_or_setter name, args
1543
+ end
1544
+
1545
+ def visit_array_setter(node)
1546
+ # exp[arg1, ..., argN]
1547
+ # (followed by `=`, though not included in this node)
1548
+ #
1549
+ # [:aref_field, name, args]
1550
+ _, name, args = node
1551
+
1552
+ visit_array_getter_or_setter name, args
1553
+ end
1554
+
1555
+ def visit_array_getter_or_setter(name, args)
1556
+ visit name
1557
+
1558
+ check :on_lbracket
1559
+ write "["
1560
+ next_token
1561
+
1562
+ column = @column
1563
+
1564
+ skip_space
1565
+
1566
+ if newline? || comment?
1567
+ needed_indent = next_indent
1568
+ if args
1569
+ consume_end_of_line
1570
+ write_indent(needed_indent)
1571
+ else
1572
+ skip_space_or_newline
1573
+ end
1574
+ else
1575
+ needed_indent = column
1576
+ end
1577
+
1578
+ if args
1579
+ indent(needed_indent) do
1580
+ visit args
1581
+ end
1582
+ end
1583
+
1584
+ skip_space_or_newline
1585
+
1586
+ check :on_rbracket
1587
+ write "]"
1588
+ next_token
1589
+ end
1590
+
1591
+ def visit_sclass(node)
1592
+ # class << self
1593
+ #
1594
+ # [:sclass, target, body]
1595
+ _, target, body = node
1596
+
1597
+ consume_keyword "class"
1598
+ consume_space
1599
+ consume_op "<<"
1600
+ consume_space
1601
+ visit target
1602
+ visit body
1603
+ end
1604
+
1605
+ def visit_setter(node)
1606
+ # foo.bar
1607
+ # (followed by `=`, though not included in this node)
1608
+ #
1609
+ # [:field, receiver, :".", name]
1610
+ _, receiver, dot, name = node
1611
+
1612
+ @dot_column = nil
1613
+ visit receiver
1614
+ skip_space
1615
+
1616
+ if newline? || comment?
1617
+ consume_end_of_line
1618
+
1619
+ write_indent(@dot_column || next_indent)
1620
+ end
1621
+
1622
+ # Remember dot column
1623
+ dot_column = @column
1624
+ check :on_period
1625
+ write "."
1626
+ next_token
1627
+ skip_space
1628
+
1629
+ if newline? || comment?
1630
+ consume_end_of_line
1631
+ write_indent(next_indent)
1632
+ else
1633
+ skip_space_or_newline
1634
+ end
1635
+
1636
+ visit name
1637
+
1638
+ # Only set it after we visit the call after the dot,
1639
+ # so we remember the outmost dot position
1640
+ @dot_column = dot_column
1641
+ end
1642
+
1643
+ def visit_return(node)
1644
+ # [:return, exp]
1645
+ visit_control_keyword node, "return"
1646
+ end
1647
+
1648
+ def visit_break(node)
1649
+ # [:break, exp]
1650
+ visit_control_keyword node, "break"
1651
+ end
1652
+
1653
+ def visit_next(node)
1654
+ # [:next, exp]
1655
+ visit_control_keyword node, "next"
1656
+ end
1657
+
1658
+ def visit_yield(node)
1659
+ # [:yield, exp]
1660
+ visit_control_keyword node, "yield"
1661
+ end
1662
+
1663
+ def visit_control_keyword(node, keyword)
1664
+ _, exp = node
1665
+
1666
+ consume_keyword keyword
1667
+
1668
+ if exp && !exp.empty?
1669
+ consume_space if space?
1670
+
1671
+ indent(@column) do
1672
+ visit node[1]
1673
+ end
1674
+ end
1675
+ end
1676
+
1677
+ def visit_lambda(node)
1678
+ # [:lambda, [:params, nil, nil, nil, nil, nil, nil, nil], [[:void_stmt]]]
1679
+ _, params, body = node
1680
+
1681
+ check :on_tlambda
1682
+ write "->"
1683
+ next_token
1684
+ skip_space_or_newline
1685
+
1686
+ if empty_params?(params)
1687
+ if current_token_kind == :on_lparen
1688
+ next_token
1689
+ skip_space_or_newline
1690
+ check :on_rparen
1691
+ next_token
1692
+ skip_space_or_newline
1693
+ end
1694
+ else
1695
+ visit params
1696
+ consume_space
1697
+ end
1698
+
1699
+ if void_exps?(body)
1700
+ consume_token :on_tlambeg
1701
+ consume_space
1702
+ consume_token :on_rbrace
1703
+ return
1704
+ end
1705
+
1706
+ brace = current_token_value == "{"
1707
+
1708
+ if brace
1709
+ closing_brace_token = find_closing_brace_token
1710
+
1711
+ # Check if the whole block fits into a single line
1712
+ if current_token[0][0] == closing_brace_token[0][0]
1713
+ consume_token :on_tlambeg
1714
+
1715
+ consume_space
1716
+ visit_exps body, false, false
1717
+ consume_space
1718
+
1719
+ consume_token :on_rbrace
1720
+ return
1721
+ end
1722
+
1723
+ consume_token :on_tlambeg
1724
+ else
1725
+ consume_keyword "do"
1726
+ end
1727
+
1728
+ indent_body body
1729
+
1730
+ write_indent
1731
+
1732
+ if brace
1733
+ consume_token :on_rbrace
1734
+ else
1735
+ consume_keyword "end"
1736
+ end
1737
+ end
1738
+
1739
+ def visit_super(node)
1740
+ # [:super, args]
1741
+ _, args = node
1742
+
1743
+ consume_keyword "super"
1744
+
1745
+ if space?
1746
+ consume_space
1747
+ visit_command_end node, args
1748
+ else
1749
+ visit_call_at_paren node, args
1750
+ end
1751
+ end
1752
+
1753
+ def visit_literal_elements(elements)
1754
+ base_column = @column
1755
+
1756
+ skip_space
1757
+
1758
+ # If there's a newline right at the beginning,
1759
+ # write it, and we'll indent element and always
1760
+ # add a trailing comma to the last element
1761
+ needs_trailing_comma = newline? || comment?
1762
+ if needs_trailing_comma
1763
+ needed_indent = next_indent
1764
+ indent { consume_end_of_line }
1765
+ write_indent(needed_indent)
1766
+ else
1767
+ needed_indent = base_column
1768
+ end
1769
+
1770
+ elements.each_with_index do |elem, i|
1771
+ if needs_trailing_comma
1772
+ indent(needed_indent) { visit elem }
1773
+ else
1774
+ visit elem
1775
+ end
1776
+ skip_space
1777
+
1778
+ if comma?
1779
+ is_last = last?(i, elements)
1780
+
1781
+ write "," unless is_last
1782
+ next_token
1783
+ skip_space
1784
+
1785
+ if newline? || comment?
1786
+ if is_last
1787
+ # Nothing
1788
+ else
1789
+ consume_end_of_line
1790
+ write_indent(needed_indent)
1791
+ end
1792
+ else
1793
+ write_space " " unless is_last
1794
+ end
1795
+ end
1796
+ end
1797
+
1798
+ if needs_trailing_comma
1799
+ write ","
1800
+ consume_end_of_line
1801
+ write_indent
1802
+ elsif comment?
1803
+ consume_end_of_line
1804
+ else
1805
+ skip_space_or_newline
1806
+ end
1807
+ end
1808
+
1809
+ def visit_if(node)
1810
+ visit_if_or_unless node, "if"
1811
+ end
1812
+
1813
+ def visit_unless(node)
1814
+ visit_if_or_unless node, "unless"
1815
+ end
1816
+
1817
+ def visit_if_or_unless(node, keyword, check_end = true)
1818
+ # if cond
1819
+ # then_body
1820
+ # else
1821
+ # else_body
1822
+ # end
1823
+ #
1824
+ # [:if, cond, then, else]
1825
+ consume_keyword(keyword)
1826
+ consume_space
1827
+ visit node[1]
1828
+ skip_space
1829
+
1830
+ # Remove "then"
1831
+ if keyword?("then")
1832
+ next_token
1833
+ skip_space
1834
+ end
1835
+
1836
+ indent_body node[2]
1837
+ if else_body = node[3]
1838
+ # [:else, else_contents]
1839
+ # [:elsif, cond, then, else]
1840
+ write_indent
1841
+
1842
+ case else_body[0]
1843
+ when :else
1844
+ consume_keyword "else"
1845
+ indent_body else_body[1]
1846
+ when :elsif
1847
+ visit_if_or_unless else_body, "elsif", false
1848
+ else
1849
+ bug "expected else or elsif, not #{else_body[0]}"
1850
+ end
1851
+ end
1852
+
1853
+ if check_end
1854
+ write_indent
1855
+ consume_keyword "end"
1856
+ end
1857
+ end
1858
+
1859
+ def visit_while(node)
1860
+ # [:while, cond, body]
1861
+ visit_while_or_until node, "while"
1862
+ end
1863
+
1864
+ def visit_until(node)
1865
+ # [:until, cond, body]
1866
+ visit_while_or_until node, "until"
1867
+ end
1868
+
1869
+ def visit_while_or_until(node, keyword)
1870
+ _, cond, body = node
1871
+
1872
+ consume_keyword keyword
1873
+ consume_space
1874
+
1875
+ visit cond
1876
+
1877
+ skip_space
1878
+
1879
+ # Keep `while cond; end` as is
1880
+ if semicolon? && void_exps?(body)
1881
+ next_token
1882
+ skip_space
1883
+
1884
+ if keyword?("end")
1885
+ write "; end"
1886
+ next_token
1887
+ return
1888
+ end
1889
+ end
1890
+
1891
+ indent_body body
1892
+
1893
+ write_indent
1894
+ consume_keyword "end"
1895
+ end
1896
+
1897
+ def visit_case(node)
1898
+ # [:case, cond, case_when]
1899
+ _, cond, case_when = node
1900
+
1901
+ consume_keyword "case"
1902
+
1903
+ if cond
1904
+ consume_space
1905
+ visit cond
1906
+ end
1907
+
1908
+ consume_end_of_line
1909
+
1910
+ write_indent
1911
+ visit case_when
1912
+
1913
+ write_indent
1914
+ consume_keyword "end"
1915
+ end
1916
+
1917
+ def visit_when(node)
1918
+ # [:when, conds, body, next_exp]
1919
+ _, conds, body, next_exp = node
1920
+
1921
+ consume_keyword "when"
1922
+ consume_space
1923
+
1924
+ indent(@column) do
1925
+ visit_comma_separated_list conds
1926
+ end
1927
+
1928
+ then_keyword = keyword?("then")
1929
+ inline = then_keyword || semicolon?
1930
+ if then_keyword
1931
+ next_token
1932
+ skip_space
1933
+ skip_semicolons
1934
+
1935
+ if newline? || comment?
1936
+ inline = false
1937
+ else
1938
+ write " then "
1939
+ end
1940
+ elsif semicolon?
1941
+ skip_semicolons
1942
+
1943
+ if newline? || comment?
1944
+ inline = false
1945
+ else
1946
+ write "; "
1947
+ end
1948
+ end
1949
+
1950
+ if inline
1951
+ indent do
1952
+ visit_exps body
1953
+ end
1954
+ else
1955
+ indent_body body
1956
+ end
1957
+
1958
+ if next_exp
1959
+ write_indent
1960
+
1961
+ if next_exp[0] == :else
1962
+ # [:else, body]
1963
+ consume_keyword "else"
1964
+ skip_space
1965
+
1966
+ if newline? || semicolon? || comment?
1967
+ indent_body next_exp[1]
1968
+ else
1969
+ write_space " "
1970
+ visit_exps next_exp[1]
1971
+ end
1972
+ else
1973
+ visit next_exp
1974
+ end
1975
+ end
1976
+ end
1977
+
1978
+ def consume_space
1979
+ skip_space_or_newline
1980
+ write_space " "
1981
+ end
1982
+
1983
+ def skip_space
1984
+ while space?
1985
+ next_token
1986
+ end
1987
+ end
1988
+
1989
+ def skip_space_or_newline(want_semicolon = false)
1990
+ found_newline = false
1991
+ found_comment = false
1992
+ last = nil
1993
+
1994
+ while true
1995
+ case current_token_kind
1996
+ when :on_sp
1997
+ next_token
1998
+ when :on_nl, :on_ignored_nl
1999
+ next_token
2000
+ last = :newline
2001
+ found_newline = true
2002
+ when :on_semicolon
2003
+ if !found_newline && !found_comment
2004
+ write "; "
2005
+ end
2006
+ next_token
2007
+ last = :semicolon
2008
+ when :on_comment
2009
+ write_line if last == :newline
2010
+
2011
+ write_indent if found_comment
2012
+ if current_token_value.end_with?("\n")
2013
+ write current_token_value.rstrip
2014
+ write_line
2015
+ else
2016
+ write current_token_value
2017
+ end
2018
+ next_token
2019
+ found_comment = true
2020
+ last = :comment
2021
+ else
2022
+ break
2023
+ end
2024
+ end
2025
+ end
2026
+
2027
+ def skip_semicolons
2028
+ while semicolon? || space?
2029
+ next_token
2030
+ end
2031
+ end
2032
+
2033
+ def empty_body?(body)
2034
+ body[0] == :bodystmt &&
2035
+ body[1].size == 1 &&
2036
+ body[1][0][0] == :void_stmt
2037
+ end
2038
+
2039
+ def consume_token(kind)
2040
+ check kind
2041
+ consume_token_value(current_token_value)
2042
+ next_token
2043
+ end
2044
+
2045
+ def consume_token_value(value)
2046
+ write value
2047
+
2048
+ # If the value has newlines, we need to adjust line and column
2049
+ number_of_lines = value.count("\n")
2050
+ if number_of_lines > 0
2051
+ @line += number_of_lines
2052
+ last_line_index = value.rindex("\n")
2053
+ @column = value.size - (last_line_index + 1)
2054
+ @last_was_newline = @column == 0
2055
+ end
2056
+ end
2057
+
2058
+ def consume_keyword(value)
2059
+ check :on_kw
2060
+ if current_token_value != value
2061
+ bug "Expected keyword #{value}, not #{current_token_value}"
2062
+ end
2063
+ write value
2064
+ next_token
2065
+ end
2066
+
2067
+ def consume_op(value)
2068
+ check :on_op
2069
+ if current_token_value != value
2070
+ bug "Expected op #{value}, not #{current_token_value}"
2071
+ end
2072
+ write value
2073
+ next_token
2074
+ end
2075
+
2076
+ # Consume and print an end of line, handling semicolons and comments
2077
+ #
2078
+ # - at_prefix: are we at a point before an expression? (if so, we don't need a space before the first comment)
2079
+ # - want_semicolon: do we want do print a semicolon to separate expressions?
2080
+ # - want_multiline: do we want multiple lines to appear, or at most one?
2081
+ def consume_end_of_line(at_prefix = false, want_semicolon = false, want_multiline = true)
2082
+ found_newline = false # Did we find any newline during this method?
2083
+ last = nil # Last token kind found
2084
+ multilple_lines = false # Did we pass through more than one newline?
2085
+ last_comment_has_newline = false # Does the last comment has a newline?
2086
+
2087
+ while true
2088
+ case current_token_kind
2089
+ when :on_sp
2090
+ # Ignore spaces
2091
+ next_token
2092
+ when :on_nl, :on_ignored_nl
2093
+ if last == :newline
2094
+ # If we pass through consecutive newlines, don't print them
2095
+ # yet, but remember this fact
2096
+ multilple_lines = true unless last_comment_has_newline
2097
+ else
2098
+ # If we just printed a comment that had a newline,
2099
+ # we must print two newlines because we remove newlines from comments (rstrip call)
2100
+ if last == :comment && last_comment_has_newline
2101
+ write_line
2102
+ multilple_lines = true
2103
+ else
2104
+ write_line
2105
+ multilple_lines = false
2106
+ end
2107
+ end
2108
+ found_newline = true
2109
+ next_token
2110
+ last = :newline
2111
+ when :on_semicolon
2112
+ next_token
2113
+ # If we want to print semicolons and we didn't find a newline yet,
2114
+ # print it, but only if it's not followed by a newline
2115
+ if !found_newline && want_semicolon && last != :semicolon
2116
+ skip_space
2117
+ case current_token_kind
2118
+ when :on_ignored_nl, :on_eof
2119
+ else
2120
+ write "; "
2121
+ last = :semicolon
2122
+ end
2123
+ end
2124
+ multilple_lines = false
2125
+ when :on_comment
2126
+ if last == :comment
2127
+ # Since we remove newlines from comments, we must add the last
2128
+ # one if it was a comment
2129
+ write_line
2130
+ write_indent
2131
+ else
2132
+ if found_newline
2133
+ # Write line or second line if needed
2134
+ write_line if last != :newline || multilple_lines
2135
+ write_indent
2136
+ else
2137
+ # If we didn't find any newline yet, this is the first comment,
2138
+ # so append a space if needed (for example after an expression)
2139
+ write_space " " unless at_prefix
2140
+ @comments_positions << [@line, @column, @indent, nil]
2141
+ end
2142
+ end
2143
+ last_comment_has_newline = current_token_value.end_with?("\n")
2144
+ write current_token_value.rstrip
2145
+ next_token
2146
+ last = :comment
2147
+ multilple_lines = false
2148
+ else
2149
+ break
2150
+ end
2151
+ end
2152
+
2153
+ # Output a newline if we didn't do so yet:
2154
+ # either we didn't find a newline and we are at the end of a line (and we didn't just pass a semicolon),
2155
+ # or the last thing was a comment (from which we removed the newline)
2156
+ # or we just passed multiple lines (but printed only one)
2157
+ if (!found_newline && !at_prefix && !(want_semicolon && last == :semicolon)) ||
2158
+ last == :comment ||
2159
+ (multilple_lines && want_multiline)
2160
+ write_line
2161
+ end
2162
+ end
2163
+
2164
+ def indent(value = nil)
2165
+ if value
2166
+ old_indent = @indent
2167
+ @indent = value
2168
+ yield
2169
+ @indent = old_indent
2170
+ else
2171
+ @indent += @indent_size
2172
+ yield
2173
+ @indent -= @indent_size
2174
+ end
2175
+ end
2176
+
2177
+ def indent_body(exps)
2178
+ indent do
2179
+ consume_end_of_line(false, false, false)
2180
+ end
2181
+
2182
+ # If the body is [[:void_stmt]] it's an empty body
2183
+ # so there's nothing to write
2184
+ if exps.size == 1 && exps[0][0] == :void_stmt
2185
+ skip_space_or_newline
2186
+ else
2187
+ indent do
2188
+ visit_exps exps, true
2189
+ end
2190
+ write_line unless @last_was_newline
2191
+ end
2192
+ end
2193
+
2194
+ def maybe_indent(toggle, indent_size)
2195
+ if toggle
2196
+ indent(indent_size) do
2197
+ yield
2198
+ end
2199
+ else
2200
+ yield
2201
+ end
2202
+ end
2203
+
2204
+ def write(value)
2205
+ @output << value
2206
+ @last_was_newline = false
2207
+ @column += value.size
2208
+ end
2209
+
2210
+ def write_space(value)
2211
+ @output << value
2212
+ @column += value.size
2213
+ end
2214
+
2215
+ def write_line
2216
+ @output << "\n"
2217
+ @last_was_newline = true
2218
+ @column = 0
2219
+ @line += 1
2220
+ end
2221
+
2222
+ def write_indent(indent = @indent)
2223
+ indent.times do
2224
+ @output << " "
2225
+ end
2226
+ @column += indent
2227
+ @last_was_newline = false
2228
+ end
2229
+
2230
+ def indent_after_space(node, sticky = false, want_space = true)
2231
+ skip_space
2232
+ case current_token_kind
2233
+ when :on_ignored_nl, :on_comment
2234
+ indent do
2235
+ consume_end_of_line
2236
+ write_indent
2237
+ visit node
2238
+ end
2239
+ else
2240
+ write_space " " if want_space
2241
+ if sticky
2242
+ indent(@column) do
2243
+ visit node
2244
+ end
2245
+ else
2246
+ visit node
2247
+ end
2248
+ end
2249
+ end
2250
+
2251
+ def next_indent
2252
+ @indent + @indent_size
2253
+ end
2254
+
2255
+ def check(kind)
2256
+ if current_token_kind != kind
2257
+ bug "Expected token #{kind}, not #{current_token_kind}"
2258
+ end
2259
+ end
2260
+
2261
+ def bug(msg)
2262
+ raise Rufo::Bug.new("#{msg} at #{current_token}")
2263
+ end
2264
+
2265
+ # [[1, 0], :on_int, "1"]
2266
+ def current_token
2267
+ @tokens.last
2268
+ end
2269
+
2270
+ def current_token_kind
2271
+ tok = current_token
2272
+ tok ? tok[1] : :on_eof
2273
+ end
2274
+
2275
+ def current_token_value
2276
+ tok = current_token
2277
+ tok ? tok[2] : ""
2278
+ end
2279
+
2280
+ def keyword?(kw)
2281
+ current_token_kind == :on_kw && current_token_value == kw
2282
+ end
2283
+
2284
+ def newline?
2285
+ current_token_kind == :on_nl || current_token_kind == :on_ignored_nl
2286
+ end
2287
+
2288
+ def comment?
2289
+ current_token_kind == :on_comment
2290
+ end
2291
+
2292
+ def semicolon?
2293
+ current_token_kind == :on_semicolon
2294
+ end
2295
+
2296
+ def comma?
2297
+ current_token_kind == :on_comma
2298
+ end
2299
+
2300
+ def space?
2301
+ current_token_kind == :on_sp
2302
+ end
2303
+
2304
+ def void_exps?(node)
2305
+ node.size == 1 && node[0].size == 1 && node[0][0] == :void_stmt
2306
+ end
2307
+
2308
+ def find_closing_brace_token
2309
+ count = 0
2310
+ @tokens.reverse_each do |token|
2311
+ (line, column), kind = token
2312
+ case kind
2313
+ when :on_lbrace, :on_tlambeg
2314
+ count += 1
2315
+ when :on_rbrace
2316
+ count -= 1
2317
+ return token if count == 0
2318
+ end
2319
+ end
2320
+ end
2321
+
2322
+ def next_token
2323
+ @tokens.pop
2324
+ end
2325
+
2326
+ def last?(i, array)
2327
+ i == array.size - 1
2328
+ end
2329
+
2330
+ def push_call(call)
2331
+ old_call = @current_call
2332
+ @current_call = call
2333
+
2334
+ # A call can specify hash arguments so it acts as a
2335
+ # hash for key alignment purposes
2336
+ push_hash(call) do
2337
+ yield
2338
+ end
2339
+
2340
+ @current_call = old_call
2341
+ end
2342
+
2343
+ def push_hash(node)
2344
+ old_hash = @current_hash
2345
+ @current_hash = node
2346
+ yield
2347
+ @current_hash = old_hash
2348
+ end
2349
+
2350
+ def do_align_comments
2351
+ do_align @comments_positions
2352
+ end
2353
+
2354
+ def do_align_assignments
2355
+ do_align @assignments_positions
2356
+ end
2357
+
2358
+ def do_align_hash_keys
2359
+ do_align @hash_keys_positions
2360
+ end
2361
+
2362
+ def do_align(elements)
2363
+ lines = @output.lines
2364
+
2365
+ elements.reject! { |l, c, indent, id, off, ignore| ignore == :ignore }
2366
+
2367
+ # Chunk comments that are in consecutive lines
2368
+ chunks = elements.chunk_while do |(l1, c1, i1, id1), (l2, c2, i2, id2)|
2369
+ l1 + 1 == l2 && i1 == i2 && id1 == id2
2370
+ end
2371
+
2372
+ chunks.each do |comments|
2373
+ next if comments.size == 1
2374
+
2375
+ max_column = comments.map { |l, c| c }.max
2376
+ comments.each do |(line, column, _, _, offset)|
2377
+ next if column == max_column
2378
+
2379
+ split_index = column
2380
+ split_index -= offset if offset
2381
+
2382
+ target_line = lines[line]
2383
+
2384
+ before = target_line[0...split_index]
2385
+ after = target_line[split_index..-1]
2386
+
2387
+ filler = " " * (max_column - column)
2388
+ lines[line] = "#{before}#{filler}#{after}"
2389
+ end
2390
+ end
2391
+
2392
+ @output = lines.join
198
2393
  end
199
2394
 
200
2395
  def result