rufo 0.0.1 → 0.0.2
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +1 -1
- data/exe/rufo +1 -6
- data/lib/rufo.rb +6 -2
- data/lib/rufo/command.rb +27 -0
- data/lib/rufo/formatter.rb +2296 -101
- data/lib/rufo/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4254f11d5e797cb76d947f8e5ae0e9fddd0fc281
|
4
|
+
data.tar.gz: b3937d7d503a7faf716e6deebf2f228feae26a36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b7110b0fc44a623f5c473b6be8b6a80bb7dede05a551a5fa8b1408f264c18281ceba1078ff991f0cda1deffa15b92369676c42254414af9ac46f3b2f3f4a07a
|
7
|
+
data.tar.gz: 80d832ba0b8798af6de39fe9adeaa10c6ab0c4f09350c5b3205684e2e34b658d54cbb64f9723240833f46eaa111c44da87c165a46ab80da823e69a99be8a221d
|
data/.gitignore
CHANGED
data/README.md
CHANGED
data/exe/rufo
CHANGED
data/lib/rufo.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
require "rufo/version"
|
2
2
|
|
3
3
|
module Rufo
|
4
|
-
|
5
|
-
|
4
|
+
class Bug < Exception; end
|
5
|
+
class SyntaxError < Exception; end
|
6
|
+
|
7
|
+
def self.format(code, **options)
|
8
|
+
Formatter.format(code, **options)
|
6
9
|
end
|
7
10
|
end
|
8
11
|
|
12
|
+
require_relative "rufo/command"
|
9
13
|
require_relative "rufo/formatter"
|
data/lib/rufo/command.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rufo::Command
|
2
|
+
def self.run
|
3
|
+
if ARGV.empty?
|
4
|
+
format_stdin
|
5
|
+
else
|
6
|
+
format_file ARGV[0]
|
7
|
+
end
|
8
|
+
rescue Rufo::Bug => ex
|
9
|
+
STDERR.puts "You've found a bug! Please report it to https://github.com/asterite/rufo/issues with code that triggers it"
|
10
|
+
STDERR.puts
|
11
|
+
raise ex
|
12
|
+
rescue Rufo::SyntaxError
|
13
|
+
STDERR.puts "Error: the given text is not a valid ruby program (it has syntax errors)"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.format_stdin
|
17
|
+
code = STDIN.read
|
18
|
+
result = Rufo.format(code)
|
19
|
+
print result
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.format_file(filename)
|
23
|
+
code = File.read(filename)
|
24
|
+
result = Rufo.format(code)
|
25
|
+
File.write(filename, result)
|
26
|
+
end
|
27
|
+
end
|
data/lib/rufo/formatter.rb
CHANGED
@@ -1,58 +1,128 @@
|
|
1
1
|
require "ripper"
|
2
2
|
|
3
3
|
class Rufo::Formatter
|
4
|
-
def self.format(code)
|
5
|
-
formatter = new(code)
|
4
|
+
def self.format(code, **options)
|
5
|
+
formatter = new(code, **options)
|
6
6
|
formatter.format
|
7
7
|
formatter.result
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
# The indent size (default: 2)
|
11
|
+
attr_accessor :indent_size
|
12
|
+
|
13
|
+
# Whether to align successive comments (default: true)
|
14
|
+
attr_accessor :align_comments
|
15
|
+
|
16
|
+
# Whether to convert multiline `{ ... }` block
|
17
|
+
# to `do ... end` (default: true)
|
18
|
+
attr_accessor :convert_brace_to_do
|
19
|
+
|
20
|
+
# Whether to align successive assignments (default: true)
|
21
|
+
attr_accessor :align_assignments
|
22
|
+
|
23
|
+
# Whether to align successive hash keys (default: true)
|
24
|
+
attr_accessor :align_hash_keys
|
25
|
+
|
26
|
+
def initialize(code, **options)
|
11
27
|
@code = code
|
12
28
|
@tokens = Ripper.lex(code).reverse!
|
13
29
|
@sexp = Ripper.sexp(code)
|
30
|
+
|
31
|
+
unless @sexp
|
32
|
+
raise ::Rufo::SyntaxError.new
|
33
|
+
end
|
34
|
+
|
35
|
+
@indent = 0
|
36
|
+
@line = 0
|
37
|
+
@column = 0
|
38
|
+
@last_was_newline = false
|
14
39
|
@output = ""
|
40
|
+
|
41
|
+
# The column of a `obj.method` call, so we can align
|
42
|
+
# calls to that dot
|
43
|
+
@dot_column = nil
|
44
|
+
|
45
|
+
# Heredocs list, associated with calls ([call, heredoc, tilde])
|
46
|
+
@heredocs = []
|
47
|
+
|
48
|
+
# Current call, to be able to associate it to heredocs
|
49
|
+
@current_call = nil
|
50
|
+
|
51
|
+
# The current heredoc being printed
|
52
|
+
@current_heredoc = nil
|
53
|
+
|
54
|
+
# The current hash or call or method that has hash-like parameters
|
55
|
+
@current_hash = nil
|
56
|
+
|
57
|
+
# Position of comments that occur at the end of a line
|
58
|
+
@comments_positions = []
|
59
|
+
|
60
|
+
# Position of assignments
|
61
|
+
@assignments_positions = []
|
62
|
+
|
63
|
+
# Hash keys positions
|
64
|
+
@hash_keys_positions = []
|
65
|
+
|
66
|
+
# Settings
|
67
|
+
@indent_size = options.fetch(:indent_size, 2)
|
68
|
+
@align_comments = options.fetch(:align_comments, true)
|
69
|
+
@convert_brace_to_do = options.fetch(:convert_brace_to_do, true)
|
70
|
+
@align_assignments = options.fetch(:align_assignments, true)
|
71
|
+
@align_hash_keys = options.fetch(:align_hash_keys, true)
|
15
72
|
end
|
16
73
|
|
17
74
|
def format
|
18
75
|
visit @sexp
|
19
|
-
write_line
|
76
|
+
write_line unless @last_was_newline
|
77
|
+
do_align_comments if @align_comments
|
78
|
+
do_align_assignments if @align_assignments
|
79
|
+
do_align_hash_keys if @align_hash_keys
|
20
80
|
end
|
21
81
|
|
22
82
|
def visit(node)
|
83
|
+
unless node.is_a?(Array)
|
84
|
+
bug "unexpected node: #{node} at #{current_token}"
|
85
|
+
end
|
86
|
+
|
23
87
|
case node.first
|
24
88
|
when :program
|
89
|
+
# Topmost node
|
90
|
+
#
|
25
91
|
# [:program, exps]
|
26
92
|
visit_exps node[1]
|
27
93
|
when :void_stmt
|
94
|
+
# Empty statement
|
95
|
+
#
|
28
96
|
# [:void_stmt]
|
29
|
-
|
97
|
+
skip_space_or_newline
|
30
98
|
when :@int
|
99
|
+
# Number literal
|
100
|
+
#
|
31
101
|
# [:@int, "123", [1, 0]]
|
32
102
|
consume_token :on_int
|
33
103
|
when :string_literal
|
34
|
-
|
35
|
-
consume_token :on_tstring_beg
|
36
|
-
visit_exps(node[1][1..-1])
|
37
|
-
consume_token :on_tstring_end
|
104
|
+
visit_string_literal node
|
38
105
|
when :@tstring_content
|
39
106
|
# [:@tstring_content, "hello ", [1, 1]]
|
40
|
-
|
107
|
+
heredoc, tilde = @current_heredoc
|
108
|
+
column = node[2][0]
|
109
|
+
|
110
|
+
# For heredocs with tilde we sometimes need to align the contents
|
111
|
+
if heredoc && tilde && @last_was_newline
|
112
|
+
write_indent(next_indent)
|
113
|
+
check :on_tstring_content
|
114
|
+
consume_token_value(current_token_value)
|
115
|
+
next_token
|
116
|
+
else
|
117
|
+
consume_token :on_tstring_content
|
118
|
+
end
|
41
119
|
when :string_embexpr
|
42
|
-
#
|
43
|
-
|
44
|
-
skip_space
|
45
|
-
visit_exps node[1]
|
46
|
-
consume_token :on_embexpr_end
|
120
|
+
# String interpolation piece ( #{exp} )
|
121
|
+
visit_string_interpolation node
|
47
122
|
when :symbol_literal
|
48
|
-
|
49
|
-
consume_token :on_symbeg
|
50
|
-
visit_exps node[1][1..-1]
|
123
|
+
visit_symbol_literal(node)
|
51
124
|
when :dyna_symbol
|
52
|
-
|
53
|
-
consume_token :on_symbeg
|
54
|
-
visit_exps node[1]
|
55
|
-
consume_token :on_tstring_end
|
125
|
+
visit_quoted_symbol_literal(node)
|
56
126
|
when :@ident
|
57
127
|
consume_token :on_ident
|
58
128
|
when :var_ref
|
@@ -64,137 +134,2262 @@ class Rufo::Formatter
|
|
64
134
|
when :@kw
|
65
135
|
# [:@kw, "nil", [1, 0]]
|
66
136
|
consume_token :on_kw
|
137
|
+
when :@ivar
|
138
|
+
# [:@ivar, "@foo", [1, 0]]
|
139
|
+
consume_token :on_ivar
|
140
|
+
when :@const
|
141
|
+
# [:@const, "FOO", [1, 0]]
|
142
|
+
consume_token :on_const
|
143
|
+
when :const_ref
|
144
|
+
# [:const_ref, [:@const, "Foo", [1, 8]]]
|
145
|
+
visit node[1]
|
146
|
+
when :top_const_ref
|
147
|
+
# [:top_const_ref, [:@const, "Foo", [1, 2]]]
|
148
|
+
consume_op "::"
|
149
|
+
skip_space_or_newline
|
150
|
+
visit node[1]
|
151
|
+
when :const_path_ref
|
152
|
+
visit_path(node)
|
67
153
|
when :assign
|
68
|
-
|
154
|
+
visit_assign(node)
|
155
|
+
when :opassign
|
156
|
+
visit_op_assign(node)
|
157
|
+
when :massign
|
158
|
+
visit_multiple_assign(node)
|
159
|
+
when :ifop
|
160
|
+
visit_ternary_if(node)
|
161
|
+
when :if_mod
|
162
|
+
visit_suffix(node, "if")
|
163
|
+
when :unless_mod
|
164
|
+
visit_suffix(node, "unless")
|
165
|
+
when :rescue_mod
|
166
|
+
visit_suffix(node, "rescue")
|
167
|
+
when :while_mod
|
168
|
+
visit_suffix(node, "while")
|
169
|
+
when :until_mod
|
170
|
+
visit_suffix(node, "until")
|
171
|
+
when :vcall
|
172
|
+
# [:vcall, exp]
|
69
173
|
visit node[1]
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
174
|
+
when :fcall
|
175
|
+
# [:fcall, [:@ident, "foo", [1, 0]]]
|
176
|
+
visit node[1]
|
177
|
+
when :command
|
178
|
+
visit_command(node)
|
179
|
+
when :command_call
|
180
|
+
visit_command_call(node)
|
181
|
+
when :args_add_block
|
182
|
+
visit_call_args(node)
|
183
|
+
when :args_add_star
|
184
|
+
visit_args_add_star(node)
|
185
|
+
when :bare_assoc_hash
|
186
|
+
# **x, **y, ...
|
187
|
+
#
|
188
|
+
# [:bare_assoc_hash, exps]
|
189
|
+
visit_comma_separated_list node[1]
|
190
|
+
when :method_add_arg
|
191
|
+
visit_call_without_receiver(node)
|
192
|
+
when :method_add_block
|
193
|
+
visit_call_with_block(node)
|
194
|
+
when :call
|
195
|
+
visit_call_with_receiver(node)
|
196
|
+
when :brace_block
|
197
|
+
visit_brace_block(node)
|
198
|
+
when :do_block
|
199
|
+
visit_do_block(node)
|
200
|
+
when :block_var
|
201
|
+
visit_block_arguments(node)
|
202
|
+
when :begin
|
203
|
+
visit_begin(node)
|
204
|
+
when :bodystmt
|
205
|
+
visit_bodystmt(node)
|
206
|
+
when :if
|
207
|
+
visit_if(node)
|
208
|
+
when :unless
|
209
|
+
visit_unless(node)
|
210
|
+
when :while
|
211
|
+
visit_while(node)
|
212
|
+
when :until
|
213
|
+
visit_until(node)
|
214
|
+
when :case
|
215
|
+
visit_case(node)
|
216
|
+
when :when
|
217
|
+
visit_when(node)
|
218
|
+
when :unary
|
219
|
+
visit_unary(node)
|
220
|
+
when :binary
|
221
|
+
visit_binary(node)
|
222
|
+
when :class
|
223
|
+
visit_class(node)
|
224
|
+
when :module
|
225
|
+
visit_module(node)
|
226
|
+
when :mrhs_new_from_args
|
227
|
+
visit_mrhs_new_from_args(node)
|
228
|
+
when :mlhs_paren
|
229
|
+
visit_mlhs_paren(node)
|
230
|
+
when :def
|
231
|
+
visit_def(node)
|
232
|
+
when :defs
|
233
|
+
visit_def_with_receiver(node)
|
234
|
+
when :paren
|
235
|
+
visit_paren(node)
|
236
|
+
when :params
|
237
|
+
visit_params(node)
|
238
|
+
when :array
|
239
|
+
visit_array(node)
|
240
|
+
when :hash
|
241
|
+
visit_hash(node)
|
242
|
+
when :assoc_new
|
243
|
+
visit_hash_key_value(node)
|
244
|
+
when :assoc_splat
|
245
|
+
visit_splat_inside_hash(node)
|
246
|
+
when :@label
|
247
|
+
# [:@label, "foo:", [1, 3]]
|
248
|
+
write node[1]
|
249
|
+
next_token
|
250
|
+
when :dot2
|
251
|
+
visit_range(node, true)
|
252
|
+
when :dot3
|
253
|
+
visit_range(node, false)
|
254
|
+
when :regexp_literal
|
255
|
+
visit_regexp_literal(node)
|
256
|
+
when :aref
|
257
|
+
visit_array_access(node)
|
258
|
+
when :aref_field
|
259
|
+
visit_array_setter(node)
|
260
|
+
when :sclass
|
261
|
+
visit_sclass(node)
|
262
|
+
when :field
|
263
|
+
visit_setter(node)
|
264
|
+
when :return0
|
265
|
+
consume_keyword "return"
|
266
|
+
when :return
|
267
|
+
visit_return(node)
|
268
|
+
when :break
|
269
|
+
visit_break(node)
|
270
|
+
when :next
|
271
|
+
visit_next(node)
|
272
|
+
when :yield0
|
273
|
+
consume_keyword "yield"
|
274
|
+
when :yield
|
275
|
+
visit_yield(node)
|
276
|
+
when :@op
|
277
|
+
# [:@op, "*", [1, 1]]
|
278
|
+
write node[1]
|
279
|
+
next_token
|
280
|
+
when :lambda
|
281
|
+
visit_lambda(node)
|
282
|
+
when :zsuper
|
283
|
+
# [:zsuper]
|
284
|
+
consume_keyword "super"
|
285
|
+
when :super
|
286
|
+
visit_super(node)
|
74
287
|
else
|
75
|
-
|
288
|
+
bug "Unhandled node: #{node.first}"
|
76
289
|
end
|
77
290
|
end
|
78
291
|
|
79
|
-
def visit_exps(exps)
|
292
|
+
def visit_exps(exps, with_indent = false, with_lines = true)
|
293
|
+
consume_end_of_line(true)
|
294
|
+
|
295
|
+
line_before_endline = nil
|
296
|
+
|
80
297
|
exps.each_with_index do |exp, i|
|
298
|
+
exp_kind = exp[0]
|
299
|
+
|
300
|
+
# Skip voids to avoid extra indentation
|
301
|
+
if exp_kind == :void_stmt
|
302
|
+
next
|
303
|
+
end
|
304
|
+
|
305
|
+
if with_indent
|
306
|
+
# Don't indent if this exp is in the same line as the previous
|
307
|
+
# one (this happens when there's a semicolon between the exps)
|
308
|
+
unless line_before_endline && line_before_endline == @line
|
309
|
+
write_indent
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
81
313
|
visit exp
|
82
|
-
skip_space
|
83
314
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
else
|
94
|
-
write ";"
|
95
|
-
write " " unless last?(i, exps)
|
315
|
+
line_before_endline = @line
|
316
|
+
|
317
|
+
is_last = last?(i, exps)
|
318
|
+
if with_lines
|
319
|
+
consume_end_of_line(false, !is_last, !is_last)
|
320
|
+
|
321
|
+
# Make sure to put two lines before defs, class and others
|
322
|
+
if !is_last && (needs_two_lines?(exp_kind) || needs_two_lines?(exps[i + 1][0])) && @line <= line_before_endline + 1
|
323
|
+
write_line
|
96
324
|
end
|
97
|
-
|
98
|
-
|
325
|
+
else
|
326
|
+
skip_space_or_newline unless is_last
|
99
327
|
end
|
328
|
+
end
|
329
|
+
end
|
100
330
|
|
101
|
-
|
331
|
+
def needs_two_lines?(exp_kind)
|
332
|
+
case exp_kind
|
333
|
+
when :def, :class, :module
|
334
|
+
true
|
335
|
+
else
|
336
|
+
false
|
102
337
|
end
|
103
338
|
end
|
104
339
|
|
105
|
-
def
|
340
|
+
def visit_string_literal(node)
|
341
|
+
# [:string_literal, [:string_content, exps]]
|
342
|
+
heredoc = current_token_kind == :on_heredoc_beg
|
343
|
+
tilde = current_token_value.include?("~")
|
344
|
+
|
345
|
+
if heredoc
|
346
|
+
write current_token_value.rstrip
|
347
|
+
next_token
|
348
|
+
skip_space
|
349
|
+
|
350
|
+
# A comma after a heredoc means the heredoc contents
|
351
|
+
# come after an argument list, so put it in a list
|
352
|
+
# for later.
|
353
|
+
# The same happens if we already have a heredoc in
|
354
|
+
# the list, which means this will come after other
|
355
|
+
# heredocs.
|
356
|
+
if comma? || !@heredocs.empty?
|
357
|
+
@heredocs << [@current_call, node, tilde]
|
358
|
+
return
|
359
|
+
end
|
360
|
+
else
|
361
|
+
consume_token :on_tstring_beg
|
362
|
+
end
|
363
|
+
|
364
|
+
if heredoc
|
365
|
+
@current_heredoc = [node, tilde]
|
366
|
+
end
|
367
|
+
|
368
|
+
visit_string_literal_end(node)
|
369
|
+
|
370
|
+
@current_heredoc = nil if heredoc
|
106
371
|
end
|
107
372
|
|
108
|
-
def
|
109
|
-
|
373
|
+
def visit_string_literal_end(node)
|
374
|
+
visit_exps(node[1][1..-1], false, false)
|
110
375
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
next_token
|
117
|
-
when :on_sp, :on_semicolon
|
118
|
-
next_token
|
376
|
+
if current_token_kind == :on_heredoc_end
|
377
|
+
heredoc, tilde = @current_heredoc
|
378
|
+
if heredoc && tilde
|
379
|
+
write_indent
|
380
|
+
write current_token_value.strip
|
119
381
|
else
|
120
|
-
|
382
|
+
write current_token_value.rstrip
|
121
383
|
end
|
384
|
+
next_token
|
385
|
+
else
|
386
|
+
consume_token :on_tstring_end
|
122
387
|
end
|
123
388
|
end
|
124
389
|
|
125
|
-
def
|
126
|
-
|
127
|
-
|
390
|
+
def visit_string_interpolation(node)
|
391
|
+
# [:string_embexpr, exps]
|
392
|
+
consume_token :on_embexpr_beg
|
393
|
+
skip_space_or_newline
|
394
|
+
visit_exps node[1], false, false
|
395
|
+
skip_space_or_newline
|
396
|
+
consume_token :on_embexpr_end
|
128
397
|
end
|
129
398
|
|
130
|
-
def
|
131
|
-
|
132
|
-
|
133
|
-
|
399
|
+
def visit_symbol_literal(node)
|
400
|
+
# :foo
|
401
|
+
#
|
402
|
+
# [:symbol_literal, [:symbol, [:@ident, "foo", [1, 1]]]]
|
403
|
+
consume_token :on_symbeg
|
404
|
+
visit_exps node[1][1..-1], false, false
|
134
405
|
end
|
135
406
|
|
136
|
-
def
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
407
|
+
def visit_quoted_symbol_literal(node)
|
408
|
+
# :"foo"
|
409
|
+
#
|
410
|
+
# [:dyna_symbol, exps]
|
411
|
+
consume_token :on_symbeg
|
412
|
+
visit_exps node[1], false, false
|
413
|
+
consume_token :on_tstring_end
|
414
|
+
end
|
415
|
+
|
416
|
+
def visit_path(node)
|
417
|
+
# Foo::Bar
|
418
|
+
#
|
419
|
+
# [:const_path_ref,
|
420
|
+
# [:var_ref, [:@const, "Foo", [1, 0]]],
|
421
|
+
# [:@const, "Bar", [1, 5]]]
|
422
|
+
pieces = node[1..-1]
|
423
|
+
pieces.each_with_index do |piece, i|
|
424
|
+
visit piece
|
425
|
+
unless last?(i, pieces)
|
426
|
+
consume_op "::"
|
427
|
+
skip_space_or_newline
|
144
428
|
end
|
145
429
|
end
|
146
430
|
end
|
147
431
|
|
148
|
-
def
|
149
|
-
|
432
|
+
def visit_assign(node)
|
433
|
+
# target = value
|
434
|
+
#
|
435
|
+
# [:assign, target, value]
|
436
|
+
_, target, value = node
|
437
|
+
|
438
|
+
visit target
|
439
|
+
consume_space
|
440
|
+
track_assignment
|
441
|
+
consume_op "="
|
442
|
+
visit_assign_value value
|
150
443
|
end
|
151
444
|
|
152
|
-
def
|
153
|
-
|
154
|
-
|
445
|
+
def visit_op_assign(node)
|
446
|
+
# target += value
|
447
|
+
#
|
448
|
+
# [:opassign, target, op, value]
|
449
|
+
_, target, op, value = node
|
450
|
+
visit target
|
451
|
+
consume_space
|
452
|
+
|
453
|
+
# [:@op, "+=", [1, 2]],
|
454
|
+
check :on_op
|
455
|
+
|
456
|
+
before = op[1][0...-1]
|
457
|
+
after = op[1][-1]
|
458
|
+
|
459
|
+
write before
|
460
|
+
track_assignment before.size
|
461
|
+
write after
|
155
462
|
next_token
|
463
|
+
|
464
|
+
visit_assign_value value
|
156
465
|
end
|
157
466
|
|
158
|
-
def
|
159
|
-
|
160
|
-
|
161
|
-
|
467
|
+
def visit_multiple_assign(node)
|
468
|
+
# [:massign, lefts, right]
|
469
|
+
_, lefts, right = node
|
470
|
+
|
471
|
+
visit_comma_separated_list lefts
|
472
|
+
consume_space
|
473
|
+
track_assignment
|
474
|
+
consume_op "="
|
475
|
+
visit_assign_value right
|
476
|
+
end
|
477
|
+
|
478
|
+
def visit_assign_value(value)
|
479
|
+
skip_space
|
480
|
+
indent_after_space value, indentable_keyword?
|
481
|
+
end
|
482
|
+
|
483
|
+
def indentable_keyword?
|
484
|
+
return unless current_token_kind == :on_kw
|
485
|
+
|
486
|
+
case current_token_value
|
487
|
+
when "if", "unless", "case"
|
488
|
+
true
|
489
|
+
else
|
490
|
+
false
|
162
491
|
end
|
163
|
-
write current_token_value
|
164
|
-
next_token
|
165
492
|
end
|
166
493
|
|
167
|
-
def
|
168
|
-
@
|
494
|
+
def track_assignment(offset = 0)
|
495
|
+
track_alignment @assignments_positions, offset
|
169
496
|
end
|
170
497
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
498
|
+
def track_hash_key
|
499
|
+
return unless @current_hash
|
500
|
+
|
501
|
+
track_alignment @hash_keys_positions, 0, @current_hash.object_id
|
502
|
+
end
|
503
|
+
|
504
|
+
def track_alignment(target, offset = 0, id = nil)
|
505
|
+
last = target.last
|
506
|
+
if last && last[0] == @line
|
507
|
+
last << :ignore if last.size < 6
|
508
|
+
return
|
174
509
|
end
|
510
|
+
|
511
|
+
target << [@line, @column, @indent, id, offset]
|
175
512
|
end
|
176
513
|
|
177
|
-
|
178
|
-
|
179
|
-
|
514
|
+
def visit_ternary_if(node)
|
515
|
+
# cond ? then : else
|
516
|
+
#
|
517
|
+
# [:ifop, cond, then_body, else_body]
|
518
|
+
_, cond, then_body, else_body = node
|
519
|
+
|
520
|
+
visit cond
|
521
|
+
consume_space
|
522
|
+
consume_op "?"
|
523
|
+
|
524
|
+
skip_space
|
525
|
+
if newline? || comment?
|
526
|
+
consume_end_of_line
|
527
|
+
write_indent(next_indent)
|
528
|
+
else
|
529
|
+
consume_space
|
530
|
+
end
|
531
|
+
|
532
|
+
visit then_body
|
533
|
+
consume_space
|
534
|
+
consume_op ":"
|
535
|
+
|
536
|
+
skip_space
|
537
|
+
if newline? || comment?
|
538
|
+
consume_end_of_line
|
539
|
+
write_indent(next_indent)
|
540
|
+
else
|
541
|
+
consume_space
|
542
|
+
end
|
543
|
+
|
544
|
+
visit else_body
|
180
545
|
end
|
181
546
|
|
182
|
-
def
|
183
|
-
|
184
|
-
|
547
|
+
def visit_suffix(node, suffix)
|
548
|
+
# then if cond
|
549
|
+
# then unless cond
|
550
|
+
# exp rescue handler
|
551
|
+
#
|
552
|
+
# [:if_mod, cond, body]
|
553
|
+
_, cond, body = node
|
554
|
+
|
555
|
+
visit body
|
556
|
+
consume_space
|
557
|
+
consume_keyword(suffix)
|
558
|
+
consume_space
|
559
|
+
visit cond
|
185
560
|
end
|
186
561
|
|
187
|
-
def
|
188
|
-
|
189
|
-
|
562
|
+
def visit_call_with_receiver(node)
|
563
|
+
# [:call, obj, :".", call]
|
564
|
+
_, obj, text, call = node
|
565
|
+
|
566
|
+
@dot_column = nil
|
567
|
+
visit obj
|
568
|
+
|
569
|
+
skip_space
|
570
|
+
|
571
|
+
if newline? || comment?
|
572
|
+
consume_end_of_line
|
573
|
+
|
574
|
+
write_indent(@dot_column || next_indent)
|
575
|
+
end
|
576
|
+
|
577
|
+
# Remember dot column
|
578
|
+
dot_column = @column
|
579
|
+
consume_call_dot
|
580
|
+
|
581
|
+
skip_space
|
582
|
+
|
583
|
+
if newline? || comment?
|
584
|
+
consume_end_of_line
|
585
|
+
write_indent(next_indent)
|
586
|
+
else
|
587
|
+
skip_space_or_newline
|
588
|
+
end
|
589
|
+
|
590
|
+
visit call
|
591
|
+
|
592
|
+
# Only set it after we visit the call after the dot,
|
593
|
+
# so we remember the outmost dot position
|
594
|
+
@dot_column = dot_column
|
190
595
|
end
|
191
596
|
|
192
|
-
def
|
193
|
-
|
597
|
+
def consume_call_dot
|
598
|
+
if current_token_kind == :on_op
|
599
|
+
consume_op "::"
|
600
|
+
else
|
601
|
+
check :on_period
|
602
|
+
next_token
|
603
|
+
write "."
|
604
|
+
end
|
194
605
|
end
|
195
606
|
|
196
|
-
def
|
197
|
-
|
607
|
+
def visit_call_without_receiver(node)
|
608
|
+
# foo(arg1, ..., argN)
|
609
|
+
#
|
610
|
+
# [:method_add_arg,
|
611
|
+
# [:fcall, [:@ident, "foo", [1, 0]]],
|
612
|
+
# [:arg_paren, [:args_add_block, [[:@int, "1", [1, 6]]], false]]]
|
613
|
+
_, name, args = node
|
614
|
+
|
615
|
+
visit name
|
616
|
+
|
617
|
+
# Some times a call comes without parens (should probably come as command, but well...)
|
618
|
+
return if args.empty?
|
619
|
+
|
620
|
+
# Remember dot column so it's not affected by args
|
621
|
+
dot_column = @dot_column
|
622
|
+
|
623
|
+
visit_call_at_paren(node, args)
|
624
|
+
|
625
|
+
# Restore dot column so it's not affected by args
|
626
|
+
@dot_column = dot_column
|
627
|
+
end
|
628
|
+
|
629
|
+
def visit_call_at_paren(node, args)
|
630
|
+
consume_token :on_lparen
|
631
|
+
|
632
|
+
# If there's a trailing comma then comes [:arg_paren, args],
|
633
|
+
# which is a bit unexpected, so we fix it
|
634
|
+
if args[1].is_a?(Array) && args[1][0].is_a?(Array)
|
635
|
+
args_node = [:args_add_block, args[1], false]
|
636
|
+
else
|
637
|
+
args_node = args[1]
|
638
|
+
end
|
639
|
+
|
640
|
+
if args_node
|
641
|
+
skip_space
|
642
|
+
|
643
|
+
needs_trailing_newline = newline? || comment?
|
644
|
+
|
645
|
+
push_call(node) do
|
646
|
+
visit args_node
|
647
|
+
end
|
648
|
+
|
649
|
+
found_comma = comma?
|
650
|
+
|
651
|
+
if found_comma
|
652
|
+
if needs_trailing_newline
|
653
|
+
write ","
|
654
|
+
next_token
|
655
|
+
indent(next_indent) do
|
656
|
+
consume_end_of_line
|
657
|
+
end
|
658
|
+
write_indent
|
659
|
+
else
|
660
|
+
next_token
|
661
|
+
skip_space
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
if newline? || comment?
|
666
|
+
if needs_trailing_newline
|
667
|
+
indent(next_indent) do
|
668
|
+
consume_end_of_line
|
669
|
+
end
|
670
|
+
write_indent
|
671
|
+
else
|
672
|
+
skip_space_or_newline
|
673
|
+
end
|
674
|
+
else
|
675
|
+
if needs_trailing_newline && !found_comma
|
676
|
+
consume_end_of_line
|
677
|
+
write_indent
|
678
|
+
end
|
679
|
+
end
|
680
|
+
else
|
681
|
+
skip_space_or_newline
|
682
|
+
end
|
683
|
+
|
684
|
+
consume_token :on_rparen
|
685
|
+
|
686
|
+
check_heredocs_at_call_end(node)
|
687
|
+
end
|
688
|
+
|
689
|
+
def visit_command(node)
|
690
|
+
# foo arg1, ..., argN
|
691
|
+
#
|
692
|
+
# [:command, name, args]
|
693
|
+
_, name, args = node
|
694
|
+
|
695
|
+
push_call(node) do
|
696
|
+
visit name
|
697
|
+
consume_space
|
698
|
+
end
|
699
|
+
|
700
|
+
visit_command_end(node, args)
|
701
|
+
end
|
702
|
+
|
703
|
+
def visit_command_end(node, args)
|
704
|
+
push_call(node) do
|
705
|
+
indent(@column) do
|
706
|
+
visit args
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
check_heredocs_at_call_end(node)
|
711
|
+
end
|
712
|
+
|
713
|
+
def check_heredocs_at_call_end(node)
|
714
|
+
printed = false
|
715
|
+
|
716
|
+
until @heredocs.empty?
|
717
|
+
scope, heredoc, tilde = @heredocs.first
|
718
|
+
break unless scope.equal?(node)
|
719
|
+
|
720
|
+
# Need to print a line between consecutive heredoc ends
|
721
|
+
write_line if printed
|
722
|
+
|
723
|
+
@heredocs.shift
|
724
|
+
@current_heredoc = [heredoc, tilde]
|
725
|
+
visit_string_literal_end(heredoc)
|
726
|
+
@current_heredoc = nil
|
727
|
+
printed = true
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
def visit_command_call(node)
|
732
|
+
# [:command_call,
|
733
|
+
# receiver
|
734
|
+
# :".",
|
735
|
+
# name
|
736
|
+
# [:args_add_block, [[:@int, "1", [1, 8]]], block]]
|
737
|
+
_, receiver, dot, name, args = node
|
738
|
+
|
739
|
+
visit receiver
|
740
|
+
skip_space_or_newline
|
741
|
+
|
742
|
+
# Remember dot column
|
743
|
+
dot_column = @column
|
744
|
+
consume_call_dot
|
745
|
+
|
746
|
+
skip_space
|
747
|
+
|
748
|
+
if newline? || comment?
|
749
|
+
consume_end_of_line
|
750
|
+
write_indent(next_indent)
|
751
|
+
else
|
752
|
+
skip_space_or_newline
|
753
|
+
end
|
754
|
+
|
755
|
+
visit name
|
756
|
+
consume_space
|
757
|
+
|
758
|
+
indent(@column) do
|
759
|
+
visit args
|
760
|
+
end
|
761
|
+
|
762
|
+
# Only set it after we visit the call after the dot,
|
763
|
+
# so we remember the outmost dot position
|
764
|
+
@dot_column = dot_column
|
765
|
+
end
|
766
|
+
|
767
|
+
def visit_call_with_block(node)
|
768
|
+
# [:method_add_block, call, block]
|
769
|
+
_, call, block = node
|
770
|
+
|
771
|
+
visit call
|
772
|
+
|
773
|
+
consume_space
|
774
|
+
visit block
|
775
|
+
end
|
776
|
+
|
777
|
+
def visit_brace_block(node)
|
778
|
+
# [:brace_block, args, body]
|
779
|
+
_, args, body = node
|
780
|
+
|
781
|
+
# This is for the empty `{ }` block
|
782
|
+
if void_exps?(body)
|
783
|
+
consume_token :on_lbrace
|
784
|
+
consume_block_args args
|
785
|
+
consume_space
|
786
|
+
consume_token :on_rbrace
|
787
|
+
return
|
788
|
+
end
|
789
|
+
|
790
|
+
closing_brace_token = find_closing_brace_token
|
791
|
+
|
792
|
+
# If the whole block fits into a single line, use braces
|
793
|
+
if current_token[0][0] == closing_brace_token[0][0]
|
794
|
+
consume_token :on_lbrace
|
795
|
+
|
796
|
+
consume_block_args args
|
797
|
+
|
798
|
+
consume_space
|
799
|
+
visit_exps body, false, false
|
800
|
+
consume_space
|
801
|
+
|
802
|
+
consume_token :on_rbrace
|
803
|
+
return
|
804
|
+
end
|
805
|
+
|
806
|
+
# Otherwise, use `do` (if told so)
|
807
|
+
check :on_lbrace
|
808
|
+
|
809
|
+
if @convert_brace_to_do
|
810
|
+
write "do"
|
811
|
+
else
|
812
|
+
write "{"
|
813
|
+
end
|
814
|
+
|
815
|
+
next_token
|
816
|
+
|
817
|
+
consume_block_args args
|
818
|
+
|
819
|
+
indent_body body
|
820
|
+
|
821
|
+
write_indent
|
822
|
+
|
823
|
+
check :on_rbrace
|
824
|
+
next_token
|
825
|
+
|
826
|
+
if @convert_brace_to_do
|
827
|
+
write "end"
|
828
|
+
else
|
829
|
+
write "}"
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
def visit_do_block(node)
|
834
|
+
# [:brace_block, args, body]
|
835
|
+
_, args, body = node
|
836
|
+
|
837
|
+
consume_keyword "do"
|
838
|
+
|
839
|
+
consume_block_args args
|
840
|
+
|
841
|
+
indent_body body
|
842
|
+
|
843
|
+
write_indent
|
844
|
+
consume_keyword "end"
|
845
|
+
end
|
846
|
+
|
847
|
+
def consume_block_args(args)
|
848
|
+
if args
|
849
|
+
consume_space
|
850
|
+
# + 1 because of |...|
|
851
|
+
# ^
|
852
|
+
indent(@column + 1) do
|
853
|
+
visit args
|
854
|
+
end
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
def visit_block_arguments(node)
|
859
|
+
# [:block_var, params, ??]
|
860
|
+
_, params = node
|
861
|
+
|
862
|
+
consume_op "|"
|
863
|
+
skip_space_or_newline
|
864
|
+
|
865
|
+
visit params
|
866
|
+
|
867
|
+
skip_space_or_newline
|
868
|
+
consume_op "|"
|
869
|
+
end
|
870
|
+
|
871
|
+
def visit_call_args(node)
|
872
|
+
# [:args_add_block, args, block]
|
873
|
+
_, args, block_arg = node
|
874
|
+
|
875
|
+
if !args.empty? && args[0] == :args_add_star
|
876
|
+
# arg1, ..., *star
|
877
|
+
visit args
|
878
|
+
return
|
879
|
+
end
|
880
|
+
|
881
|
+
visit_comma_separated_list args, true
|
882
|
+
|
883
|
+
if block_arg
|
884
|
+
write_params_comma if comma?
|
885
|
+
|
886
|
+
consume_op "&"
|
887
|
+
visit block_arg
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
def visit_args_add_star(node)
|
892
|
+
# [:args_add_star, args, star, post_args]
|
893
|
+
_, args, star, *post_args = node
|
894
|
+
|
895
|
+
if !args.empty? && args[0] == :args_add_star
|
896
|
+
# arg1, ..., *star
|
897
|
+
visit args
|
898
|
+
else
|
899
|
+
visit_comma_separated_list args
|
900
|
+
end
|
901
|
+
|
902
|
+
skip_space
|
903
|
+
|
904
|
+
write_params_comma if comma?
|
905
|
+
|
906
|
+
consume_op "*"
|
907
|
+
skip_space_or_newline
|
908
|
+
visit star
|
909
|
+
|
910
|
+
if post_args && !post_args.empty?
|
911
|
+
write_params_comma
|
912
|
+
visit_comma_separated_list post_args
|
913
|
+
end
|
914
|
+
end
|
915
|
+
|
916
|
+
def visit_begin(node)
|
917
|
+
# begin
|
918
|
+
# body
|
919
|
+
# end
|
920
|
+
#
|
921
|
+
# [:begin, [:bodystmt, body, rescue_body, else_body, ensure_body]]
|
922
|
+
consume_keyword "begin"
|
923
|
+
visit node[1]
|
924
|
+
end
|
925
|
+
|
926
|
+
def visit_bodystmt(node)
|
927
|
+
# [:bodystmt, body, rescue_body, else_body, ensure_body]
|
928
|
+
_, body, rescue_body, else_body, ensure_body = node
|
929
|
+
indent_body body
|
930
|
+
|
931
|
+
if rescue_body
|
932
|
+
# [:rescue, type, name, body, nil]
|
933
|
+
_, type, name, body = rescue_body
|
934
|
+
write_indent
|
935
|
+
consume_keyword "rescue"
|
936
|
+
if type
|
937
|
+
skip_space
|
938
|
+
write_space " "
|
939
|
+
indent(@column) do
|
940
|
+
visit_rescue_types(type)
|
941
|
+
end
|
942
|
+
end
|
943
|
+
|
944
|
+
if name
|
945
|
+
skip_space
|
946
|
+
write_space " "
|
947
|
+
consume_op "=>"
|
948
|
+
skip_space
|
949
|
+
write_space " "
|
950
|
+
visit name
|
951
|
+
end
|
952
|
+
|
953
|
+
indent_body body
|
954
|
+
end
|
955
|
+
|
956
|
+
if else_body
|
957
|
+
# [:else, body]
|
958
|
+
write_indent
|
959
|
+
consume_keyword "else"
|
960
|
+
indent_body else_body[1]
|
961
|
+
end
|
962
|
+
|
963
|
+
if ensure_body
|
964
|
+
# [:ensure, body]
|
965
|
+
write_indent
|
966
|
+
consume_keyword "ensure"
|
967
|
+
indent_body ensure_body[1]
|
968
|
+
end
|
969
|
+
|
970
|
+
write_indent
|
971
|
+
consume_keyword "end"
|
972
|
+
end
|
973
|
+
|
974
|
+
def visit_rescue_types(node)
|
975
|
+
if node[0] == :mrhs_new_from_args
|
976
|
+
visit node
|
977
|
+
else
|
978
|
+
visit_exps node, false, false
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
def visit_mrhs_new_from_args(node)
|
983
|
+
# Multiple exception types
|
984
|
+
# [:mrhs_new_from_args, exps, final_exp]
|
985
|
+
nodes = [*node[1], node[2]]
|
986
|
+
visit_comma_separated_list(nodes)
|
987
|
+
end
|
988
|
+
|
989
|
+
def visit_mlhs_paren(node)
|
990
|
+
# [:mlhs_paren,
|
991
|
+
# [[:mlhs_paren, [:@ident, "x", [1, 12]]]]
|
992
|
+
# ]
|
993
|
+
_, args = node
|
994
|
+
|
995
|
+
# For some reason there's nested :mlhs_paren for
|
996
|
+
# a single parentheses. It seems when there's
|
997
|
+
# a nested array we need parens, otherwise we
|
998
|
+
# just output whatever's inside `args`.
|
999
|
+
if args.is_a?(Array) && args[0].is_a?(Array)
|
1000
|
+
check :on_lparen
|
1001
|
+
write "("
|
1002
|
+
next_token
|
1003
|
+
skip_space_or_newline
|
1004
|
+
|
1005
|
+
indent(@column) do
|
1006
|
+
visit_comma_separated_list args
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
check :on_rparen
|
1010
|
+
write ")"
|
1011
|
+
next_token
|
1012
|
+
else
|
1013
|
+
visit args
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
def visit_comma_separated_list(nodes, inside_call = false)
|
1018
|
+
# This is `x, *y, z` on the left hand side of an assignment
|
1019
|
+
if nodes[0] == :mlhs_add_star
|
1020
|
+
visit_mlhs_add_star(nodes)
|
1021
|
+
return
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
needs_indent = false
|
1025
|
+
|
1026
|
+
if inside_call
|
1027
|
+
if newline? || comment?
|
1028
|
+
needs_indent = true
|
1029
|
+
base_column = next_indent
|
1030
|
+
consume_end_of_line
|
1031
|
+
write_indent(base_column)
|
1032
|
+
else
|
1033
|
+
base_column = @column
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
nodes.each_with_index do |exp, i|
|
1038
|
+
maybe_indent(needs_indent, base_column) do
|
1039
|
+
if block_given?
|
1040
|
+
yield exp
|
1041
|
+
else
|
1042
|
+
visit exp
|
1043
|
+
end
|
1044
|
+
end
|
1045
|
+
skip_space
|
1046
|
+
unless last?(i, nodes)
|
1047
|
+
check :on_comma
|
1048
|
+
write ","
|
1049
|
+
next_token
|
1050
|
+
skip_space
|
1051
|
+
|
1052
|
+
if newline? || comment?
|
1053
|
+
indent(base_column || @indent) do
|
1054
|
+
consume_end_of_line(false, false, false)
|
1055
|
+
write_indent
|
1056
|
+
end
|
1057
|
+
else
|
1058
|
+
write_space " "
|
1059
|
+
skip_space_or_newline
|
1060
|
+
end
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def visit_mlhs_add_star(node)
|
1066
|
+
# [:mlhs_add_star, before, star, after]
|
1067
|
+
_, before, star, after = node
|
1068
|
+
|
1069
|
+
if before && !before.empty?
|
1070
|
+
visit_comma_separated_list before
|
1071
|
+
write_params_comma
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
consume_op "*"
|
1075
|
+
skip_space_or_newline
|
1076
|
+
|
1077
|
+
visit star
|
1078
|
+
|
1079
|
+
if after && !after.empty?
|
1080
|
+
write_params_comma
|
1081
|
+
visit_comma_separated_list after
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
def visit_unary(node)
|
1086
|
+
# [:unary, :-@, [:vcall, [:@ident, "x", [1, 2]]]]
|
1087
|
+
check :on_op
|
1088
|
+
write current_token_value
|
1089
|
+
next_token
|
1090
|
+
skip_space_or_newline
|
1091
|
+
visit node[2]
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
def visit_binary(node)
|
1095
|
+
# [:binary, left, op, right]
|
1096
|
+
_, left, op, right = node
|
1097
|
+
|
1098
|
+
visit left
|
1099
|
+
if space?
|
1100
|
+
needs_space = true
|
1101
|
+
else
|
1102
|
+
needs_space = op != :* && op != :/ && op != :**
|
1103
|
+
end
|
1104
|
+
skip_space
|
1105
|
+
write_space " " if needs_space
|
1106
|
+
check :on_op
|
1107
|
+
write current_token_value
|
1108
|
+
next_token
|
1109
|
+
indent_after_space right, false, needs_space
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
def visit_class(node)
|
1113
|
+
# [:class,
|
1114
|
+
# name
|
1115
|
+
# superclass
|
1116
|
+
# [:bodystmt, body, nil, nil, nil]]
|
1117
|
+
_, name, superclass, body = node
|
1118
|
+
|
1119
|
+
consume_keyword "class"
|
1120
|
+
skip_space_or_newline
|
1121
|
+
write_space " "
|
1122
|
+
visit name
|
1123
|
+
|
1124
|
+
if superclass
|
1125
|
+
skip_space_or_newline
|
1126
|
+
write_space " "
|
1127
|
+
consume_op "<"
|
1128
|
+
skip_space_or_newline
|
1129
|
+
write_space " "
|
1130
|
+
visit superclass
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
maybe_inline_body body
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
def visit_module(node)
|
1137
|
+
# [:module,
|
1138
|
+
# name
|
1139
|
+
# [:bodystmt, body, nil, nil, nil]]
|
1140
|
+
_, name, body = node
|
1141
|
+
|
1142
|
+
consume_keyword "module"
|
1143
|
+
skip_space_or_newline
|
1144
|
+
write_space " "
|
1145
|
+
visit name
|
1146
|
+
maybe_inline_body body
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
def maybe_inline_body(body)
|
1150
|
+
skip_space
|
1151
|
+
if semicolon? && empty_body?(body)
|
1152
|
+
next_token
|
1153
|
+
skip_space
|
1154
|
+
if newline?
|
1155
|
+
skip_space_or_newline
|
1156
|
+
visit body
|
1157
|
+
else
|
1158
|
+
write "; "
|
1159
|
+
skip_space_or_newline
|
1160
|
+
consume_keyword "end"
|
1161
|
+
end
|
1162
|
+
else
|
1163
|
+
visit body
|
1164
|
+
end
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
def visit_def(node)
|
1168
|
+
# [:def,
|
1169
|
+
# [:@ident, "foo", [1, 6]],
|
1170
|
+
# [:params, nil, nil, nil, nil, nil, nil, nil],
|
1171
|
+
# [:bodystmt, [[:void_stmt]], nil, nil, nil]]
|
1172
|
+
_, name, params, body = node
|
1173
|
+
|
1174
|
+
consume_keyword "def"
|
1175
|
+
consume_space
|
1176
|
+
|
1177
|
+
push_hash(node) do
|
1178
|
+
visit_def_from_name name, params, body
|
1179
|
+
end
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
def visit_def_with_receiver(node)
|
1183
|
+
# [:defs,
|
1184
|
+
# [:vcall, [:@ident, "foo", [1, 5]]],
|
1185
|
+
# [:@period, ".", [1, 8]],
|
1186
|
+
# [:@ident, "bar", [1, 9]],
|
1187
|
+
# [:params, nil, nil, nil, nil, nil, nil, nil],
|
1188
|
+
# [:bodystmt, [[:void_stmt]], nil, nil, nil]]
|
1189
|
+
_, receiver, period, name, params, body = node
|
1190
|
+
|
1191
|
+
consume_keyword "def"
|
1192
|
+
consume_space
|
1193
|
+
visit receiver
|
1194
|
+
skip_space_or_newline
|
1195
|
+
|
1196
|
+
check :on_period
|
1197
|
+
write "."
|
1198
|
+
next_token
|
1199
|
+
skip_space_or_newline
|
1200
|
+
|
1201
|
+
push_hash(node) do
|
1202
|
+
visit_def_from_name name, params, body
|
1203
|
+
end
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
def visit_def_from_name(name, params, body)
|
1207
|
+
visit name
|
1208
|
+
|
1209
|
+
if params[0] == :paren
|
1210
|
+
params = params[1]
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
skip_space
|
1214
|
+
if current_token_kind == :on_lparen
|
1215
|
+
next_token
|
1216
|
+
skip_space
|
1217
|
+
skip_semicolons
|
1218
|
+
|
1219
|
+
if empty_params?(params)
|
1220
|
+
skip_space_or_newline
|
1221
|
+
check :on_rparen
|
1222
|
+
next_token
|
1223
|
+
skip_space_or_newline
|
1224
|
+
else
|
1225
|
+
write "("
|
1226
|
+
|
1227
|
+
if newline? || comment?
|
1228
|
+
column = @column
|
1229
|
+
indent(column) do
|
1230
|
+
consume_end_of_line
|
1231
|
+
write_indent
|
1232
|
+
visit params
|
1233
|
+
end
|
1234
|
+
else
|
1235
|
+
indent(@column) do
|
1236
|
+
visit params
|
1237
|
+
end
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
skip_space_or_newline
|
1241
|
+
check :on_rparen
|
1242
|
+
write ")"
|
1243
|
+
next_token
|
1244
|
+
end
|
1245
|
+
elsif !empty_params?(params)
|
1246
|
+
write "("
|
1247
|
+
visit params
|
1248
|
+
write ")"
|
1249
|
+
skip_space_or_newline
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
visit body
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
def empty_params?(node)
|
1256
|
+
_, a, b, c, d, e, f, g = node
|
1257
|
+
!a && !b && !c && !d && !e && !f && !g
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
def visit_paren(node)
|
1261
|
+
# ( exps )
|
1262
|
+
#
|
1263
|
+
# [:paren, exps]
|
1264
|
+
check :on_lparen
|
1265
|
+
write "("
|
1266
|
+
next_token
|
1267
|
+
skip_space_or_newline
|
1268
|
+
|
1269
|
+
if node[1][0].is_a?(Symbol)
|
1270
|
+
visit node[1]
|
1271
|
+
else
|
1272
|
+
visit_exps node[1], false, false
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
skip_space_or_newline
|
1276
|
+
check :on_rparen
|
1277
|
+
write ")"
|
1278
|
+
next_token
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
def visit_params(node)
|
1282
|
+
# (def params)
|
1283
|
+
#
|
1284
|
+
# [:params, pre_rest_params, args_with_default, rest_param, post_rest_params, label_params, double_star_param, blockarg]
|
1285
|
+
_, pre_rest_params, args_with_default, rest_param, post_rest_params, label_params, double_star_param, blockarg = node
|
1286
|
+
|
1287
|
+
needs_comma = false
|
1288
|
+
|
1289
|
+
if pre_rest_params
|
1290
|
+
visit_comma_separated_list pre_rest_params
|
1291
|
+
needs_comma = true
|
1292
|
+
end
|
1293
|
+
|
1294
|
+
if args_with_default
|
1295
|
+
write_params_comma if needs_comma
|
1296
|
+
visit_comma_separated_list(args_with_default) do |arg, default|
|
1297
|
+
visit arg
|
1298
|
+
skip_space
|
1299
|
+
write_space " "
|
1300
|
+
consume_op "="
|
1301
|
+
skip_space_or_newline
|
1302
|
+
write_space " "
|
1303
|
+
visit default
|
1304
|
+
end
|
1305
|
+
needs_comma = true
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
if rest_param
|
1309
|
+
# [:rest_param, [:@ident, "x", [1, 15]]]
|
1310
|
+
write_params_comma if needs_comma
|
1311
|
+
consume_op "*"
|
1312
|
+
skip_space_or_newline
|
1313
|
+
visit rest_param[1]
|
1314
|
+
needs_comma = true
|
1315
|
+
end
|
1316
|
+
|
1317
|
+
if post_rest_params
|
1318
|
+
write_params_comma if needs_comma
|
1319
|
+
visit_comma_separated_list post_rest_params
|
1320
|
+
needs_comma = true
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
if label_params
|
1324
|
+
# [[label, value], ...]
|
1325
|
+
write_params_comma if needs_comma
|
1326
|
+
visit_comma_separated_list(label_params) do |label, value|
|
1327
|
+
# [:@label, "b:", [1, 20]]
|
1328
|
+
write label[1]
|
1329
|
+
next_token
|
1330
|
+
skip_space_or_newline
|
1331
|
+
if value
|
1332
|
+
consume_space
|
1333
|
+
track_hash_key
|
1334
|
+
visit value
|
1335
|
+
end
|
1336
|
+
end
|
1337
|
+
needs_comma = true
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
if double_star_param
|
1341
|
+
write_params_comma if needs_comma
|
1342
|
+
consume_op "**"
|
1343
|
+
skip_space_or_newline
|
1344
|
+
visit double_star_param
|
1345
|
+
skip_space_or_newline
|
1346
|
+
needs_comma = true
|
1347
|
+
end
|
1348
|
+
|
1349
|
+
if blockarg
|
1350
|
+
# [:blockarg, [:@ident, "block", [1, 16]]]
|
1351
|
+
write_params_comma if needs_comma
|
1352
|
+
skip_space_or_newline
|
1353
|
+
consume_op "&"
|
1354
|
+
skip_space_or_newline
|
1355
|
+
visit blockarg[1]
|
1356
|
+
end
|
1357
|
+
end
|
1358
|
+
|
1359
|
+
def write_params_comma
|
1360
|
+
skip_space
|
1361
|
+
check :on_comma
|
1362
|
+
write ","
|
1363
|
+
next_token
|
1364
|
+
skip_space
|
1365
|
+
|
1366
|
+
if newline? || comment?
|
1367
|
+
consume_end_of_line
|
1368
|
+
write_indent
|
1369
|
+
else
|
1370
|
+
write_space " "
|
1371
|
+
skip_space_or_newline
|
1372
|
+
end
|
1373
|
+
end
|
1374
|
+
|
1375
|
+
def visit_array(node)
|
1376
|
+
# [:array, elements]
|
1377
|
+
|
1378
|
+
# Check if it's `%w(...)` or `%i(...)`
|
1379
|
+
if current_token_kind == :on_qwords_beg || current_token_kind == :on_qsymbols_beg
|
1380
|
+
visit_q_or_i_array(node)
|
1381
|
+
return
|
1382
|
+
end
|
1383
|
+
|
1384
|
+
_, elements = node
|
1385
|
+
|
1386
|
+
check :on_lbracket
|
1387
|
+
write "["
|
1388
|
+
next_token
|
1389
|
+
|
1390
|
+
if elements
|
1391
|
+
if elements[0].is_a?(Symbol)
|
1392
|
+
visit elements
|
1393
|
+
skip_space_or_newline
|
1394
|
+
else
|
1395
|
+
visit_literal_elements elements
|
1396
|
+
end
|
1397
|
+
else
|
1398
|
+
skip_space_or_newline
|
1399
|
+
end
|
1400
|
+
|
1401
|
+
check :on_rbracket
|
1402
|
+
write "]"
|
1403
|
+
next_token
|
1404
|
+
end
|
1405
|
+
|
1406
|
+
def visit_q_or_i_array(node)
|
1407
|
+
_, elements = node
|
1408
|
+
|
1409
|
+
write current_token_value.strip
|
1410
|
+
|
1411
|
+
# If there's a newline after `%w(`, write line and indent
|
1412
|
+
if current_token_value.include?("\n") && elements
|
1413
|
+
write_line
|
1414
|
+
write_indent(next_indent)
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
next_token
|
1418
|
+
|
1419
|
+
if elements
|
1420
|
+
elements.each_with_index do |elem, i|
|
1421
|
+
# elem is [:@tstring_content, string, [1, 5]
|
1422
|
+
write elem[1].strip
|
1423
|
+
next_token
|
1424
|
+
unless last?(i, elements)
|
1425
|
+
check :on_words_sep
|
1426
|
+
|
1427
|
+
# On a newline, write line and indent
|
1428
|
+
if current_token_value.include?("\n")
|
1429
|
+
next_token
|
1430
|
+
write_line
|
1431
|
+
write_indent(next_indent)
|
1432
|
+
else
|
1433
|
+
next_token
|
1434
|
+
write_space " "
|
1435
|
+
end
|
1436
|
+
end
|
1437
|
+
end
|
1438
|
+
end
|
1439
|
+
|
1440
|
+
has_newline = false
|
1441
|
+
|
1442
|
+
while current_token_kind == :on_words_sep
|
1443
|
+
has_newline ||= current_token_value.include?("\n")
|
1444
|
+
next_token
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
if has_newline
|
1448
|
+
write_line
|
1449
|
+
write_indent(next_indent)
|
1450
|
+
end
|
1451
|
+
|
1452
|
+
write ")"
|
1453
|
+
return
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
def visit_hash(node)
|
1457
|
+
# [:hash, elements]
|
1458
|
+
_, elements = node
|
1459
|
+
|
1460
|
+
check :on_lbrace
|
1461
|
+
write "{"
|
1462
|
+
next_token
|
1463
|
+
|
1464
|
+
if elements
|
1465
|
+
# [:assoclist_from_args, elements]
|
1466
|
+
push_hash(node) do
|
1467
|
+
visit_literal_elements(elements[1])
|
1468
|
+
end
|
1469
|
+
else
|
1470
|
+
skip_space_or_newline
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
check :on_rbrace
|
1474
|
+
write "}"
|
1475
|
+
next_token
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
def visit_hash_key_value(node)
|
1479
|
+
# key => value
|
1480
|
+
#
|
1481
|
+
# [:assoc_new, key, value]
|
1482
|
+
_, key, value = node
|
1483
|
+
|
1484
|
+
visit key
|
1485
|
+
|
1486
|
+
skip_space_or_newline
|
1487
|
+
consume_space
|
1488
|
+
|
1489
|
+
track_hash_key
|
1490
|
+
|
1491
|
+
# Don't output `=>` for keys that are `label: value`
|
1492
|
+
unless key[0] == :@label
|
1493
|
+
consume_op "=>"
|
1494
|
+
skip_space_or_newline
|
1495
|
+
write_space " "
|
1496
|
+
end
|
1497
|
+
|
1498
|
+
visit value
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
def visit_splat_inside_hash(node)
|
1502
|
+
# **exp
|
1503
|
+
#
|
1504
|
+
# [:assoc_splat, exp]
|
1505
|
+
consume_op "**"
|
1506
|
+
skip_space_or_newline
|
1507
|
+
visit node[1]
|
1508
|
+
end
|
1509
|
+
|
1510
|
+
def visit_range(node, inclusive)
|
1511
|
+
# [:dot2, left, right]
|
1512
|
+
_, left, right = node
|
1513
|
+
|
1514
|
+
visit left
|
1515
|
+
skip_space_or_newline
|
1516
|
+
consume_op(inclusive ? ".." : "...")
|
1517
|
+
skip_space_or_newline
|
1518
|
+
visit right
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
def visit_regexp_literal(node)
|
1522
|
+
# [:regexp_literal, pieces, [:@regexp_end, "/", [1, 1]]]
|
1523
|
+
_, pieces = node
|
1524
|
+
|
1525
|
+
check :on_regexp_beg
|
1526
|
+
write current_token_value
|
1527
|
+
next_token
|
1528
|
+
|
1529
|
+
visit_exps pieces, false, false
|
1530
|
+
|
1531
|
+
check :on_regexp_end
|
1532
|
+
write current_token_value
|
1533
|
+
next_token
|
1534
|
+
end
|
1535
|
+
|
1536
|
+
def visit_array_access(node)
|
1537
|
+
# exp[arg1, ..., argN]
|
1538
|
+
#
|
1539
|
+
# [:aref, name, args]
|
1540
|
+
_, name, args = node
|
1541
|
+
|
1542
|
+
visit_array_getter_or_setter name, args
|
1543
|
+
end
|
1544
|
+
|
1545
|
+
def visit_array_setter(node)
|
1546
|
+
# exp[arg1, ..., argN]
|
1547
|
+
# (followed by `=`, though not included in this node)
|
1548
|
+
#
|
1549
|
+
# [:aref_field, name, args]
|
1550
|
+
_, name, args = node
|
1551
|
+
|
1552
|
+
visit_array_getter_or_setter name, args
|
1553
|
+
end
|
1554
|
+
|
1555
|
+
def visit_array_getter_or_setter(name, args)
|
1556
|
+
visit name
|
1557
|
+
|
1558
|
+
check :on_lbracket
|
1559
|
+
write "["
|
1560
|
+
next_token
|
1561
|
+
|
1562
|
+
column = @column
|
1563
|
+
|
1564
|
+
skip_space
|
1565
|
+
|
1566
|
+
if newline? || comment?
|
1567
|
+
needed_indent = next_indent
|
1568
|
+
if args
|
1569
|
+
consume_end_of_line
|
1570
|
+
write_indent(needed_indent)
|
1571
|
+
else
|
1572
|
+
skip_space_or_newline
|
1573
|
+
end
|
1574
|
+
else
|
1575
|
+
needed_indent = column
|
1576
|
+
end
|
1577
|
+
|
1578
|
+
if args
|
1579
|
+
indent(needed_indent) do
|
1580
|
+
visit args
|
1581
|
+
end
|
1582
|
+
end
|
1583
|
+
|
1584
|
+
skip_space_or_newline
|
1585
|
+
|
1586
|
+
check :on_rbracket
|
1587
|
+
write "]"
|
1588
|
+
next_token
|
1589
|
+
end
|
1590
|
+
|
1591
|
+
def visit_sclass(node)
|
1592
|
+
# class << self
|
1593
|
+
#
|
1594
|
+
# [:sclass, target, body]
|
1595
|
+
_, target, body = node
|
1596
|
+
|
1597
|
+
consume_keyword "class"
|
1598
|
+
consume_space
|
1599
|
+
consume_op "<<"
|
1600
|
+
consume_space
|
1601
|
+
visit target
|
1602
|
+
visit body
|
1603
|
+
end
|
1604
|
+
|
1605
|
+
def visit_setter(node)
|
1606
|
+
# foo.bar
|
1607
|
+
# (followed by `=`, though not included in this node)
|
1608
|
+
#
|
1609
|
+
# [:field, receiver, :".", name]
|
1610
|
+
_, receiver, dot, name = node
|
1611
|
+
|
1612
|
+
@dot_column = nil
|
1613
|
+
visit receiver
|
1614
|
+
skip_space
|
1615
|
+
|
1616
|
+
if newline? || comment?
|
1617
|
+
consume_end_of_line
|
1618
|
+
|
1619
|
+
write_indent(@dot_column || next_indent)
|
1620
|
+
end
|
1621
|
+
|
1622
|
+
# Remember dot column
|
1623
|
+
dot_column = @column
|
1624
|
+
check :on_period
|
1625
|
+
write "."
|
1626
|
+
next_token
|
1627
|
+
skip_space
|
1628
|
+
|
1629
|
+
if newline? || comment?
|
1630
|
+
consume_end_of_line
|
1631
|
+
write_indent(next_indent)
|
1632
|
+
else
|
1633
|
+
skip_space_or_newline
|
1634
|
+
end
|
1635
|
+
|
1636
|
+
visit name
|
1637
|
+
|
1638
|
+
# Only set it after we visit the call after the dot,
|
1639
|
+
# so we remember the outmost dot position
|
1640
|
+
@dot_column = dot_column
|
1641
|
+
end
|
1642
|
+
|
1643
|
+
def visit_return(node)
|
1644
|
+
# [:return, exp]
|
1645
|
+
visit_control_keyword node, "return"
|
1646
|
+
end
|
1647
|
+
|
1648
|
+
def visit_break(node)
|
1649
|
+
# [:break, exp]
|
1650
|
+
visit_control_keyword node, "break"
|
1651
|
+
end
|
1652
|
+
|
1653
|
+
def visit_next(node)
|
1654
|
+
# [:next, exp]
|
1655
|
+
visit_control_keyword node, "next"
|
1656
|
+
end
|
1657
|
+
|
1658
|
+
def visit_yield(node)
|
1659
|
+
# [:yield, exp]
|
1660
|
+
visit_control_keyword node, "yield"
|
1661
|
+
end
|
1662
|
+
|
1663
|
+
def visit_control_keyword(node, keyword)
|
1664
|
+
_, exp = node
|
1665
|
+
|
1666
|
+
consume_keyword keyword
|
1667
|
+
|
1668
|
+
if exp && !exp.empty?
|
1669
|
+
consume_space if space?
|
1670
|
+
|
1671
|
+
indent(@column) do
|
1672
|
+
visit node[1]
|
1673
|
+
end
|
1674
|
+
end
|
1675
|
+
end
|
1676
|
+
|
1677
|
+
def visit_lambda(node)
|
1678
|
+
# [:lambda, [:params, nil, nil, nil, nil, nil, nil, nil], [[:void_stmt]]]
|
1679
|
+
_, params, body = node
|
1680
|
+
|
1681
|
+
check :on_tlambda
|
1682
|
+
write "->"
|
1683
|
+
next_token
|
1684
|
+
skip_space_or_newline
|
1685
|
+
|
1686
|
+
if empty_params?(params)
|
1687
|
+
if current_token_kind == :on_lparen
|
1688
|
+
next_token
|
1689
|
+
skip_space_or_newline
|
1690
|
+
check :on_rparen
|
1691
|
+
next_token
|
1692
|
+
skip_space_or_newline
|
1693
|
+
end
|
1694
|
+
else
|
1695
|
+
visit params
|
1696
|
+
consume_space
|
1697
|
+
end
|
1698
|
+
|
1699
|
+
if void_exps?(body)
|
1700
|
+
consume_token :on_tlambeg
|
1701
|
+
consume_space
|
1702
|
+
consume_token :on_rbrace
|
1703
|
+
return
|
1704
|
+
end
|
1705
|
+
|
1706
|
+
brace = current_token_value == "{"
|
1707
|
+
|
1708
|
+
if brace
|
1709
|
+
closing_brace_token = find_closing_brace_token
|
1710
|
+
|
1711
|
+
# Check if the whole block fits into a single line
|
1712
|
+
if current_token[0][0] == closing_brace_token[0][0]
|
1713
|
+
consume_token :on_tlambeg
|
1714
|
+
|
1715
|
+
consume_space
|
1716
|
+
visit_exps body, false, false
|
1717
|
+
consume_space
|
1718
|
+
|
1719
|
+
consume_token :on_rbrace
|
1720
|
+
return
|
1721
|
+
end
|
1722
|
+
|
1723
|
+
consume_token :on_tlambeg
|
1724
|
+
else
|
1725
|
+
consume_keyword "do"
|
1726
|
+
end
|
1727
|
+
|
1728
|
+
indent_body body
|
1729
|
+
|
1730
|
+
write_indent
|
1731
|
+
|
1732
|
+
if brace
|
1733
|
+
consume_token :on_rbrace
|
1734
|
+
else
|
1735
|
+
consume_keyword "end"
|
1736
|
+
end
|
1737
|
+
end
|
1738
|
+
|
1739
|
+
def visit_super(node)
|
1740
|
+
# [:super, args]
|
1741
|
+
_, args = node
|
1742
|
+
|
1743
|
+
consume_keyword "super"
|
1744
|
+
|
1745
|
+
if space?
|
1746
|
+
consume_space
|
1747
|
+
visit_command_end node, args
|
1748
|
+
else
|
1749
|
+
visit_call_at_paren node, args
|
1750
|
+
end
|
1751
|
+
end
|
1752
|
+
|
1753
|
+
def visit_literal_elements(elements)
|
1754
|
+
base_column = @column
|
1755
|
+
|
1756
|
+
skip_space
|
1757
|
+
|
1758
|
+
# If there's a newline right at the beginning,
|
1759
|
+
# write it, and we'll indent element and always
|
1760
|
+
# add a trailing comma to the last element
|
1761
|
+
needs_trailing_comma = newline? || comment?
|
1762
|
+
if needs_trailing_comma
|
1763
|
+
needed_indent = next_indent
|
1764
|
+
indent { consume_end_of_line }
|
1765
|
+
write_indent(needed_indent)
|
1766
|
+
else
|
1767
|
+
needed_indent = base_column
|
1768
|
+
end
|
1769
|
+
|
1770
|
+
elements.each_with_index do |elem, i|
|
1771
|
+
if needs_trailing_comma
|
1772
|
+
indent(needed_indent) { visit elem }
|
1773
|
+
else
|
1774
|
+
visit elem
|
1775
|
+
end
|
1776
|
+
skip_space
|
1777
|
+
|
1778
|
+
if comma?
|
1779
|
+
is_last = last?(i, elements)
|
1780
|
+
|
1781
|
+
write "," unless is_last
|
1782
|
+
next_token
|
1783
|
+
skip_space
|
1784
|
+
|
1785
|
+
if newline? || comment?
|
1786
|
+
if is_last
|
1787
|
+
# Nothing
|
1788
|
+
else
|
1789
|
+
consume_end_of_line
|
1790
|
+
write_indent(needed_indent)
|
1791
|
+
end
|
1792
|
+
else
|
1793
|
+
write_space " " unless is_last
|
1794
|
+
end
|
1795
|
+
end
|
1796
|
+
end
|
1797
|
+
|
1798
|
+
if needs_trailing_comma
|
1799
|
+
write ","
|
1800
|
+
consume_end_of_line
|
1801
|
+
write_indent
|
1802
|
+
elsif comment?
|
1803
|
+
consume_end_of_line
|
1804
|
+
else
|
1805
|
+
skip_space_or_newline
|
1806
|
+
end
|
1807
|
+
end
|
1808
|
+
|
1809
|
+
def visit_if(node)
|
1810
|
+
visit_if_or_unless node, "if"
|
1811
|
+
end
|
1812
|
+
|
1813
|
+
def visit_unless(node)
|
1814
|
+
visit_if_or_unless node, "unless"
|
1815
|
+
end
|
1816
|
+
|
1817
|
+
def visit_if_or_unless(node, keyword, check_end = true)
|
1818
|
+
# if cond
|
1819
|
+
# then_body
|
1820
|
+
# else
|
1821
|
+
# else_body
|
1822
|
+
# end
|
1823
|
+
#
|
1824
|
+
# [:if, cond, then, else]
|
1825
|
+
consume_keyword(keyword)
|
1826
|
+
consume_space
|
1827
|
+
visit node[1]
|
1828
|
+
skip_space
|
1829
|
+
|
1830
|
+
# Remove "then"
|
1831
|
+
if keyword?("then")
|
1832
|
+
next_token
|
1833
|
+
skip_space
|
1834
|
+
end
|
1835
|
+
|
1836
|
+
indent_body node[2]
|
1837
|
+
if else_body = node[3]
|
1838
|
+
# [:else, else_contents]
|
1839
|
+
# [:elsif, cond, then, else]
|
1840
|
+
write_indent
|
1841
|
+
|
1842
|
+
case else_body[0]
|
1843
|
+
when :else
|
1844
|
+
consume_keyword "else"
|
1845
|
+
indent_body else_body[1]
|
1846
|
+
when :elsif
|
1847
|
+
visit_if_or_unless else_body, "elsif", false
|
1848
|
+
else
|
1849
|
+
bug "expected else or elsif, not #{else_body[0]}"
|
1850
|
+
end
|
1851
|
+
end
|
1852
|
+
|
1853
|
+
if check_end
|
1854
|
+
write_indent
|
1855
|
+
consume_keyword "end"
|
1856
|
+
end
|
1857
|
+
end
|
1858
|
+
|
1859
|
+
def visit_while(node)
|
1860
|
+
# [:while, cond, body]
|
1861
|
+
visit_while_or_until node, "while"
|
1862
|
+
end
|
1863
|
+
|
1864
|
+
def visit_until(node)
|
1865
|
+
# [:until, cond, body]
|
1866
|
+
visit_while_or_until node, "until"
|
1867
|
+
end
|
1868
|
+
|
1869
|
+
def visit_while_or_until(node, keyword)
|
1870
|
+
_, cond, body = node
|
1871
|
+
|
1872
|
+
consume_keyword keyword
|
1873
|
+
consume_space
|
1874
|
+
|
1875
|
+
visit cond
|
1876
|
+
|
1877
|
+
skip_space
|
1878
|
+
|
1879
|
+
# Keep `while cond; end` as is
|
1880
|
+
if semicolon? && void_exps?(body)
|
1881
|
+
next_token
|
1882
|
+
skip_space
|
1883
|
+
|
1884
|
+
if keyword?("end")
|
1885
|
+
write "; end"
|
1886
|
+
next_token
|
1887
|
+
return
|
1888
|
+
end
|
1889
|
+
end
|
1890
|
+
|
1891
|
+
indent_body body
|
1892
|
+
|
1893
|
+
write_indent
|
1894
|
+
consume_keyword "end"
|
1895
|
+
end
|
1896
|
+
|
1897
|
+
def visit_case(node)
|
1898
|
+
# [:case, cond, case_when]
|
1899
|
+
_, cond, case_when = node
|
1900
|
+
|
1901
|
+
consume_keyword "case"
|
1902
|
+
|
1903
|
+
if cond
|
1904
|
+
consume_space
|
1905
|
+
visit cond
|
1906
|
+
end
|
1907
|
+
|
1908
|
+
consume_end_of_line
|
1909
|
+
|
1910
|
+
write_indent
|
1911
|
+
visit case_when
|
1912
|
+
|
1913
|
+
write_indent
|
1914
|
+
consume_keyword "end"
|
1915
|
+
end
|
1916
|
+
|
1917
|
+
def visit_when(node)
|
1918
|
+
# [:when, conds, body, next_exp]
|
1919
|
+
_, conds, body, next_exp = node
|
1920
|
+
|
1921
|
+
consume_keyword "when"
|
1922
|
+
consume_space
|
1923
|
+
|
1924
|
+
indent(@column) do
|
1925
|
+
visit_comma_separated_list conds
|
1926
|
+
end
|
1927
|
+
|
1928
|
+
then_keyword = keyword?("then")
|
1929
|
+
inline = then_keyword || semicolon?
|
1930
|
+
if then_keyword
|
1931
|
+
next_token
|
1932
|
+
skip_space
|
1933
|
+
skip_semicolons
|
1934
|
+
|
1935
|
+
if newline? || comment?
|
1936
|
+
inline = false
|
1937
|
+
else
|
1938
|
+
write " then "
|
1939
|
+
end
|
1940
|
+
elsif semicolon?
|
1941
|
+
skip_semicolons
|
1942
|
+
|
1943
|
+
if newline? || comment?
|
1944
|
+
inline = false
|
1945
|
+
else
|
1946
|
+
write "; "
|
1947
|
+
end
|
1948
|
+
end
|
1949
|
+
|
1950
|
+
if inline
|
1951
|
+
indent do
|
1952
|
+
visit_exps body
|
1953
|
+
end
|
1954
|
+
else
|
1955
|
+
indent_body body
|
1956
|
+
end
|
1957
|
+
|
1958
|
+
if next_exp
|
1959
|
+
write_indent
|
1960
|
+
|
1961
|
+
if next_exp[0] == :else
|
1962
|
+
# [:else, body]
|
1963
|
+
consume_keyword "else"
|
1964
|
+
skip_space
|
1965
|
+
|
1966
|
+
if newline? || semicolon? || comment?
|
1967
|
+
indent_body next_exp[1]
|
1968
|
+
else
|
1969
|
+
write_space " "
|
1970
|
+
visit_exps next_exp[1]
|
1971
|
+
end
|
1972
|
+
else
|
1973
|
+
visit next_exp
|
1974
|
+
end
|
1975
|
+
end
|
1976
|
+
end
|
1977
|
+
|
1978
|
+
def consume_space
|
1979
|
+
skip_space_or_newline
|
1980
|
+
write_space " "
|
1981
|
+
end
|
1982
|
+
|
1983
|
+
def skip_space
|
1984
|
+
while space?
|
1985
|
+
next_token
|
1986
|
+
end
|
1987
|
+
end
|
1988
|
+
|
1989
|
+
def skip_space_or_newline(want_semicolon = false)
|
1990
|
+
found_newline = false
|
1991
|
+
found_comment = false
|
1992
|
+
last = nil
|
1993
|
+
|
1994
|
+
while true
|
1995
|
+
case current_token_kind
|
1996
|
+
when :on_sp
|
1997
|
+
next_token
|
1998
|
+
when :on_nl, :on_ignored_nl
|
1999
|
+
next_token
|
2000
|
+
last = :newline
|
2001
|
+
found_newline = true
|
2002
|
+
when :on_semicolon
|
2003
|
+
if !found_newline && !found_comment
|
2004
|
+
write "; "
|
2005
|
+
end
|
2006
|
+
next_token
|
2007
|
+
last = :semicolon
|
2008
|
+
when :on_comment
|
2009
|
+
write_line if last == :newline
|
2010
|
+
|
2011
|
+
write_indent if found_comment
|
2012
|
+
if current_token_value.end_with?("\n")
|
2013
|
+
write current_token_value.rstrip
|
2014
|
+
write_line
|
2015
|
+
else
|
2016
|
+
write current_token_value
|
2017
|
+
end
|
2018
|
+
next_token
|
2019
|
+
found_comment = true
|
2020
|
+
last = :comment
|
2021
|
+
else
|
2022
|
+
break
|
2023
|
+
end
|
2024
|
+
end
|
2025
|
+
end
|
2026
|
+
|
2027
|
+
def skip_semicolons
|
2028
|
+
while semicolon? || space?
|
2029
|
+
next_token
|
2030
|
+
end
|
2031
|
+
end
|
2032
|
+
|
2033
|
+
def empty_body?(body)
|
2034
|
+
body[0] == :bodystmt &&
|
2035
|
+
body[1].size == 1 &&
|
2036
|
+
body[1][0][0] == :void_stmt
|
2037
|
+
end
|
2038
|
+
|
2039
|
+
def consume_token(kind)
|
2040
|
+
check kind
|
2041
|
+
consume_token_value(current_token_value)
|
2042
|
+
next_token
|
2043
|
+
end
|
2044
|
+
|
2045
|
+
def consume_token_value(value)
|
2046
|
+
write value
|
2047
|
+
|
2048
|
+
# If the value has newlines, we need to adjust line and column
|
2049
|
+
number_of_lines = value.count("\n")
|
2050
|
+
if number_of_lines > 0
|
2051
|
+
@line += number_of_lines
|
2052
|
+
last_line_index = value.rindex("\n")
|
2053
|
+
@column = value.size - (last_line_index + 1)
|
2054
|
+
@last_was_newline = @column == 0
|
2055
|
+
end
|
2056
|
+
end
|
2057
|
+
|
2058
|
+
def consume_keyword(value)
|
2059
|
+
check :on_kw
|
2060
|
+
if current_token_value != value
|
2061
|
+
bug "Expected keyword #{value}, not #{current_token_value}"
|
2062
|
+
end
|
2063
|
+
write value
|
2064
|
+
next_token
|
2065
|
+
end
|
2066
|
+
|
2067
|
+
def consume_op(value)
|
2068
|
+
check :on_op
|
2069
|
+
if current_token_value != value
|
2070
|
+
bug "Expected op #{value}, not #{current_token_value}"
|
2071
|
+
end
|
2072
|
+
write value
|
2073
|
+
next_token
|
2074
|
+
end
|
2075
|
+
|
2076
|
+
# Consume and print an end of line, handling semicolons and comments
|
2077
|
+
#
|
2078
|
+
# - at_prefix: are we at a point before an expression? (if so, we don't need a space before the first comment)
|
2079
|
+
# - want_semicolon: do we want do print a semicolon to separate expressions?
|
2080
|
+
# - want_multiline: do we want multiple lines to appear, or at most one?
|
2081
|
+
def consume_end_of_line(at_prefix = false, want_semicolon = false, want_multiline = true)
|
2082
|
+
found_newline = false # Did we find any newline during this method?
|
2083
|
+
last = nil # Last token kind found
|
2084
|
+
multilple_lines = false # Did we pass through more than one newline?
|
2085
|
+
last_comment_has_newline = false # Does the last comment has a newline?
|
2086
|
+
|
2087
|
+
while true
|
2088
|
+
case current_token_kind
|
2089
|
+
when :on_sp
|
2090
|
+
# Ignore spaces
|
2091
|
+
next_token
|
2092
|
+
when :on_nl, :on_ignored_nl
|
2093
|
+
if last == :newline
|
2094
|
+
# If we pass through consecutive newlines, don't print them
|
2095
|
+
# yet, but remember this fact
|
2096
|
+
multilple_lines = true unless last_comment_has_newline
|
2097
|
+
else
|
2098
|
+
# If we just printed a comment that had a newline,
|
2099
|
+
# we must print two newlines because we remove newlines from comments (rstrip call)
|
2100
|
+
if last == :comment && last_comment_has_newline
|
2101
|
+
write_line
|
2102
|
+
multilple_lines = true
|
2103
|
+
else
|
2104
|
+
write_line
|
2105
|
+
multilple_lines = false
|
2106
|
+
end
|
2107
|
+
end
|
2108
|
+
found_newline = true
|
2109
|
+
next_token
|
2110
|
+
last = :newline
|
2111
|
+
when :on_semicolon
|
2112
|
+
next_token
|
2113
|
+
# If we want to print semicolons and we didn't find a newline yet,
|
2114
|
+
# print it, but only if it's not followed by a newline
|
2115
|
+
if !found_newline && want_semicolon && last != :semicolon
|
2116
|
+
skip_space
|
2117
|
+
case current_token_kind
|
2118
|
+
when :on_ignored_nl, :on_eof
|
2119
|
+
else
|
2120
|
+
write "; "
|
2121
|
+
last = :semicolon
|
2122
|
+
end
|
2123
|
+
end
|
2124
|
+
multilple_lines = false
|
2125
|
+
when :on_comment
|
2126
|
+
if last == :comment
|
2127
|
+
# Since we remove newlines from comments, we must add the last
|
2128
|
+
# one if it was a comment
|
2129
|
+
write_line
|
2130
|
+
write_indent
|
2131
|
+
else
|
2132
|
+
if found_newline
|
2133
|
+
# Write line or second line if needed
|
2134
|
+
write_line if last != :newline || multilple_lines
|
2135
|
+
write_indent
|
2136
|
+
else
|
2137
|
+
# If we didn't find any newline yet, this is the first comment,
|
2138
|
+
# so append a space if needed (for example after an expression)
|
2139
|
+
write_space " " unless at_prefix
|
2140
|
+
@comments_positions << [@line, @column, @indent, nil]
|
2141
|
+
end
|
2142
|
+
end
|
2143
|
+
last_comment_has_newline = current_token_value.end_with?("\n")
|
2144
|
+
write current_token_value.rstrip
|
2145
|
+
next_token
|
2146
|
+
last = :comment
|
2147
|
+
multilple_lines = false
|
2148
|
+
else
|
2149
|
+
break
|
2150
|
+
end
|
2151
|
+
end
|
2152
|
+
|
2153
|
+
# Output a newline if we didn't do so yet:
|
2154
|
+
# either we didn't find a newline and we are at the end of a line (and we didn't just pass a semicolon),
|
2155
|
+
# or the last thing was a comment (from which we removed the newline)
|
2156
|
+
# or we just passed multiple lines (but printed only one)
|
2157
|
+
if (!found_newline && !at_prefix && !(want_semicolon && last == :semicolon)) ||
|
2158
|
+
last == :comment ||
|
2159
|
+
(multilple_lines && want_multiline)
|
2160
|
+
write_line
|
2161
|
+
end
|
2162
|
+
end
|
2163
|
+
|
2164
|
+
def indent(value = nil)
|
2165
|
+
if value
|
2166
|
+
old_indent = @indent
|
2167
|
+
@indent = value
|
2168
|
+
yield
|
2169
|
+
@indent = old_indent
|
2170
|
+
else
|
2171
|
+
@indent += @indent_size
|
2172
|
+
yield
|
2173
|
+
@indent -= @indent_size
|
2174
|
+
end
|
2175
|
+
end
|
2176
|
+
|
2177
|
+
def indent_body(exps)
|
2178
|
+
indent do
|
2179
|
+
consume_end_of_line(false, false, false)
|
2180
|
+
end
|
2181
|
+
|
2182
|
+
# If the body is [[:void_stmt]] it's an empty body
|
2183
|
+
# so there's nothing to write
|
2184
|
+
if exps.size == 1 && exps[0][0] == :void_stmt
|
2185
|
+
skip_space_or_newline
|
2186
|
+
else
|
2187
|
+
indent do
|
2188
|
+
visit_exps exps, true
|
2189
|
+
end
|
2190
|
+
write_line unless @last_was_newline
|
2191
|
+
end
|
2192
|
+
end
|
2193
|
+
|
2194
|
+
def maybe_indent(toggle, indent_size)
|
2195
|
+
if toggle
|
2196
|
+
indent(indent_size) do
|
2197
|
+
yield
|
2198
|
+
end
|
2199
|
+
else
|
2200
|
+
yield
|
2201
|
+
end
|
2202
|
+
end
|
2203
|
+
|
2204
|
+
def write(value)
|
2205
|
+
@output << value
|
2206
|
+
@last_was_newline = false
|
2207
|
+
@column += value.size
|
2208
|
+
end
|
2209
|
+
|
2210
|
+
def write_space(value)
|
2211
|
+
@output << value
|
2212
|
+
@column += value.size
|
2213
|
+
end
|
2214
|
+
|
2215
|
+
def write_line
|
2216
|
+
@output << "\n"
|
2217
|
+
@last_was_newline = true
|
2218
|
+
@column = 0
|
2219
|
+
@line += 1
|
2220
|
+
end
|
2221
|
+
|
2222
|
+
def write_indent(indent = @indent)
|
2223
|
+
indent.times do
|
2224
|
+
@output << " "
|
2225
|
+
end
|
2226
|
+
@column += indent
|
2227
|
+
@last_was_newline = false
|
2228
|
+
end
|
2229
|
+
|
2230
|
+
def indent_after_space(node, sticky = false, want_space = true)
|
2231
|
+
skip_space
|
2232
|
+
case current_token_kind
|
2233
|
+
when :on_ignored_nl, :on_comment
|
2234
|
+
indent do
|
2235
|
+
consume_end_of_line
|
2236
|
+
write_indent
|
2237
|
+
visit node
|
2238
|
+
end
|
2239
|
+
else
|
2240
|
+
write_space " " if want_space
|
2241
|
+
if sticky
|
2242
|
+
indent(@column) do
|
2243
|
+
visit node
|
2244
|
+
end
|
2245
|
+
else
|
2246
|
+
visit node
|
2247
|
+
end
|
2248
|
+
end
|
2249
|
+
end
|
2250
|
+
|
2251
|
+
def next_indent
|
2252
|
+
@indent + @indent_size
|
2253
|
+
end
|
2254
|
+
|
2255
|
+
def check(kind)
|
2256
|
+
if current_token_kind != kind
|
2257
|
+
bug "Expected token #{kind}, not #{current_token_kind}"
|
2258
|
+
end
|
2259
|
+
end
|
2260
|
+
|
2261
|
+
def bug(msg)
|
2262
|
+
raise Rufo::Bug.new("#{msg} at #{current_token}")
|
2263
|
+
end
|
2264
|
+
|
2265
|
+
# [[1, 0], :on_int, "1"]
|
2266
|
+
def current_token
|
2267
|
+
@tokens.last
|
2268
|
+
end
|
2269
|
+
|
2270
|
+
def current_token_kind
|
2271
|
+
tok = current_token
|
2272
|
+
tok ? tok[1] : :on_eof
|
2273
|
+
end
|
2274
|
+
|
2275
|
+
def current_token_value
|
2276
|
+
tok = current_token
|
2277
|
+
tok ? tok[2] : ""
|
2278
|
+
end
|
2279
|
+
|
2280
|
+
def keyword?(kw)
|
2281
|
+
current_token_kind == :on_kw && current_token_value == kw
|
2282
|
+
end
|
2283
|
+
|
2284
|
+
def newline?
|
2285
|
+
current_token_kind == :on_nl || current_token_kind == :on_ignored_nl
|
2286
|
+
end
|
2287
|
+
|
2288
|
+
def comment?
|
2289
|
+
current_token_kind == :on_comment
|
2290
|
+
end
|
2291
|
+
|
2292
|
+
def semicolon?
|
2293
|
+
current_token_kind == :on_semicolon
|
2294
|
+
end
|
2295
|
+
|
2296
|
+
def comma?
|
2297
|
+
current_token_kind == :on_comma
|
2298
|
+
end
|
2299
|
+
|
2300
|
+
def space?
|
2301
|
+
current_token_kind == :on_sp
|
2302
|
+
end
|
2303
|
+
|
2304
|
+
def void_exps?(node)
|
2305
|
+
node.size == 1 && node[0].size == 1 && node[0][0] == :void_stmt
|
2306
|
+
end
|
2307
|
+
|
2308
|
+
def find_closing_brace_token
|
2309
|
+
count = 0
|
2310
|
+
@tokens.reverse_each do |token|
|
2311
|
+
(line, column), kind = token
|
2312
|
+
case kind
|
2313
|
+
when :on_lbrace, :on_tlambeg
|
2314
|
+
count += 1
|
2315
|
+
when :on_rbrace
|
2316
|
+
count -= 1
|
2317
|
+
return token if count == 0
|
2318
|
+
end
|
2319
|
+
end
|
2320
|
+
end
|
2321
|
+
|
2322
|
+
def next_token
|
2323
|
+
@tokens.pop
|
2324
|
+
end
|
2325
|
+
|
2326
|
+
def last?(i, array)
|
2327
|
+
i == array.size - 1
|
2328
|
+
end
|
2329
|
+
|
2330
|
+
def push_call(call)
|
2331
|
+
old_call = @current_call
|
2332
|
+
@current_call = call
|
2333
|
+
|
2334
|
+
# A call can specify hash arguments so it acts as a
|
2335
|
+
# hash for key alignment purposes
|
2336
|
+
push_hash(call) do
|
2337
|
+
yield
|
2338
|
+
end
|
2339
|
+
|
2340
|
+
@current_call = old_call
|
2341
|
+
end
|
2342
|
+
|
2343
|
+
def push_hash(node)
|
2344
|
+
old_hash = @current_hash
|
2345
|
+
@current_hash = node
|
2346
|
+
yield
|
2347
|
+
@current_hash = old_hash
|
2348
|
+
end
|
2349
|
+
|
2350
|
+
def do_align_comments
|
2351
|
+
do_align @comments_positions
|
2352
|
+
end
|
2353
|
+
|
2354
|
+
def do_align_assignments
|
2355
|
+
do_align @assignments_positions
|
2356
|
+
end
|
2357
|
+
|
2358
|
+
def do_align_hash_keys
|
2359
|
+
do_align @hash_keys_positions
|
2360
|
+
end
|
2361
|
+
|
2362
|
+
def do_align(elements)
|
2363
|
+
lines = @output.lines
|
2364
|
+
|
2365
|
+
elements.reject! { |l, c, indent, id, off, ignore| ignore == :ignore }
|
2366
|
+
|
2367
|
+
# Chunk comments that are in consecutive lines
|
2368
|
+
chunks = elements.chunk_while do |(l1, c1, i1, id1), (l2, c2, i2, id2)|
|
2369
|
+
l1 + 1 == l2 && i1 == i2 && id1 == id2
|
2370
|
+
end
|
2371
|
+
|
2372
|
+
chunks.each do |comments|
|
2373
|
+
next if comments.size == 1
|
2374
|
+
|
2375
|
+
max_column = comments.map { |l, c| c }.max
|
2376
|
+
comments.each do |(line, column, _, _, offset)|
|
2377
|
+
next if column == max_column
|
2378
|
+
|
2379
|
+
split_index = column
|
2380
|
+
split_index -= offset if offset
|
2381
|
+
|
2382
|
+
target_line = lines[line]
|
2383
|
+
|
2384
|
+
before = target_line[0...split_index]
|
2385
|
+
after = target_line[split_index..-1]
|
2386
|
+
|
2387
|
+
filler = " " * (max_column - column)
|
2388
|
+
lines[line] = "#{before}#{filler}#{after}"
|
2389
|
+
end
|
2390
|
+
end
|
2391
|
+
|
2392
|
+
@output = lines.join
|
198
2393
|
end
|
199
2394
|
|
200
2395
|
def result
|