prettyrb 0.4.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,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
@@ -1,7 +1,23 @@
1
1
  module Prettyrb
2
2
  module Nodes
3
3
  module StringHelper
4
- HEREDOC_TYPE_REGEX = /<<(.)?/
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
5
21
 
6
22
  def heredoc_identifier
7
23
  loc.heredoc_end.source.strip
@@ -1,3 +1,3 @@
1
1
  module Prettyrb
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -1,5 +1,7 @@
1
1
  module Prettyrb
2
2
  class Visitor
3
+ include Document::DSL
4
+
3
5
  MAX_LENGTH = 100
4
6
  SINGLE_LINE = "single_line"
5
7
  MULTI_LINE = "multi_line"
@@ -33,730 +35,912 @@ module Prettyrb
33
35
  ]
34
36
  VALID_SYMBOLS = FNAMES + ["!", "!="]
35
37
 
36
- attr_reader :output
37
-
38
- def initialize
39
- @indent_level = 0
40
- @output = ""
41
-
42
- @newline = true
43
- @multiline_conditional_level = 0
44
- @previous_node = nil
45
- @current_line = ''
46
- end
47
-
48
- def indents
49
- ' ' * @indent_level
50
- end
51
-
52
- def dedent(&block)
53
- old_indent_level = @indent_level
54
-
55
- @indent_level = [0, @indent_level - 1].max
56
- yield
57
- @indent_level = old_indent_level
58
- end
59
-
60
- def indent(&block)
61
- old_indent_level = @indent_level
62
- old_previous_node = @previous_node
63
-
64
- @previous_node = nil
65
- @indent_level += 1
66
- value = yield
67
- @indent_level = old_indent_level
68
- @previous_node = old_previous_node
69
- value
70
- end
71
-
72
- def multiline_conditional_level
73
- @multiline_conditional_level
74
- end
75
-
76
- def in_multiline_conditional(&block)
77
- old_multiline_condtiional_level = @multiline_conditional_level
78
- @multiline_conditional_level += 1
79
- @multiline_condtiional_level = 0
80
- yield
81
- @multiline_conditional_level = old_multiline_condtiional_level
82
- end
83
-
84
- def capture(&block)
85
- old_newline = @newline
86
- old_output = @output
87
- old_current_line = @current_line
88
-
89
- @current_line = ""
38
+ def initialize(root_node)
90
39
  @output = ""
91
-
92
- yield
93
-
94
- @output.tap do |output|
95
- @output = old_output
96
- @newline = old_newline
97
- @current_line = old_current_line
98
- end
40
+ @builder = visit(root_node)
99
41
  end
100
42
 
101
- def newline
102
- @output << "\n"
103
- @newline = true
104
- @current_line = ''
105
- end
106
-
107
- def write(input, skip_indent: false)
108
- if @newline
109
- @output << indents unless skip_indent
110
- @current_line << indents unless skip_indent
111
- end
112
- @newline = false
113
- @output << input
114
- @current_line << input
43
+ def output
44
+ Writer.new(@builder).to_s
115
45
  end
116
46
 
117
47
  def visit(node)
118
48
  case node.type
119
49
  when :module
120
- write "module "
121
- visit node.children[0]
122
- newline
123
-
124
- indent do
125
- visit node.children[1]
126
- end
127
-
128
- write "end"
129
- newline
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
+ )
130
83
  when :class
131
- newline unless @previous_node.nil?
132
- write "class "
133
- visit node.children[0]
134
- newline
135
- # TODO handle children[1] which is inheritance
136
-
137
- indent do
138
- visit node.children[2] if node.children[2]
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
139
128
  end
140
129
 
141
- newline unless @output.end_with?("\n")
142
- write "end"
143
- newline
144
- when :const
145
- visit node.children[0] if node.children[0]
146
- write node.children[1].to_s
147
- when :casgn
148
- write node.children[1].to_s
149
- write " = "
150
- visit node.children[2]
151
- when :block
152
- newline unless @previous_node.nil?
153
- visit node.children[0]
154
- write " do"
155
-
156
- if node.children[1].children.length > 0
157
- write " |"
158
- visit node.children[1]
159
- write "|"
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
+ )
160
162
  end
161
163
 
162
- newline
163
-
164
- indent do
165
- visit node.children[2]
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
166
177
  end
167
178
 
168
- newline
169
-
170
- write "end"
171
- when :send
172
- newline if node.parent&.type == :begin && @previous_node && @previous_node&.type != :send
173
- if node.children[0] == nil
174
- write node.children[1].to_s
175
-
176
- # TODO possible > MAX via `capture`
177
- arguments = node.children[2..-1]
178
- if arguments.length > 0
179
- write "("
180
- indent do
181
- if splittable_separated_map(node, arguments) == MULTI_LINE
182
- newline
183
- end
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))
184
200
  end
185
- write ")"
186
- end
187
-
188
- newline if @previous_node&.type == :class
189
- elsif node.children[1] == :[]
190
- visit node.children[0]
191
- write "["
192
- visit node.children[2]
193
- write "]"
194
- elsif node.children[1] == :!
195
- write "!"
196
- visit node.children[0]
197
- elsif node.children[1] == :-@ && node.children[2].nil?
198
- write "-"
199
- visit node.children[0]
200
- elsif !node.children[1].to_s.match?(/[a-zA-Z]/)
201
- visit node.children[0]
202
- write " "
203
- write node.children[1].to_s
204
- write " "
205
- visit node.children[2]
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
206
236
  else
207
- visit node.children[0]
208
- write "."
209
- write node.children[1].to_s
210
-
211
- # TODO possible > MAX via `capture`
212
- arguments = node.children[2..-1]
213
- if arguments.length > 0
214
- write "("
215
- arguments.each_with_index do |child_node, index|
216
- visit child_node
217
- write ", " unless index == arguments.length - 1
218
- end
219
- write ")"
220
- end
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
+ )
221
260
  end
222
- when :if
223
- newline if @previous_node && node.parent&.type != :if
224
- conditions = node.conditions_node
225
-
226
- write node.if_type
227
-
228
- conditions = capture do
229
- visit node.children[0]
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"
230
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)
231
313
 
232
- indent do
233
- if !conditions.start_with?("\n")
234
- write(" ")
235
- write conditions
236
- else
237
- visit node.conditions_node
314
+ next_child = node.children[index + 1]
315
+ excluded_types = [:class, :module, :sclass, :def, :defs]
238
316
 
239
- dedent do
240
- newline
241
- write "then"
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
242
322
  end
243
323
  end
244
324
 
245
- newline
246
- visit node.body_node
247
- end
248
-
249
- newline
250
-
251
- if node.else_body_node
252
- if node.has_elsif?
253
- visit node.else_body_node
254
- else
255
- write "else"
256
- newline
257
-
258
- indent do
259
- visit node.else_body_node
260
- end
261
-
262
- newline
263
- end
325
+ concat(*children)
264
326
  end
327
+ when :defs
328
+ args_blocks = visit node.children[2] if node.children[2]
265
329
 
266
- if !node.is_elsif?
267
- write "end"
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
268
365
  end
269
- when :true
270
- write "true"
271
- when :false
272
- write "false"
273
- when :nil
274
- write "nil"
275
- when :int, :float
276
- write node.children[0].to_s
277
- when :next
278
- write "next"
279
366
 
280
- if node.children[0]
281
- write " "
282
- visit node.children[0]
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
283
396
  end
284
- when :array
285
- if node.children[0]&.type == :splat
286
- visit node.children[0]
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
+ )
287
412
  else
288
- write "[" unless node.parent&.type == :resbody
289
- if node.children.length > 0
290
- indent do
291
- result = splittable_separated_map(node, node.children)
292
- newline if result == MULTI_LINE
293
- end
294
- end
295
- write "]" unless node.parent&.type == :resbody
413
+ join(separator: ",", parts: visit_each(node.children))
296
414
  end
297
- when :str
298
- if node.heredoc?
299
- if node&.parent&.type == :send
300
- write "("
301
- end
302
- write "<<"
303
- write node.heredoc_type if node.heredoc_type
304
- write node.heredoc_identifier
305
- newline
306
- write node.heredoc_body, skip_indent: true
307
- @newline = true
308
- write node.heredoc_identifier
309
-
310
- if node&.parent&.type == :send
311
- newline
312
- write ")"
313
- 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
+ )
314
439
  else
315
- write '"'
316
- write node.format
317
- write '"'
440
+ concat(
441
+ node.children[0].to_s,
442
+ )
318
443
  end
319
- when :dstr
320
- if node.heredoc?
321
- if node&.parent&.type == :send
322
- write "("
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
+ " = "
323
450
  end
324
- write "<<"
325
- write node.heredoc_type if node.heredoc_type
326
- write node.heredoc_identifier
327
- newline
328
- write node.heredoc_body, skip_indent: true
329
- @newline = true
330
- write node.heredoc_identifier
331
-
332
- if node&.parent&.type == :send
333
- newline
334
- write ")"
451
+
452
+ body = if node.children[3]
453
+ visit(node.children[3])
335
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
+ )
336
500
  else
337
- write "\""
338
- node.children.map do |child|
339
- if child.type == :str
340
- write child.format
341
- else
342
- write '#{'
343
- visit child
344
- write '}'
345
- end
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
+ )
346
511
  end
347
- write "\""
348
- end
349
- when :begin
350
- if @previous_node&.type == :or || @previous_node&.type == :and
351
- write "("
352
- @previous_node = nil
353
- node.children.map do |child|
354
- visit child
355
- @previous_node = child
512
+
513
+ method = if node.left_hand_mass_assignment?
514
+ node.method.to_s[0...-1]
515
+ else
516
+ node.method
356
517
  end
357
- @previous_node = nil
358
- write ")"
359
- else
360
- @previous_node = nil
361
- node.children.each_with_index do |child, index|
362
- visit child
363
- newline unless index == node.children.length - 1
364
- @previous_node = child
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
+ )
365
535
  end
366
- @previous_node = nil
367
536
  end
368
- when :or, :and
369
- write "(" if node.parent&.type == :begin
370
- possible_output = capture do
371
- visit node.children[0]
372
- if node.type == :or
373
- write " || "
374
- elsif node.type == :and
375
- write " && "
376
- end
377
- visit node.children[1]
378
- end
379
- if @multiline_conditional_level > 0 # TODO track and check currently level
380
- write_multiline_conditional(node)
381
- elsif possible_output.length > MAX_LENGTH
382
- in_multiline_conditional do
383
- newline
384
- write_multiline_conditional(node)
385
- 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
+ )
386
549
  else
387
- write possible_output
388
- end
389
- write ")" if node.parent&.type == :begin
390
- when :def, :defs
391
- newline unless @previous_node&.type.nil?
392
- if node.type == :defs
393
- write "def self."
394
- method_name_node = node.children[1]
395
- arguments_node = node.children[2]
396
- body_node = node.children[3]
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
+ )
397
558
  else
398
- write "def "
399
- method_name_node = node.children[0]
400
- arguments_node = node.children[1]
401
- body_node = node.children[2]
559
+ concat(
560
+ visit(node.children[0]),
561
+ " => ",
562
+ visit(node.children[1])
563
+ )
402
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
403
595
 
404
- write method_name_node.to_s
405
-
406
- if arguments_node.children.length > 0 || arguments_node.type != :args
407
- write "("
408
- visit arguments_node
409
- write ")"
596
+ group(
597
+ "[",
598
+ indent(
599
+ join(separator: ",", parts: array_nodes),
600
+ ),
601
+ softline,
602
+ "]"
603
+ )
410
604
  end
411
- newline
412
-
413
- if body_node
414
- indent do
415
- visit body_node
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
416
613
  end
417
-
418
- newline
419
614
  end
420
615
 
421
- write "end"
422
- when :args
423
- node.children.each_with_index do |child, index|
424
- visit child
425
- write ", " unless index == node.children.length - 1
616
+ options = if node.children[-1]
617
+ visit node.children[-1]
426
618
  end
427
- when :arg
428
- write node.children[0].to_s
429
- when :lvar, :gvar
430
- write node.children[0].to_s
431
- when :self
432
- "self"
433
- when :sym
434
- content = node.children[0].to_s
435
619
 
436
- # TODO handle already quoted symbols
437
- if !VALID_SYMBOLS.include?(content) && !content.match?(/\A[a-zA-Z_]{1}[a-zA-Z0-9_!?]*\z/)
438
- content = "'#{content}'"
439
- write ":"
440
- write content
620
+ if node.percent?
621
+ concat(
622
+ "%",
623
+ node.percent_type,
624
+ node.start_delimiter,
625
+ *content,
626
+ node.end_delimiter,
627
+ options,
628
+ )
441
629
  else
442
- if node.parent&.type == :pair
443
- write content
444
- write ": "
445
- else
446
- write ":"
447
- write content
448
- end
449
- end
450
- when :return
451
- write "return"
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
452
651
 
453
- if node.children[0]
454
- possible_output = capture do
455
- visit node.children[0]
456
- end
652
+ method_calls.push(
653
+ concat(
654
+ ".",
655
+ parent.children[1].to_s,
656
+ arguments,
657
+ )
658
+ )
659
+ parent = parent.parent
660
+ end
457
661
 
458
- if !possible_output.start_with?("\n")
459
- write " "
662
+ concat(*method_calls)
460
663
  end
461
- end
462
- when :case
463
- write "case "
464
- visit node.children[0]
465
- newline
466
- node.children[1..-1].each do |child|
467
- if child && child.type != :when
468
- write "else"
469
- newline
470
664
 
471
- indent do
472
- visit child
473
- end
474
- else
475
- if child
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
476
681
  visit child
477
- newline
478
682
  end
479
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
+ )
480
698
  end
481
- write "end"
482
- when :regexp
483
- write '/'
484
- node.children[0...-1].map do |child_node|
485
- if child_node.type == :str
486
- write child_node.children[0].to_s
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]
487
710
  else
488
- visit child_node
711
+ concat(
712
+ "\#{",
713
+ visit(child),
714
+ "}",
715
+ )
489
716
  end
490
717
  end
491
- write '/'
492
- visit node.children[-1]
493
- when :regopt
494
- node.children.map { |child| child.to_s }.join('')
495
- when :when
496
- write "when"
497
-
498
- indent do
499
- splittable_separated_map(node, node.children[0..-2], skip_last_multiline_separator: true, write_space_if_single_line: true)
500
- end
501
718
 
502
- newline
503
- indent do
504
- visit node.children[-1]
505
- end
506
- when :or_asgn, :and_asgn
507
- newline if @previous_node && ![:ivasgn, :or_asgn, :lvasgn, :op_asgn].include?(@previous_node.type)
508
- visit node.children[0]
509
- if node.type == :or_asgn
510
- write " ||= " # TODO handle long lines here too
511
- elsif node.type == :and_asgn
512
- write " &&= " # TODO handle long lines here too
513
- end
514
- visit node.children[1]
515
- when :ivasgn
516
- newline if @previous_node && ![:ivasgn, :or_asgn, :lvasgn, :op_asgn].include?(@previous_node.type)
517
- write node.children[0].to_s
719
+ concat(
720
+ ":\"",
721
+ *body,
722
+ "\"",
723
+ )
724
+ when :sym
725
+ content = node.children[0].to_s
518
726
 
519
- if node.children[1]
520
- write " = "
521
- visit node.children[1]
522
- end
523
- when :csend
524
- visit node.children[0]
525
- write "&."
526
- write node.children[1].to_s
527
- when :ivar
528
- write node.children[0].to_s
529
- when :blockarg
530
- write '&'
531
- write node.children[0].to_s
532
- when :yield
533
- newline unless @previous_node.nil? || [:op_asgn, :lvasgn, :or_asgn, :and_asgn].include?(@previous_node.type)
534
- write "yield"
535
- when :op_asgn
536
- newline if @previous_node && ![:ivasgn, :or_asgn, :lvasgn, :op_asgn].include?(@previous_node.type)
537
- visit node.children[0]
538
- write " "
539
- write node.children[1].to_s
540
- write "="
541
- write " "
542
- visit node.children[2]
543
- when :lvasgn
544
- newline if @previous_node && ![:ivasgn, :or_asgn, :lvasgn, :op_asgn].include?(@previous_node.type)
545
- write node.children[0].to_s
546
- if node.children[1]
547
- write " = "
548
- visit node.children[1]
549
- end
550
- when :irange
551
- visit node.children[0] unless node.children[0].nil?
552
- write ".."
553
- visit node.children[1] unless node.children[1].nil?
554
- when :erange
555
- visit node.children[0] unless node.children[0].nil?
556
- write "..."
557
- visit node.children[1] unless node.children[1].nil?
558
- when :hash
559
- if node.children.length == 0
560
- write "{}"
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
+ )
561
735
  else
562
- write "{"
563
-
564
- result = indent do
565
- splittable_separated_map(node, node.children, write_space_if_single_line: true)
566
- end
567
-
568
- if result == MULTI_LINE
569
- newline
570
- write "}"
736
+ if node.parent&.type == :pair && node.parent.children[0] == node
737
+ concat(
738
+ content,
739
+ ": ",
740
+ )
571
741
  else
572
- write " }"
742
+ concat(
743
+ ":",
744
+ content,
745
+ )
573
746
  end
574
747
  end
575
- when :pair
576
- visit node.children[0]
577
- if node.children[0].type != :sym
578
- write " => "
579
- end
580
- visit node.children[1]
748
+ when :kwsplat
749
+ concat(
750
+ "**",
751
+ visit(node.children[0])
752
+ )
581
753
  when :splat
582
- write "*"
583
- visit node.children[0]
584
- when :defined?
585
- write "defined?("
586
- visit node.children[0]
587
- write ")"
588
- when :complex,
589
- :dsym,
590
- :xstr,
591
- :'nth-ref',
592
- :'back-ref',
593
- :gvasgn,
594
- :procarg0,
595
- :shadowarg
596
- raise "implement me, #{node.inspect}"
597
- when :sclass
598
- write "class << "
599
- visit node.children[0]
600
- newline
601
-
602
- indent do
603
- visit node.children[1] if node.children[1]
604
- end
605
-
606
- newline if node.children[1]
607
- write "end"
754
+ concat(
755
+ "*",
756
+ visit(node.children[0])
757
+ )
608
758
  when :undef
609
- write "undef "
610
- node.children.each_with_index do |child_node, index|
611
- visit child_node
612
- write ", " unless index == node.children.length - 1
613
- end
614
- when :alias
615
- write 'alias '
616
- visit node.children[0]
617
- write ' '
618
- visit node.children[1]
619
- when :restarg
620
- write "*"
621
- write node.children[0].to_s
759
+ concat(
760
+ "undef",
761
+ " ",
762
+ join(separator: ",", parts: visit_each(node.children))
763
+ )
764
+ when :forward_args
765
+ "(...)"
766
+ when :forwarded_args
767
+ "..."
622
768
  when :optarg
623
- write node.children[0].to_s
624
- write " = "
625
- visit node.children[1]
626
- when :kwsplat
627
- write "**"
628
- visit node.children[0]
769
+ concat(
770
+ node.children[0],
771
+ " = ",
772
+ visit(node.children[1]),
773
+ )
774
+ when :restarg
775
+ concat(
776
+ "*",
777
+ node.children[0],
778
+ )
629
779
  when :kwarg
630
- write node.children[0].to_s
631
- write ":"
632
- when :forward_args, :forwarded_args
633
- write "..."
780
+ concat(
781
+ node.children[0],
782
+ ":",
783
+ )
634
784
  when :kwoptarg
635
- write node.children[0].to_s
636
- write ": "
637
- visit node.children[1]
785
+ concat(
786
+ node.children[0],
787
+ ": ",
788
+ visit(node.children[1]),
789
+ )
638
790
  when :kwrestarg
639
- write "**"
640
- write node.children[0].to_s if node.children[0]
791
+ if node.children[0]
792
+ "**" + node.children[0].to_s
793
+ else
794
+ "**"
795
+ end
641
796
  when :kwnilarg
642
- write "**nil"
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
643
802
  when :cbase
644
- write "::"
645
- when :zsuper
646
- write "super"
647
- when :nth_ref
648
- write "$"
649
- write node.children[0].to_s
650
- when :masgn
651
- left = node.children[0...-1]
652
- right = node.children[-1]
653
-
654
- left.each_with_index do |child_node, index|
655
- visit child_node
656
- end
657
-
658
- write " = "
659
- visit right
660
- when :mlhs
661
- write "(" if node.parent&.type == :mlhs
662
- node.children.each_with_index do |child_node, index|
663
- visit child_node
664
- write ", " unless index == node.children.length - 1
665
- end
666
- write ")" if node.parent&.type == :mlhs
667
- when :block_pass
668
- write "&"
669
- visit node.children[0]
803
+ "::"
670
804
  when :kwbegin
671
- write "begin"
672
- newline
673
- indent do
674
- visit node.children[0]
675
- end
676
- newline
677
- write "end"
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
+ )
678
826
  when :rescue
679
- visit node.children[0]
680
- newline
681
-
682
- dedent do
683
- write "rescue"
684
- visit node.children[1]
685
- end
827
+ concat(
828
+ visit(node.children[0]),
829
+ dedent(
830
+ hardline,
831
+ visit(node.children[1])
832
+ )
833
+ )
686
834
  when :resbody
687
- if node.children[0]
688
- write " "
689
- visit node.children[0] if node.children[0]
690
- end
691
-
692
- if node.children[1]
693
- write " => "
694
- write node.children[1].children[0].to_s
695
- end
696
- newline
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]
697
896
 
698
- indent do
699
- visit node.children[2] if node.children[2]
700
- end
701
- when :ensure
702
- visit node.children[0] if node.children[0]
703
- newline
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]
704
904
 
705
- dedent do
706
- write "ensure"
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]))
707
924
  end
708
925
 
709
- newline
710
- visit node.children[1] if node.children[1]
711
- else
712
- raise "unhandled node type `#{node.type}`\nnode: #{node}"
713
- end
714
- end
715
-
716
- def write_multiline_conditional(node)
717
- visit node.children[0]
718
-
719
- if node.type == :or
720
- write " ||"
721
- elsif node.type == :and
722
- write " &&"
723
- end
724
-
725
- newline
726
-
727
- visit node.children[1]
728
- end
729
-
730
- def format_string(string)
731
- raw_content = string.loc.expression.source
732
- content = raw_content[1...-1]
733
-
734
- if raw_content[0] == "'"
735
- content.gsub('"', '\\"').gsub('#{', '\\#{')
926
+ concat(
927
+ "next",
928
+ body
929
+ )
930
+ when :zsuper
931
+ "super"
932
+ when :block_pass
933
+ concat("&", visit(node.children[0]))
736
934
  else
737
- content.gsub("\\", "\\\\")
935
+ raise "Unexpected node type: #{node.type}"
738
936
  end
739
937
  end
740
938
 
741
- def splittable_separated_map(current_node, mappable, separator: ", ", skip_last_multiline_separator: false, write_space_if_single_line: false)
742
- one_line = capture do
743
- mappable.each_with_index do |child_node, index|
744
- visit child_node
745
- write separator unless index == mappable.length - 1
746
- end
747
- end
939
+ private
748
940
 
749
- if @current_line.length + one_line.length > MAX_LENGTH
750
- mappable.each_with_index do |child_node, index|
751
- newline
752
- visit child_node
753
- write separator.rstrip unless skip_last_multiline_separator && index == mappable.length - 1
754
- end
755
- MULTI_LINE
756
- else
757
- write ' ' if write_space_if_single_line
758
- write one_line
759
- SINGLE_LINE
941
+ def visit_each(node)
942
+ node.map do |child|
943
+ visit(child)
760
944
  end
761
945
  end
762
946
  end