prettyrb 0.4.0 → 0.5.0

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