prettyrb 0.1.0 → 0.5.0

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