prettyrb 0.1.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ module Prettyrb
2
+ MAX_LINE_LENGTH = 100
3
+
4
+ class Formatter
5
+ def initialize(code)
6
+ @code = code
7
+ end
8
+
9
+ def format
10
+ parser = Parser::CurrentRuby.new(Prettyrb::Builder.new)
11
+
12
+ parser.diagnostics.all_errors_are_fatal = true
13
+ parser.diagnostics.ignore_warnings = true
14
+
15
+ parser.diagnostics.consumer = lambda do |diagnostic|
16
+ $stderr.puts(diagnostic.render)
17
+ end
18
+
19
+ root_node, _comments = parser.parse_with_comments(
20
+ Parser::CurrentRuby.send(:setup_source_buffer, "file='(string)'", 1, @code, parser.default_encoding)
21
+ )
22
+
23
+ visitor = Visitor.new(root_node)
24
+ visitor.visit(root_node)
25
+
26
+ visitor.output
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :code
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ class AndNode < BaseNode
4
+ include LogicalOperatorHelper
5
+
6
+ def operator
7
+ "&&"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ require "delegate"
2
+
3
+ module Prettyrb
4
+ module Nodes
5
+ class BaseNode < Parser::AST::Node
6
+ def initialize(type, children, properties)
7
+ @mutable = {}
8
+
9
+ super
10
+
11
+ children&.each do |child|
12
+ next unless child.is_a?(BaseNode)
13
+ child.parent = self
14
+ end
15
+
16
+ self
17
+ end
18
+
19
+ def parent
20
+ @mutable[:parent]
21
+ end
22
+
23
+ def string?
24
+ type == :str || type == :dstr
25
+ end
26
+
27
+ protected
28
+
29
+ def parent=(parent)
30
+ @mutable[:parent] = parent
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ class DefNode < BaseNode
4
+ def name
5
+ children[0]
6
+ end
7
+
8
+ def args
9
+ children[1]
10
+ end
11
+
12
+ def body
13
+ children[2]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ class DstrNode < BaseNode
4
+ include StringHelper
5
+
6
+ HEREDOC_TYPE_REGEX = /<<(.)?/
7
+
8
+ def format
9
+ raw_content = loc.expression.source
10
+ content = raw_content[1...-1]
11
+
12
+ if raw_content[0] == "'"
13
+ content.gsub('"', '\\"').gsub('#{', '\\#{')
14
+ else
15
+ content.gsub("\\", "\\\\")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,63 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ class IfNode < BaseNode
4
+ def if_type
5
+ if is_elsif?
6
+ "elsif"
7
+ elsif unless_node?
8
+ "unless"
9
+ else
10
+ "if"
11
+ end
12
+ end
13
+
14
+ def conditions
15
+ children[0]
16
+ end
17
+
18
+ def body_node
19
+ if unless_node?
20
+ children[2]
21
+ else
22
+ children[1]
23
+ end
24
+ end
25
+
26
+ def else_body_node
27
+ if unless_node?
28
+ children[1]
29
+ else
30
+ children[2]
31
+ end
32
+ end
33
+
34
+ def has_elsif?
35
+ else_body_node&.type == :if && children[1]&.type != :if
36
+ end
37
+
38
+ def is_elsif?
39
+ parent&.type == :if && parent&.children[1]&.type != :if
40
+ end
41
+
42
+ def unless_node?
43
+ children[1].nil? && children[2] != :if
44
+ end
45
+
46
+ def elsif_branches
47
+ if has_elsif?
48
+ [else_body_node] + else_body_node.elsif_branches
49
+ else
50
+ []
51
+ end
52
+ end
53
+
54
+ def else_branch
55
+ if has_elsif?
56
+ elsif_branches.last.children[2]
57
+ else
58
+ else_body_node
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,13 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ module LogicalOperatorHelper
4
+ def left
5
+ children[0]
6
+ end
7
+
8
+ def right
9
+ children[1]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ class OrNode < BaseNode
4
+ include LogicalOperatorHelper
5
+
6
+ def operator
7
+ "||"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ class RegexpNode < BaseNode
4
+ include StringHelper
5
+
6
+ PERCENT_PAIRS = {
7
+ "{" => "}",
8
+ "[" => "]",
9
+ "(" => ")",
10
+ "<" => ">",
11
+ }
12
+
13
+ def percent?
14
+ loc.expression.source.start_with?("%")
15
+ end
16
+
17
+ def percent_type
18
+ loc.expression.source[1]
19
+ end
20
+
21
+ def start_delimiter
22
+ loc.expression.source[2]
23
+ end
24
+
25
+ def end_delimiter
26
+ PERCENT_PAIRS.fetch(start_delimiter, start_delimiter)
27
+ end
28
+
29
+ def format
30
+ raw_content = loc.expression.source
31
+ content = raw_content[1...-1]
32
+
33
+ if raw_content[0] == "'"
34
+ content.gsub('"', '\\"').gsub('#{', '\\#{')
35
+ else
36
+ content.gsub("\\", "\\\\")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,63 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ class SendNode < BaseNode
4
+ def target
5
+ children[0]
6
+ end
7
+
8
+ def method
9
+ children[1]
10
+ end
11
+
12
+ def arguments
13
+ children[2..-1]
14
+ end
15
+
16
+ def self_target?
17
+ children[2].nil? && method.to_s.end_with?("@")
18
+ end
19
+
20
+ def infix?
21
+ !children[1].to_s.match?(/^[a-zA-Z_]/)
22
+ end
23
+
24
+ def negative?
25
+ children[1] == :-@ && children[2].nil?
26
+ end
27
+
28
+ def negate?
29
+ children[1] == :!
30
+ end
31
+
32
+ def array_assignment?
33
+ children[1] == :[]=
34
+ end
35
+
36
+ def left_hand_mass_assignment?
37
+ parent&.type == :mlhs && method.to_s.end_with?("=")
38
+ end
39
+
40
+ def array_access?
41
+ children[1] == :[]
42
+ end
43
+
44
+ def called_on_heredoc?
45
+ child = target
46
+
47
+ while child&.type == :send || child&.string?
48
+ return true if child.string? && child.heredoc?
49
+ child = child.children[0]
50
+ return false unless child.respond_to?(:type)
51
+ end
52
+
53
+ false
54
+ end
55
+
56
+ def heredoc_arguments?
57
+ arguments.any? do |child|
58
+ child.string? && child.heredoc? || (child.type == :send && child.called_on_heredoc?)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,18 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ class StrNode < BaseNode
4
+ include StringHelper
5
+
6
+ def format
7
+ raw_content = loc.expression.source
8
+ content = raw_content[1...-1]
9
+
10
+ if raw_content[0] == "'"
11
+ content.gsub('"', '\\"').gsub('#{', '\\#{')
12
+ else
13
+ content.gsub("\\", "\\\\")
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ module Prettyrb
2
+ module Nodes
3
+ module StringHelper
4
+ HEREDOC_TYPE_REGEX = /<<([~-])?/
5
+
6
+ def percent_string?
7
+ loc.expression.source.start_with?("%")
8
+ end
9
+
10
+ def percent_character
11
+ loc.expression.source[1]
12
+ end
13
+
14
+ def start_delimiter
15
+ loc.expression.source[2]
16
+ end
17
+
18
+ def closing_delimiter
19
+ loc.expression.source.rstrip[-1]
20
+ end
21
+
22
+ def heredoc_identifier
23
+ loc.heredoc_end.source.strip
24
+ end
25
+
26
+ def heredoc_type
27
+ # Always use indentable ending heredoc type if no type was provided
28
+ #
29
+ # eg: <<RUBY becomes <<-RUBY since <<- allows the ending identifier
30
+ # to be indented
31
+ loc.expression.source.match(HEREDOC_TYPE_REGEX)[1] || "-"
32
+ end
33
+
34
+ def heredoc_body
35
+ loc.heredoc_body.source
36
+ end
37
+
38
+ def heredoc?
39
+ !!loc.respond_to?(:heredoc_body)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,3 +1,3 @@
1
1
  module Prettyrb
2
- VERSION = "0.1.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -0,0 +1,947 @@
1
+ module Prettyrb
2
+ class Visitor
3
+ include Document::DSL
4
+
5
+ MAX_LENGTH = 100
6
+ SINGLE_LINE = "single_line"
7
+ MULTI_LINE = "multi_line"
8
+
9
+ FNAMES = [
10
+ "..",
11
+ "|",
12
+ "ˆ",
13
+ "&",
14
+ "<=>",
15
+ "==",
16
+ "===",
17
+ "=˜",
18
+ ">",
19
+ ">=",
20
+ "<",
21
+ "<=",
22
+ "+",
23
+ "-",
24
+ "*",
25
+ "/",
26
+ "%",
27
+ "**",
28
+ "<<",
29
+ ">>",
30
+ "~",
31
+ "+@",
32
+ "-@",
33
+ "[]",
34
+ "[]="
35
+ ]
36
+ VALID_SYMBOLS = FNAMES + ["!", "!="]
37
+
38
+ def initialize(root_node)
39
+ @output = ""
40
+ @builder = visit(root_node)
41
+ end
42
+
43
+ def output
44
+ Writer.new(@builder).to_s
45
+ end
46
+
47
+ def visit(node)
48
+ case node.type
49
+ when :module
50
+ body = if node.children[1]
51
+ concat(
52
+ indent(
53
+ hardline,
54
+ visit(node.children[1]),
55
+ ),
56
+ hardline
57
+ )
58
+ end
59
+
60
+ concat(
61
+ "module ",
62
+ visit(node.children[0]),
63
+ body,
64
+ "end"
65
+ )
66
+ when :sclass
67
+ body = if node.children[1]
68
+ concat(
69
+ indent(
70
+ hardline,
71
+ visit(node.children[1]),
72
+ ),
73
+ hardline,
74
+ )
75
+ end
76
+
77
+ concat(
78
+ "class << ",
79
+ visit(node.children[0]),
80
+ body,
81
+ "end"
82
+ )
83
+ when :class
84
+ inheritance = if node.children[1]
85
+ concat(
86
+ " < ",
87
+ visit(node.children[1]),
88
+ )
89
+ end
90
+
91
+ content = if node.children[2]
92
+ indent(
93
+ hardline,
94
+ visit(node.children[2]),
95
+ )
96
+ end
97
+
98
+ concat(
99
+ "class ",
100
+ visit(node.children[0]),
101
+ inheritance,
102
+ content,
103
+ hardline,
104
+ "end"
105
+ )
106
+ when :if
107
+ body = if node.body_node
108
+ concat(
109
+ indent(
110
+ hardline,
111
+ visit(node.body_node),
112
+ ),
113
+ )
114
+ end
115
+
116
+ elsifs = if node.has_elsif?
117
+ [hardline] + node.elsif_branches.map do |elsif_branch|
118
+ concat(
119
+ "elsif ",
120
+ visit(elsif_branch.conditions),
121
+ indent(
122
+ hardline,
123
+ visit(elsif_branch.body_node)
124
+ ),
125
+ hardline,
126
+ )
127
+ end
128
+ end
129
+
130
+ else_content = if node.else_branch
131
+ starting_newline = if !node.has_elsif?
132
+ hardline
133
+ end
134
+ concat(
135
+ starting_newline,
136
+ "else",
137
+ indent(
138
+ hardline,
139
+ visit(node.else_branch)
140
+ ),
141
+ hardline,
142
+ )
143
+ else
144
+ hardline
145
+ end
146
+
147
+ concat(
148
+ node.unless_node? ? "unless" : "if",
149
+ " ",
150
+ visit(node.conditions),
151
+ body,
152
+ *elsifs,
153
+ else_content,
154
+ "end"
155
+ )
156
+ when :case
157
+ arguments = if node.children[0]
158
+ concat(
159
+ " ",
160
+ visit(node.children[0]),
161
+ )
162
+ end
163
+
164
+ cases = node.children[1..-1].map do |child|
165
+ if child && child.type != :when
166
+ concat(
167
+ hardline,
168
+ "else",
169
+ indent(
170
+ hardline,
171
+ visit(child)
172
+ ),
173
+ )
174
+ elsif child
175
+ visit child
176
+ end
177
+ end
178
+
179
+ concat(
180
+ "case",
181
+ arguments,
182
+ concat(*cases),
183
+ hardline,
184
+ "end"
185
+ )
186
+ when :when
187
+ arguments = node.children[0..-2].compact
188
+ body = if node.children.last
189
+ indent(
190
+ hardline,
191
+ visit(node.children.last)
192
+ )
193
+ end
194
+
195
+ arguments = if arguments.size > 0
196
+ join(
197
+ separator: ",",
198
+ parts: arguments.map do |arg|
199
+ concat(softline, visit(arg))
200
+ end
201
+ )
202
+ end
203
+
204
+ concat(
205
+ hardline,
206
+ group(
207
+ "when",
208
+ if_break(with_break: "", without_break: " "),
209
+ indent(
210
+ arguments,
211
+ ),
212
+ ),
213
+ body
214
+ )
215
+ when :const
216
+ output = []
217
+
218
+ child = node.children[0]
219
+ while child&.type == :const || child&.type == :cbase
220
+ output << child.children[1]
221
+ child = child.children[0]
222
+ end
223
+
224
+ (output.reverse + [node.children[1]]).join("::")
225
+ when :or
226
+ builder = concat(
227
+ visit(node.children[0]),
228
+ " ||",
229
+ if_break(with_break: "", without_break: " "),
230
+ softline,
231
+ visit(node.children[1]),
232
+ )
233
+
234
+ if node.parent&.type == :and || node.parent&.type == :or
235
+ builder
236
+ else
237
+ group(
238
+ indent(
239
+ builder
240
+ )
241
+ )
242
+ end
243
+ when :and
244
+ builder = concat(
245
+ visit(node.children[0]),
246
+ " &&",
247
+ if_break(with_break: "", without_break: " "),
248
+ softline,
249
+ visit(node.children[1]),
250
+ )
251
+
252
+ if node.parent&.type == :and || node.parent&.type == :or
253
+ builder
254
+ else
255
+ group(
256
+ indent(
257
+ builder
258
+ )
259
+ )
260
+ end
261
+ when :int, :float
262
+ node.children[0].to_s
263
+ when :return
264
+ if node.children[0]
265
+ group(
266
+ "return",
267
+ if_break(without_break: " ", with_break: ""),
268
+ indent(
269
+ softline,
270
+ visit(node.children[0]),
271
+ only_when_break: true
272
+ )
273
+ )
274
+ else
275
+ "return"
276
+ end
277
+ when :block
278
+ args = if node.children[1]&.children&.length > 0
279
+ concat(
280
+ " |",
281
+ visit(node.children[1]),
282
+ "|",
283
+ )
284
+ end
285
+
286
+ concat(
287
+ visit(node.children[0]),
288
+ " do",
289
+ args,
290
+ indent(
291
+ hardline,
292
+ visit(node.children[2]),
293
+ ),
294
+ hardline,
295
+ "end",
296
+ )
297
+ when :begin
298
+ needs_parens = (node.parent&.type == :if && node.parent.children[0] == node) ||
299
+ node.parent&.type == :or ||
300
+ node.parent&.type == :and ||
301
+ node.parent&.type == :send
302
+
303
+ if needs_parens
304
+ concat(
305
+ "(",
306
+ *visit_each(node.children), # TODO Split or softline?
307
+ ")"
308
+ )
309
+ else
310
+ children = []
311
+ node.children.each_with_index do |child, index|
312
+ children << visit(child)
313
+
314
+ next_child = node.children[index + 1]
315
+ excluded_types = [:class, :module, :sclass, :def, :defs]
316
+
317
+ if (excluded_types.include?(child.type) && node.children.last != child) ||
318
+ (next_child&.type != child.type && node.children.last != child)
319
+ children << hardline(count: 2)
320
+ elsif node.children.last != child
321
+ children << hardline
322
+ end
323
+ end
324
+
325
+ concat(*children)
326
+ end
327
+ when :defs
328
+ args_blocks = visit node.children[2] if node.children[2]
329
+
330
+ body = if node.children[3]
331
+ concat(
332
+ indent(
333
+ hardline,
334
+ visit(node.children[3]),
335
+ ),
336
+ hardline,
337
+ )
338
+ else
339
+ hardline
340
+ end
341
+
342
+ concat(
343
+ "def ",
344
+ visit(node.children[0]),
345
+ ".",
346
+ node.children[1],
347
+ args_blocks,
348
+ body,
349
+ "end"
350
+ )
351
+ when :def
352
+ args_blocks = visit node.args if node.args
353
+ body_blocks = visit node.body if node.body
354
+
355
+ body = if node.body
356
+ concat(
357
+ indent(
358
+ hardline,
359
+ body_blocks,
360
+ ),
361
+ hardline,
362
+ )
363
+ else
364
+ hardline
365
+ end
366
+
367
+ concat(
368
+ "def ",
369
+ # TODO possible break
370
+ node.name,
371
+ args_blocks,
372
+ body,
373
+ "end",
374
+ )
375
+ when :args
376
+ if node&.parent&.type == :block
377
+ group(
378
+ join(
379
+ separator: ",",
380
+ parts: node.children.map(&method(:visit)),
381
+ ),
382
+ )
383
+ elsif node.children.length > 0
384
+ group(
385
+ "(",
386
+ softline,
387
+ join(
388
+ separator: ",",
389
+ parts: node.children.map(&method(:visit)),
390
+ ),
391
+ softline,
392
+ ")"
393
+ )
394
+ else
395
+ nil
396
+ end
397
+ when :arg
398
+ node.children[0]
399
+ when :masgn
400
+ concat(
401
+ visit(node.children[0]),
402
+ " = ",
403
+ visit(node.children[-1])
404
+ )
405
+ when :mlhs
406
+ if node.parent&.type == :mlhs
407
+ concat(
408
+ "(",
409
+ join(separator: ",", parts: visit_each(node.children)),
410
+ ")"
411
+ )
412
+ else
413
+ join(separator: ",", parts: visit_each(node.children))
414
+ end
415
+ when :casgn
416
+ if !node.children[0].nil?
417
+ puts "FATAL: FIX CASGN FIRST ARGUMENT"
418
+ exit 1
419
+ end
420
+
421
+ # TODO test softline grouping on right side of `=`
422
+ group(
423
+ node.children[1],
424
+ " = ",
425
+ # if_break(with_break: "", without_break: " "),
426
+ # softline,
427
+ visit(node.children[2]),
428
+ )
429
+ when :lvasgn, :cvasgn, :ivasgn
430
+ right_blocks = visit node.children[1] if node.children[1]
431
+
432
+ if right_blocks
433
+ concat(
434
+ node.children[0].to_s,
435
+ " = ",
436
+ # TODO line break for long lines
437
+ right_blocks,
438
+ )
439
+ else
440
+ concat(
441
+ node.children[0].to_s,
442
+ )
443
+ end
444
+ when :send
445
+ if node.called_on_heredoc?
446
+ visit node.target
447
+ elsif node.array_assignment?
448
+ equals = if !node.left_hand_mass_assignment?
449
+ " = "
450
+ end
451
+
452
+ body = if node.children[3]
453
+ visit(node.children[3])
454
+ end
455
+
456
+ concat(
457
+ visit(node.target),
458
+ "[",
459
+ visit(node.children[2]),
460
+ "]", # TODO line split
461
+ equals,
462
+ body,
463
+ )
464
+ elsif node.array_access?
465
+ concat(
466
+ visit(node.target),
467
+ "[",
468
+ visit(node.children[2]),
469
+ "]"
470
+ )
471
+ elsif node.negate?
472
+ concat(
473
+ "!",
474
+ visit(node.target),
475
+ )
476
+ elsif node.negative?
477
+ concat(
478
+ "-",
479
+ visit(node.target),
480
+ )
481
+ elsif node.self_target?
482
+ body = visit(node.target) if node.target
483
+
484
+ concat(
485
+ node.method.to_s[0..-2],
486
+ body,
487
+ )
488
+ elsif node.infix?
489
+ body = visit(node.children[2]) if node.children[2]
490
+
491
+ group(
492
+ concat(
493
+ visit(node.target),
494
+ " ",
495
+ node.method,
496
+ " ",
497
+ body,
498
+ )
499
+ )
500
+ else
501
+ arguments = if node.arguments.length > 0
502
+ concat(
503
+ "(",
504
+ indent(
505
+ join(separator: ",", parts: node.arguments.map { |child| concat(softline, visit(child)) }),
506
+ only_when_break: true,
507
+ ),
508
+ softline,
509
+ ")",
510
+ )
511
+ end
512
+
513
+ method = if node.left_hand_mass_assignment?
514
+ node.method.to_s[0...-1]
515
+ else
516
+ node.method
517
+ end
518
+
519
+ if node.target
520
+ concat(
521
+ visit(node.target),
522
+ ".",
523
+ method,
524
+ group(
525
+ arguments,
526
+ )
527
+ )
528
+ else
529
+ concat(
530
+ method,
531
+ group(
532
+ arguments,
533
+ )
534
+ )
535
+ end
536
+ end
537
+ when :hash
538
+ if node.children.length > 0
539
+ group(
540
+ "{",
541
+ if_break(without_break: " ", with_break: ""),
542
+ indent(
543
+ join(separator: ",", parts: node.children.map { |child| concat(softline, visit(child)) }),
544
+ if_break(without_break: " ", with_break: ""),
545
+ ),
546
+ softline,
547
+ "}"
548
+ )
549
+ else
550
+ "{}"
551
+ end
552
+ when :pair
553
+ if node.children[0].type == :sym
554
+ concat(
555
+ visit(node.children[0]),
556
+ visit(node.children[1])
557
+ )
558
+ else
559
+ concat(
560
+ visit(node.children[0]),
561
+ " => ",
562
+ visit(node.children[1])
563
+ )
564
+ end
565
+ when :defined?
566
+ concat(
567
+ "defined?(",
568
+ visit(node.children[0]),
569
+ ")"
570
+ )
571
+ when :and_asgn, :or_asgn
572
+ operator = if node.type == :and_asgn
573
+ "&&="
574
+ elsif node.type == :or_asgn
575
+ "||="
576
+ end
577
+
578
+ group(
579
+ visit(node.children[0]),
580
+ " ",
581
+ operator,
582
+ if_break(with_break: "", without_break: " "),
583
+ softline,
584
+ visit(node.children[1])
585
+ )
586
+ when :array
587
+ if node.parent&.type == :resbody
588
+ join(separator: ",", parts: visit_each(node.children))
589
+ elsif node.children[0]&.type == :splat
590
+ visit node.children[0]
591
+ else
592
+ array_nodes = node.children.each_with_index.map do |child, index|
593
+ concat(softline, visit(child))
594
+ end
595
+
596
+ group(
597
+ "[",
598
+ indent(
599
+ join(separator: ",", parts: array_nodes),
600
+ ),
601
+ softline,
602
+ "]"
603
+ )
604
+ end
605
+ when :regopt
606
+ node.children.map(&:to_s).join("")
607
+ when :regexp
608
+ content = node.children[0...-1].map do |child|
609
+ if child.type == :str
610
+ child.children[0].to_s
611
+ else
612
+ visit child
613
+ end
614
+ end
615
+
616
+ options = if node.children[-1]
617
+ visit node.children[-1]
618
+ end
619
+
620
+ if node.percent?
621
+ concat(
622
+ "%",
623
+ node.percent_type,
624
+ node.start_delimiter,
625
+ *content,
626
+ node.end_delimiter,
627
+ options,
628
+ )
629
+ else
630
+ concat(
631
+ "/",
632
+ *content,
633
+ "/",
634
+ options,
635
+ )
636
+ end
637
+ when :str, :dstr
638
+ if node.heredoc?
639
+ method_calls = if node.parent&.type == :send
640
+ method_calls = []
641
+ parent = node.parent
642
+
643
+ while parent && parent.type == :send && parent.called_on_heredoc?
644
+ arguments = if parent.arguments.length > 0
645
+ concat(
646
+ "(",
647
+ join(separator: ",", parts: parent.arguments.map { |child| concat(softline, visit(child)) }),
648
+ ")",
649
+ )
650
+ end
651
+
652
+ method_calls.push(
653
+ concat(
654
+ ".",
655
+ parent.children[1].to_s,
656
+ arguments,
657
+ )
658
+ )
659
+ parent = parent.parent
660
+ end
661
+
662
+ concat(*method_calls)
663
+ end
664
+
665
+ concat(
666
+ "<<",
667
+ node.heredoc_type,
668
+ node.heredoc_identifier,
669
+ method_calls,
670
+ hardline(skip_indent: true),
671
+ node.heredoc_body,
672
+ node.heredoc_identifier
673
+ )
674
+ elsif node.percent_string?
675
+ body = node.children.map do |child|
676
+ if child.is_a?(String)
677
+ child
678
+ elsif child.type == :str
679
+ child.children[0]
680
+ else
681
+ visit child
682
+ end
683
+ end
684
+
685
+ concat(
686
+ "%",
687
+ node.percent_character,
688
+ node.start_delimiter,
689
+ *body,
690
+ node.closing_delimiter,
691
+ )
692
+ else
693
+ concat(
694
+ "\"",
695
+ node.format,
696
+ "\"",
697
+ )
698
+ end
699
+ when :alias
700
+ concat(
701
+ "alias ",
702
+ visit(node.children[0]),
703
+ " ",
704
+ visit(node.children[1]),
705
+ )
706
+ when :dsym
707
+ body = node.children.map do |child|
708
+ if child.string?
709
+ child.children[0]
710
+ else
711
+ concat(
712
+ "\#{",
713
+ visit(child),
714
+ "}",
715
+ )
716
+ end
717
+ end
718
+
719
+ concat(
720
+ ":\"",
721
+ *body,
722
+ "\"",
723
+ )
724
+ when :sym
725
+ content = node.children[0].to_s
726
+
727
+ # TODO handle already quoted symbols
728
+ if !VALID_SYMBOLS.include?(content) && !content.match?(/\A[a-zA-Z_]{1}[a-zA-Z0-9_!?=]*\z/)
729
+ concat(
730
+ ":",
731
+ "'",
732
+ content,
733
+ "'",
734
+ )
735
+ else
736
+ if node.parent&.type == :pair && node.parent.children[0] == node
737
+ concat(
738
+ content,
739
+ ": ",
740
+ )
741
+ else
742
+ concat(
743
+ ":",
744
+ content,
745
+ )
746
+ end
747
+ end
748
+ when :kwsplat
749
+ concat(
750
+ "**",
751
+ visit(node.children[0])
752
+ )
753
+ when :splat
754
+ concat(
755
+ "*",
756
+ visit(node.children[0])
757
+ )
758
+ when :undef
759
+ concat(
760
+ "undef",
761
+ " ",
762
+ join(separator: ",", parts: visit_each(node.children))
763
+ )
764
+ when :forward_args
765
+ "(...)"
766
+ when :forwarded_args
767
+ "..."
768
+ when :optarg
769
+ concat(
770
+ node.children[0],
771
+ " = ",
772
+ visit(node.children[1]),
773
+ )
774
+ when :restarg
775
+ concat(
776
+ "*",
777
+ node.children[0],
778
+ )
779
+ when :kwarg
780
+ concat(
781
+ node.children[0],
782
+ ":",
783
+ )
784
+ when :kwoptarg
785
+ concat(
786
+ node.children[0],
787
+ ": ",
788
+ visit(node.children[1]),
789
+ )
790
+ when :kwrestarg
791
+ if node.children[0]
792
+ "**" + node.children[0].to_s
793
+ else
794
+ "**"
795
+ end
796
+ when :kwnilarg
797
+ "**nil"
798
+ when :lvar, :cvar, :ivar, :gvar
799
+ node.children[0].to_s
800
+ when :true, :false, :nil, :self, :break
801
+ node.type.to_s
802
+ when :cbase
803
+ "::"
804
+ when :kwbegin
805
+ concat(
806
+ "begin",
807
+ indent(
808
+ hardline,
809
+ visit(node.children[0])
810
+ ),
811
+ hardline,
812
+ "end"
813
+ )
814
+ when :ensure
815
+ concat(
816
+ indent(
817
+ visit(node.children[0]),
818
+ ),
819
+ dedent(
820
+ hardline,
821
+ "ensure",
822
+ ),
823
+ hardline,
824
+ visit(node.children[1]),
825
+ )
826
+ when :rescue
827
+ concat(
828
+ visit(node.children[0]),
829
+ dedent(
830
+ hardline,
831
+ visit(node.children[1])
832
+ )
833
+ )
834
+ when :resbody
835
+ args = node.children[0]
836
+ assignment = node.children[1]
837
+ body = node.children[2]
838
+
839
+ arguments = if args
840
+ concat(
841
+ " ",
842
+ visit(args),
843
+ )
844
+ end
845
+
846
+ argument_assignment = if assignment
847
+ concat(
848
+ " => ",
849
+ visit(assignment)
850
+ )
851
+ end
852
+
853
+ body = if body
854
+ visit(body)
855
+ end
856
+
857
+ concat(
858
+ "rescue",
859
+ arguments,
860
+ argument_assignment,
861
+ indent(
862
+ hardline,
863
+ body,
864
+ )
865
+ )
866
+ when :while
867
+ args = if node.children[0]
868
+ concat(
869
+ " ",
870
+ visit(node.children[0])
871
+ )
872
+ end
873
+
874
+ body = if node.children[1]
875
+ indent(
876
+ hardline,
877
+ visit(node.children[1]),
878
+ )
879
+ end
880
+
881
+ concat(
882
+ "while",
883
+ args,
884
+ body,
885
+ hardline,
886
+ "end",
887
+ )
888
+ when :csend
889
+ concat(
890
+ visit(node.children[0]),
891
+ "&.", # TODO softbreak
892
+ node.children[1]
893
+ )
894
+ when :erange
895
+ right_side = visit(node.children[1]) if node.children[1]
896
+
897
+ concat(
898
+ visit(node.children[0]),
899
+ "...",
900
+ right_side
901
+ )
902
+ when :irange
903
+ right_side = visit(node.children[1]) if node.children[1]
904
+
905
+ concat(
906
+ visit(node.children[0]),
907
+ "..",
908
+ right_side
909
+ )
910
+ when :nth_ref
911
+ concat(
912
+ "$",
913
+ node.children[0].to_s,
914
+ )
915
+ when :super
916
+ concat(
917
+ "super(",
918
+ *visit_each(node.children),
919
+ ")"
920
+ )
921
+ when :next
922
+ body = if node.children[0]
923
+ concat(" ", visit(node.children[0]))
924
+ end
925
+
926
+ concat(
927
+ "next",
928
+ body
929
+ )
930
+ when :zsuper
931
+ "super"
932
+ when :block_pass
933
+ concat("&", visit(node.children[0]))
934
+ else
935
+ raise "Unexpected node type: #{node.type}"
936
+ end
937
+ end
938
+
939
+ private
940
+
941
+ def visit_each(node)
942
+ node.map do |child|
943
+ visit(child)
944
+ end
945
+ end
946
+ end
947
+ end