rufo 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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