syntax_tree 0.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/CHANGELOG.md +98 -1
- data/Gemfile.lock +5 -4
- data/README.md +8 -1
- data/exe/stree +4 -80
- data/lib/syntax_tree/cli.rb +244 -0
- data/lib/syntax_tree/prettyprint.rb +23 -11
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree.rb +1405 -590
- metadata +4 -3
data/lib/syntax_tree.rb
CHANGED
@@ -7,7 +7,7 @@ require "stringio"
|
|
7
7
|
|
8
8
|
require_relative "syntax_tree/version"
|
9
9
|
|
10
|
-
# If PrettyPrint::
|
10
|
+
# If PrettyPrint::Align isn't defined, then we haven't gotten the updated
|
11
11
|
# version of prettyprint. In that case we'll define our own. This is going to
|
12
12
|
# overwrite a bunch of methods, so silencing them as well.
|
13
13
|
unless PrettyPrint.const_defined?(:Align)
|
@@ -26,12 +26,14 @@ class SyntaxTree < Ripper
|
|
26
26
|
# every character in the string is 1 byte in length, so we can just return the
|
27
27
|
# start of the line + the index.
|
28
28
|
class SingleByteString
|
29
|
+
attr_reader :start
|
30
|
+
|
29
31
|
def initialize(start)
|
30
32
|
@start = start
|
31
33
|
end
|
32
34
|
|
33
35
|
def [](byteindex)
|
34
|
-
|
36
|
+
start + byteindex
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
@@ -40,7 +42,10 @@ class SyntaxTree < Ripper
|
|
40
42
|
# an array of indices, such that array[byteindex] will be equal to the index
|
41
43
|
# of the character within the string.
|
42
44
|
class MultiByteString
|
45
|
+
attr_reader :start, :indices
|
46
|
+
|
43
47
|
def initialize(start, line)
|
48
|
+
@start = start
|
44
49
|
@indices = []
|
45
50
|
|
46
51
|
line.each_char.with_index(start) do |char, index|
|
@@ -48,8 +53,11 @@ class SyntaxTree < Ripper
|
|
48
53
|
end
|
49
54
|
end
|
50
55
|
|
56
|
+
# Technically it's possible for the column index to be a negative value if
|
57
|
+
# there's a BOM at the beginning of the file, which is the reason we need to
|
58
|
+
# compare it to 0 here.
|
51
59
|
def [](byteindex)
|
52
|
-
|
60
|
+
indices[byteindex < 0 ? 0 : byteindex]
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
@@ -66,8 +74,7 @@ class SyntaxTree < Ripper
|
|
66
74
|
|
67
75
|
def ==(other)
|
68
76
|
other.is_a?(Location) && start_line == other.start_line &&
|
69
|
-
start_char == other.start_char &&
|
70
|
-
end_line == other.end_line &&
|
77
|
+
start_char == other.start_char && end_line == other.end_line &&
|
71
78
|
end_char == other.end_char
|
72
79
|
end
|
73
80
|
|
@@ -75,7 +82,7 @@ class SyntaxTree < Ripper
|
|
75
82
|
Location.new(
|
76
83
|
start_line: start_line,
|
77
84
|
start_char: start_char,
|
78
|
-
end_line: other.end_line,
|
85
|
+
end_line: [end_line, other.end_line].max,
|
79
86
|
end_char: other.end_char
|
80
87
|
)
|
81
88
|
end
|
@@ -113,16 +120,21 @@ class SyntaxTree < Ripper
|
|
113
120
|
# A slightly enhanced PP that knows how to format recursively including
|
114
121
|
# comments.
|
115
122
|
class Formatter < PP
|
116
|
-
|
123
|
+
COMMENT_PRIORITY = 1
|
124
|
+
HEREDOC_PRIORITY = 2
|
125
|
+
|
126
|
+
attr_reader :source, :stack, :quote
|
117
127
|
|
118
|
-
def initialize(
|
119
|
-
super
|
128
|
+
def initialize(source, ...)
|
129
|
+
super(...)
|
130
|
+
|
131
|
+
@source = source
|
120
132
|
@stack = []
|
121
133
|
@quote = "\""
|
122
134
|
end
|
123
135
|
|
124
|
-
def format(node)
|
125
|
-
stack << node
|
136
|
+
def format(node, stackable: true)
|
137
|
+
stack << node if stackable
|
126
138
|
doc = nil
|
127
139
|
|
128
140
|
# If there are comments, then we're going to format them around the node
|
@@ -136,11 +148,17 @@ class SyntaxTree < Ripper
|
|
136
148
|
breakable(force: true)
|
137
149
|
end
|
138
150
|
|
139
|
-
|
151
|
+
# If the node has a stree-ignore comment right before it, then we're
|
152
|
+
# going to just print out the node as it was seen in the source.
|
153
|
+
if leading.last&.ignore?
|
154
|
+
doc = text(source[node.location.start_char...node.location.end_char])
|
155
|
+
else
|
156
|
+
doc = node.format(self)
|
157
|
+
end
|
140
158
|
|
141
159
|
# Print all comments that were found after the node.
|
142
160
|
trailing.each do |comment|
|
143
|
-
line_suffix do
|
161
|
+
line_suffix(priority: COMMENT_PRIORITY) do
|
144
162
|
text(" ")
|
145
163
|
comment.format(self)
|
146
164
|
break_parent
|
@@ -150,7 +168,7 @@ class SyntaxTree < Ripper
|
|
150
168
|
doc = node.format(self)
|
151
169
|
end
|
152
170
|
|
153
|
-
stack.pop
|
171
|
+
stack.pop if stackable
|
154
172
|
doc
|
155
173
|
end
|
156
174
|
|
@@ -173,6 +191,10 @@ class SyntaxTree < Ripper
|
|
173
191
|
# [Array[ String ]] the list of lines in the source
|
174
192
|
attr_reader :lines
|
175
193
|
|
194
|
+
# [Array[ SingleByteString | MultiByteString ]] the list of objects that
|
195
|
+
# represent the start of each line in character offsets
|
196
|
+
attr_reader :line_counts
|
197
|
+
|
176
198
|
# [Array[ untyped ]] a running list of tokens that have been found in the
|
177
199
|
# source. This list changes a lot as certain nodes will "consume" these tokens
|
178
200
|
# to determine their bounds.
|
@@ -248,6 +270,12 @@ class SyntaxTree < Ripper
|
|
248
270
|
|
249
271
|
last_index += line.size
|
250
272
|
end
|
273
|
+
|
274
|
+
# Make sure line counts is filled out with the first and last line at
|
275
|
+
# minimum so that it has something to compare against if the parser is in a
|
276
|
+
# lineno=2 state for an empty file.
|
277
|
+
@line_counts << SingleByteString.new(0) if @line_counts.empty?
|
278
|
+
@line_counts << SingleByteString.new(last_index)
|
251
279
|
end
|
252
280
|
|
253
281
|
def self.parse(source)
|
@@ -259,13 +287,26 @@ class SyntaxTree < Ripper
|
|
259
287
|
def self.format(source)
|
260
288
|
output = []
|
261
289
|
|
262
|
-
formatter = Formatter.new(output)
|
290
|
+
formatter = Formatter.new(source, output)
|
263
291
|
parse(source).format(formatter)
|
264
292
|
|
265
293
|
formatter.flush
|
266
294
|
output.join
|
267
295
|
end
|
268
296
|
|
297
|
+
# Returns the source from the given filepath taking into account any potential
|
298
|
+
# magic encoding comments.
|
299
|
+
def self.read(filepath)
|
300
|
+
encoding =
|
301
|
+
File.open(filepath, "r") do |file|
|
302
|
+
header = file.readline
|
303
|
+
header += file.readline if header.start_with?("#!")
|
304
|
+
Ripper.new(header).tap(&:parse).encoding
|
305
|
+
end
|
306
|
+
|
307
|
+
File.read(filepath, encoding: encoding)
|
308
|
+
end
|
309
|
+
|
269
310
|
private
|
270
311
|
|
271
312
|
# ----------------------------------------------------------------------------
|
@@ -280,7 +321,7 @@ class SyntaxTree < Ripper
|
|
280
321
|
# this line, then we add the number of columns into this line that we've gone
|
281
322
|
# through.
|
282
323
|
def char_pos
|
283
|
-
|
324
|
+
line_counts[lineno - 1][column]
|
284
325
|
end
|
285
326
|
|
286
327
|
# As we build up a list of tokens, we'll periodically need to go backwards and
|
@@ -294,7 +335,7 @@ class SyntaxTree < Ripper
|
|
294
335
|
# (which would happen to be the innermost keyword). Then the outer one would
|
295
336
|
# only be able to grab the first one. In this way all of the tokens act as
|
296
337
|
# their own stack.
|
297
|
-
def find_token(type, value = :any, consume: true)
|
338
|
+
def find_token(type, value = :any, consume: true, location: nil)
|
298
339
|
index =
|
299
340
|
tokens.rindex do |token|
|
300
341
|
token.is_a?(type) && (value == :any || (token.value == value))
|
@@ -307,8 +348,16 @@ class SyntaxTree < Ripper
|
|
307
348
|
# could also be caused by accidentally attempting to consume a token twice
|
308
349
|
# by two different parser event handlers.
|
309
350
|
unless index
|
310
|
-
|
311
|
-
|
351
|
+
token = value == :any ? type.name.split("::", 2).last : value
|
352
|
+
message = "Cannot find expected #{token}"
|
353
|
+
|
354
|
+
if location
|
355
|
+
lineno = location.start_line
|
356
|
+
column = location.start_char - line_counts[lineno - 1].start
|
357
|
+
raise ParseError.new(message, lineno, column)
|
358
|
+
else
|
359
|
+
raise ParseError.new(message, lineno, column)
|
360
|
+
end
|
312
361
|
end
|
313
362
|
|
314
363
|
tokens.delete_at(index)
|
@@ -476,7 +525,7 @@ class SyntaxTree < Ripper
|
|
476
525
|
q.text(value)
|
477
526
|
else
|
478
527
|
q.text(q.quote)
|
479
|
-
q.text(value[1])
|
528
|
+
q.text(value[1] == "\"" ? "\\\"" : value[1])
|
480
529
|
q.text(q.quote)
|
481
530
|
end
|
482
531
|
end
|
@@ -628,7 +677,9 @@ class SyntaxTree < Ripper
|
|
628
677
|
def format(q)
|
629
678
|
q.text("__END__")
|
630
679
|
q.breakable(force: true)
|
631
|
-
|
680
|
+
|
681
|
+
separator = -> { q.breakable(indent: false, force: true) }
|
682
|
+
q.seplist(value.split(/\r?\n/, -1), separator) { |line| q.text(line) }
|
632
683
|
end
|
633
684
|
|
634
685
|
def pretty_print(q)
|
@@ -654,7 +705,7 @@ class SyntaxTree < Ripper
|
|
654
705
|
def on___end__(value)
|
655
706
|
@__end__ =
|
656
707
|
EndContent.new(
|
657
|
-
value:
|
708
|
+
value: source[(char_pos + value.length)..-1],
|
658
709
|
location: Location.token(line: lineno, char: char_pos, size: value.size)
|
659
710
|
)
|
660
711
|
end
|
@@ -725,11 +776,11 @@ class SyntaxTree < Ripper
|
|
725
776
|
|
726
777
|
q.group do
|
727
778
|
q.text(keyword)
|
728
|
-
q.format(left_argument)
|
779
|
+
q.format(left_argument, stackable: false)
|
729
780
|
q.group do
|
730
781
|
q.nest(keyword.length) do
|
731
782
|
q.breakable(force: left_argument.comments.any?)
|
732
|
-
q.format(AliasArgumentFormatter.new(right))
|
783
|
+
q.format(AliasArgumentFormatter.new(right), stackable: false)
|
733
784
|
end
|
734
785
|
end
|
735
786
|
end
|
@@ -1122,7 +1173,7 @@ class SyntaxTree < Ripper
|
|
1122
1173
|
# method(&expression)
|
1123
1174
|
#
|
1124
1175
|
class ArgBlock
|
1125
|
-
# [untyped] the expression being turned into a block
|
1176
|
+
# [nil | untyped] the expression being turned into a block
|
1126
1177
|
attr_reader :value
|
1127
1178
|
|
1128
1179
|
# [Location] the location of this node
|
@@ -1143,15 +1194,17 @@ class SyntaxTree < Ripper
|
|
1143
1194
|
|
1144
1195
|
def format(q)
|
1145
1196
|
q.text("&")
|
1146
|
-
q.format(value)
|
1197
|
+
q.format(value) if value
|
1147
1198
|
end
|
1148
1199
|
|
1149
1200
|
def pretty_print(q)
|
1150
1201
|
q.group(2, "(", ")") do
|
1151
1202
|
q.text("arg_block")
|
1152
1203
|
|
1153
|
-
|
1154
|
-
|
1204
|
+
if value
|
1205
|
+
q.breakable
|
1206
|
+
q.pp(value)
|
1207
|
+
end
|
1155
1208
|
|
1156
1209
|
q.pp(Comment::List.new(comments))
|
1157
1210
|
end
|
@@ -1170,17 +1223,34 @@ class SyntaxTree < Ripper
|
|
1170
1223
|
# (false | untyped) block
|
1171
1224
|
# ) -> Args
|
1172
1225
|
def on_args_add_block(arguments, block)
|
1173
|
-
|
1226
|
+
operator = find_token(Op, "&", consume: false)
|
1174
1227
|
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1228
|
+
# If we can't find the & operator, then there's no block to add to the list,
|
1229
|
+
# so we're just going to return the arguments as-is.
|
1230
|
+
return arguments unless operator
|
1231
|
+
|
1232
|
+
# Now we know we have an & operator, so we're going to delete it from the
|
1233
|
+
# list of tokens to make sure it doesn't get confused with anything else.
|
1234
|
+
tokens.delete(operator)
|
1235
|
+
|
1236
|
+
# Construct the location that represents the block argument.
|
1237
|
+
location = operator.location
|
1238
|
+
location = operator.location.to(block.location) if block
|
1239
|
+
|
1240
|
+
# If there are any arguments and the operator we found from the list is not
|
1241
|
+
# after them, then we're going to return the arguments as-is because we're
|
1242
|
+
# looking at an & that occurs before the arguments are done.
|
1243
|
+
if arguments.parts.any? && location.start_char < arguments.location.end_char
|
1244
|
+
return arguments
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
# Otherwise, we're looking at an actual block argument (with or without a
|
1248
|
+
# block, which could be missing because it could be a bare & since 3.1.0).
|
1249
|
+
arg_block = ArgBlock.new(value: block, location: location)
|
1180
1250
|
|
1181
1251
|
Args.new(
|
1182
1252
|
parts: arguments.parts << arg_block,
|
1183
|
-
location: arguments.location.to(
|
1253
|
+
location: arguments.location.to(location)
|
1184
1254
|
)
|
1185
1255
|
end
|
1186
1256
|
|
@@ -1349,7 +1419,11 @@ class SyntaxTree < Ripper
|
|
1349
1419
|
q.indent do
|
1350
1420
|
q.breakable("")
|
1351
1421
|
q.seplist(contents.parts, -> { q.breakable }) do |part|
|
1352
|
-
|
1422
|
+
if part.is_a?(StringLiteral)
|
1423
|
+
q.format(part.parts.first)
|
1424
|
+
else
|
1425
|
+
q.text(part.value[1..-1])
|
1426
|
+
end
|
1353
1427
|
end
|
1354
1428
|
end
|
1355
1429
|
q.breakable("")
|
@@ -1373,10 +1447,39 @@ class SyntaxTree < Ripper
|
|
1373
1447
|
q.format(part.value)
|
1374
1448
|
end
|
1375
1449
|
end
|
1450
|
+
q.breakable("")
|
1451
|
+
end
|
1452
|
+
end
|
1453
|
+
end
|
1454
|
+
|
1455
|
+
class VarRefsFormatter
|
1456
|
+
# [Args] the contents of the array
|
1457
|
+
attr_reader :contents
|
1458
|
+
|
1459
|
+
def initialize(contents)
|
1460
|
+
@contents = contents
|
1461
|
+
end
|
1462
|
+
|
1463
|
+
def format(q)
|
1464
|
+
q.group(0, "[", "]") do
|
1465
|
+
q.indent do
|
1466
|
+
q.breakable("")
|
1467
|
+
|
1468
|
+
separator = -> do
|
1469
|
+
q.text(",")
|
1470
|
+
q.fill_breakable
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
q.seplist(contents.parts, separator) { |part| q.format(part) }
|
1474
|
+
end
|
1475
|
+
q.breakable("")
|
1376
1476
|
end
|
1377
1477
|
end
|
1378
1478
|
end
|
1379
1479
|
|
1480
|
+
# [LBracket] the bracket that opens this array
|
1481
|
+
attr_reader :lbracket
|
1482
|
+
|
1380
1483
|
# [nil | Args] the contents of the array
|
1381
1484
|
attr_reader :contents
|
1382
1485
|
|
@@ -1386,22 +1489,18 @@ class SyntaxTree < Ripper
|
|
1386
1489
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
1387
1490
|
attr_reader :comments
|
1388
1491
|
|
1389
|
-
def initialize(contents:, location:, comments: [])
|
1492
|
+
def initialize(lbracket:, contents:, location:, comments: [])
|
1493
|
+
@lbracket = lbracket
|
1390
1494
|
@contents = contents
|
1391
1495
|
@location = location
|
1392
1496
|
@comments = comments
|
1393
1497
|
end
|
1394
1498
|
|
1395
1499
|
def child_nodes
|
1396
|
-
[contents]
|
1500
|
+
[lbracket, contents]
|
1397
1501
|
end
|
1398
1502
|
|
1399
1503
|
def format(q)
|
1400
|
-
unless contents
|
1401
|
-
q.text("[]")
|
1402
|
-
return
|
1403
|
-
end
|
1404
|
-
|
1405
1504
|
if qwords?
|
1406
1505
|
QWordsFormatter.new(contents).format(q)
|
1407
1506
|
return
|
@@ -1412,12 +1511,23 @@ class SyntaxTree < Ripper
|
|
1412
1511
|
return
|
1413
1512
|
end
|
1414
1513
|
|
1415
|
-
q
|
1416
|
-
q
|
1417
|
-
|
1418
|
-
|
1514
|
+
if var_refs?(q)
|
1515
|
+
VarRefsFormatter.new(contents).format(q)
|
1516
|
+
return
|
1517
|
+
end
|
1518
|
+
|
1519
|
+
q.group do
|
1520
|
+
q.format(lbracket)
|
1521
|
+
|
1522
|
+
if contents
|
1523
|
+
q.indent do
|
1524
|
+
q.breakable("")
|
1525
|
+
q.format(contents)
|
1526
|
+
end
|
1419
1527
|
end
|
1528
|
+
|
1420
1529
|
q.breakable("")
|
1530
|
+
q.text("]")
|
1421
1531
|
end
|
1422
1532
|
end
|
1423
1533
|
|
@@ -1441,21 +1551,40 @@ class SyntaxTree < Ripper
|
|
1441
1551
|
private
|
1442
1552
|
|
1443
1553
|
def qwords?
|
1444
|
-
|
1554
|
+
lbracket.comments.empty? && contents && contents.comments.empty? &&
|
1555
|
+
contents.parts.length > 1 &&
|
1445
1556
|
contents.parts.all? do |part|
|
1446
|
-
|
1447
|
-
|
1448
|
-
part.
|
1449
|
-
|
1557
|
+
case part
|
1558
|
+
when StringLiteral
|
1559
|
+
part.comments.empty? && part.parts.length == 1 &&
|
1560
|
+
part.parts.first.is_a?(TStringContent) &&
|
1561
|
+
!part.parts.first.value.match?(/[\s\[\]\\]/)
|
1562
|
+
when CHAR
|
1563
|
+
!part.value.match?(/[\[\]\\]/)
|
1564
|
+
else
|
1565
|
+
false
|
1566
|
+
end
|
1450
1567
|
end
|
1451
1568
|
end
|
1452
1569
|
|
1453
1570
|
def qsymbols?
|
1454
|
-
|
1571
|
+
lbracket.comments.empty? && contents && contents.comments.empty? &&
|
1572
|
+
contents.parts.length > 1 &&
|
1455
1573
|
contents.parts.all? do |part|
|
1456
1574
|
part.is_a?(SymbolLiteral) && part.comments.empty?
|
1457
1575
|
end
|
1458
1576
|
end
|
1577
|
+
|
1578
|
+
def var_refs?(q)
|
1579
|
+
lbracket.comments.empty? && contents && contents.comments.empty? &&
|
1580
|
+
contents.parts.all? do |part|
|
1581
|
+
part.is_a?(VarRef) && part.comments.empty?
|
1582
|
+
end &&
|
1583
|
+
(
|
1584
|
+
contents.parts.sum { |part| part.value.value.length + 2 } >
|
1585
|
+
q.maxwidth * 2
|
1586
|
+
)
|
1587
|
+
end
|
1459
1588
|
end
|
1460
1589
|
|
1461
1590
|
# :call-seq:
|
@@ -1467,13 +1596,16 @@ class SyntaxTree < Ripper
|
|
1467
1596
|
rbracket = find_token(RBracket)
|
1468
1597
|
|
1469
1598
|
ArrayLiteral.new(
|
1599
|
+
lbracket: lbracket,
|
1470
1600
|
contents: contents,
|
1471
1601
|
location: lbracket.location.to(rbracket.location)
|
1472
1602
|
)
|
1473
1603
|
else
|
1474
|
-
tstring_end =
|
1604
|
+
tstring_end =
|
1605
|
+
find_token(TStringEnd, location: contents.beginning.location)
|
1475
1606
|
|
1476
1607
|
contents.class.new(
|
1608
|
+
beginning: contents.beginning,
|
1477
1609
|
elements: contents.elements,
|
1478
1610
|
location: contents.location.to(tstring_end.location)
|
1479
1611
|
)
|
@@ -1554,7 +1686,7 @@ class SyntaxTree < Ripper
|
|
1554
1686
|
end
|
1555
1687
|
|
1556
1688
|
def child_nodes
|
1557
|
-
[constant, *
|
1689
|
+
[constant, *requireds, rest, *posts]
|
1558
1690
|
end
|
1559
1691
|
|
1560
1692
|
def format(q)
|
@@ -1563,20 +1695,23 @@ class SyntaxTree < Ripper
|
|
1563
1695
|
parts += posts
|
1564
1696
|
|
1565
1697
|
if constant
|
1566
|
-
q.
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1698
|
+
q.group do
|
1699
|
+
q.format(constant)
|
1700
|
+
q.text("[")
|
1701
|
+
q.seplist(parts) { |part| q.format(part) }
|
1702
|
+
q.text("]")
|
1703
|
+
end
|
1704
|
+
|
1570
1705
|
return
|
1571
1706
|
end
|
1572
1707
|
|
1573
1708
|
parent = q.parent
|
1574
|
-
if parts.length == 1 || PATTERNS.
|
1709
|
+
if parts.length == 1 || PATTERNS.include?(parent.class)
|
1575
1710
|
q.text("[")
|
1576
1711
|
q.seplist(parts) { |part| q.format(part) }
|
1577
1712
|
q.text("]")
|
1578
1713
|
else
|
1579
|
-
q.seplist(parts) { |part| q.format(part) }
|
1714
|
+
q.group { q.seplist(parts) { |part| q.format(part) } }
|
1580
1715
|
end
|
1581
1716
|
end
|
1582
1717
|
|
@@ -1642,6 +1777,23 @@ class SyntaxTree < Ripper
|
|
1642
1777
|
)
|
1643
1778
|
end
|
1644
1779
|
|
1780
|
+
# Determins if the following value should be indented or not.
|
1781
|
+
module AssignFormatting
|
1782
|
+
def self.skip_indent?(value)
|
1783
|
+
(value.is_a?(Call) && skip_indent?(value.receiver)) ||
|
1784
|
+
[
|
1785
|
+
ArrayLiteral,
|
1786
|
+
HashLiteral,
|
1787
|
+
Heredoc,
|
1788
|
+
Lambda,
|
1789
|
+
QSymbols,
|
1790
|
+
QWords,
|
1791
|
+
Symbols,
|
1792
|
+
Words
|
1793
|
+
].include?(value.class)
|
1794
|
+
end
|
1795
|
+
end
|
1796
|
+
|
1645
1797
|
# Assign represents assigning something to a variable or constant. Generally,
|
1646
1798
|
# the left side of the assignment is going to be any node that ends with the
|
1647
1799
|
# name "Field".
|
@@ -1717,10 +1869,8 @@ class SyntaxTree < Ripper
|
|
1717
1869
|
private
|
1718
1870
|
|
1719
1871
|
def skip_indent?
|
1720
|
-
target.
|
1721
|
-
|
1722
|
-
value.is_a?(Heredoc) ||
|
1723
|
-
value.is_a?(Lambda)
|
1872
|
+
target.comments.empty? &&
|
1873
|
+
(target.is_a?(ARefField) || AssignFormatting.skip_indent?(value))
|
1724
1874
|
end
|
1725
1875
|
end
|
1726
1876
|
|
@@ -1768,15 +1918,11 @@ class SyntaxTree < Ripper
|
|
1768
1918
|
end
|
1769
1919
|
|
1770
1920
|
def format(q)
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
q.format(value)
|
1776
|
-
end
|
1921
|
+
if value&.is_a?(HashLiteral)
|
1922
|
+
format_contents(q)
|
1923
|
+
else
|
1924
|
+
q.group { format_contents(q) }
|
1777
1925
|
end
|
1778
|
-
|
1779
|
-
value.is_a?(HashLiteral) ? contents.call : q.group(&contents)
|
1780
1926
|
end
|
1781
1927
|
|
1782
1928
|
def pretty_print(q)
|
@@ -1786,8 +1932,10 @@ class SyntaxTree < Ripper
|
|
1786
1932
|
q.breakable
|
1787
1933
|
q.pp(key)
|
1788
1934
|
|
1789
|
-
|
1790
|
-
|
1935
|
+
if value
|
1936
|
+
q.breakable
|
1937
|
+
q.pp(value)
|
1938
|
+
end
|
1791
1939
|
|
1792
1940
|
q.pp(Comment::List.new(comments))
|
1793
1941
|
end
|
@@ -1802,12 +1950,32 @@ class SyntaxTree < Ripper
|
|
1802
1950
|
cmts: comments
|
1803
1951
|
}.to_json(*opts)
|
1804
1952
|
end
|
1953
|
+
|
1954
|
+
private
|
1955
|
+
|
1956
|
+
def format_contents(q)
|
1957
|
+
q.parent.format_key(q, key)
|
1958
|
+
return unless value
|
1959
|
+
|
1960
|
+
if key.comments.empty? && AssignFormatting.skip_indent?(value)
|
1961
|
+
q.text(" ")
|
1962
|
+
q.format(value)
|
1963
|
+
else
|
1964
|
+
q.indent do
|
1965
|
+
q.breakable
|
1966
|
+
q.format(value)
|
1967
|
+
end
|
1968
|
+
end
|
1969
|
+
end
|
1805
1970
|
end
|
1806
1971
|
|
1807
1972
|
# :call-seq:
|
1808
1973
|
# on_assoc_new: (untyped key, untyped value) -> Assoc
|
1809
1974
|
def on_assoc_new(key, value)
|
1810
|
-
|
1975
|
+
location = key.location
|
1976
|
+
location = location.to(value.location) if value
|
1977
|
+
|
1978
|
+
Assoc.new(key: key, value: value, location: location)
|
1811
1979
|
end
|
1812
1980
|
|
1813
1981
|
# AssocSplat represents double-splatting a value into a hash (either a hash
|
@@ -1990,25 +2158,8 @@ class SyntaxTree < Ripper
|
|
1990
2158
|
# This module is responsible for formatting the assocs contained within a
|
1991
2159
|
# hash or bare hash. It first determines if every key in the hash can use
|
1992
2160
|
# labels. If it can, it uses labels. Otherwise it uses hash rockets.
|
1993
|
-
module
|
1994
|
-
class
|
1995
|
-
# [HashLiteral | BareAssocHash] the source of the assocs
|
1996
|
-
attr_reader :container
|
1997
|
-
|
1998
|
-
def initialize(container)
|
1999
|
-
@container = container
|
2000
|
-
end
|
2001
|
-
|
2002
|
-
def comments
|
2003
|
-
container.comments
|
2004
|
-
end
|
2005
|
-
|
2006
|
-
def format(q)
|
2007
|
-
q.seplist(container.assocs) { |assoc| q.format(assoc) }
|
2008
|
-
end
|
2009
|
-
end
|
2010
|
-
|
2011
|
-
class Labels < Base
|
2161
|
+
module HashKeyFormatter
|
2162
|
+
class Labels
|
2012
2163
|
def format_key(q, key)
|
2013
2164
|
case key
|
2014
2165
|
when Label
|
@@ -2023,7 +2174,7 @@ class SyntaxTree < Ripper
|
|
2023
2174
|
end
|
2024
2175
|
end
|
2025
2176
|
|
2026
|
-
class Rockets
|
2177
|
+
class Rockets
|
2027
2178
|
def format_key(q, key)
|
2028
2179
|
case key
|
2029
2180
|
when Label
|
@@ -2063,7 +2214,7 @@ class SyntaxTree < Ripper
|
|
2063
2214
|
end
|
2064
2215
|
end
|
2065
2216
|
|
2066
|
-
(labels ? Labels : Rockets).new
|
2217
|
+
(labels ? Labels : Rockets).new
|
2067
2218
|
end
|
2068
2219
|
end
|
2069
2220
|
|
@@ -2094,7 +2245,11 @@ class SyntaxTree < Ripper
|
|
2094
2245
|
end
|
2095
2246
|
|
2096
2247
|
def format(q)
|
2097
|
-
q.
|
2248
|
+
q.seplist(assocs) { |assoc| q.format(assoc) }
|
2249
|
+
end
|
2250
|
+
|
2251
|
+
def format_key(q, key)
|
2252
|
+
(@key_formatter ||= HashKeyFormatter.for(self)).format_key(q, key)
|
2098
2253
|
end
|
2099
2254
|
|
2100
2255
|
def pretty_print(q)
|
@@ -2188,24 +2343,95 @@ class SyntaxTree < Ripper
|
|
2188
2343
|
end
|
2189
2344
|
end
|
2190
2345
|
|
2346
|
+
# PinnedBegin represents a pinning a nested statement within pattern matching.
|
2347
|
+
#
|
2348
|
+
# case value
|
2349
|
+
# in ^(statement)
|
2350
|
+
# end
|
2351
|
+
#
|
2352
|
+
class PinnedBegin
|
2353
|
+
# [untyped] the expression being pinned
|
2354
|
+
attr_reader :statement
|
2355
|
+
|
2356
|
+
# [Location] the location of this node
|
2357
|
+
attr_reader :location
|
2358
|
+
|
2359
|
+
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
2360
|
+
attr_reader :comments
|
2361
|
+
|
2362
|
+
def initialize(statement:, location:, comments: [])
|
2363
|
+
@statement = statement
|
2364
|
+
@location = location
|
2365
|
+
@comments = comments
|
2366
|
+
end
|
2367
|
+
|
2368
|
+
def child_nodes
|
2369
|
+
[statement]
|
2370
|
+
end
|
2371
|
+
|
2372
|
+
def format(q)
|
2373
|
+
q.group do
|
2374
|
+
q.text("^(")
|
2375
|
+
q.nest(1) do
|
2376
|
+
q.indent do
|
2377
|
+
q.breakable("")
|
2378
|
+
q.format(statement)
|
2379
|
+
end
|
2380
|
+
q.breakable("")
|
2381
|
+
q.text(")")
|
2382
|
+
end
|
2383
|
+
end
|
2384
|
+
end
|
2385
|
+
|
2386
|
+
def pretty_print(q)
|
2387
|
+
q.group(2, "(", ")") do
|
2388
|
+
q.text("pinned_begin")
|
2389
|
+
|
2390
|
+
q.breakable
|
2391
|
+
q.pp(statement)
|
2392
|
+
|
2393
|
+
q.pp(Comment::List.new(comments))
|
2394
|
+
end
|
2395
|
+
end
|
2396
|
+
|
2397
|
+
def to_json(*opts)
|
2398
|
+
{
|
2399
|
+
type: :pinned_begin,
|
2400
|
+
stmt: statement,
|
2401
|
+
loc: location,
|
2402
|
+
cmts: comments
|
2403
|
+
}.to_json(*opts)
|
2404
|
+
end
|
2405
|
+
end
|
2406
|
+
|
2191
2407
|
# :call-seq:
|
2192
|
-
# on_begin: (
|
2408
|
+
# on_begin: (untyped bodystmt) -> Begin | PinnedBegin
|
2193
2409
|
def on_begin(bodystmt)
|
2194
|
-
|
2195
|
-
end_char =
|
2196
|
-
if bodystmt.rescue_clause || bodystmt.ensure_clause ||
|
2197
|
-
bodystmt.else_clause
|
2198
|
-
bodystmt.location.end_char
|
2199
|
-
else
|
2200
|
-
find_token(Kw, "end").location.end_char
|
2201
|
-
end
|
2410
|
+
pin = find_token(Op, "^", consume: false)
|
2202
2411
|
|
2203
|
-
bodystmt.
|
2412
|
+
if pin && pin.location.start_char < bodystmt.location.start_char
|
2413
|
+
tokens.delete(pin)
|
2414
|
+
find_token(LParen)
|
2204
2415
|
|
2205
|
-
|
2206
|
-
|
2207
|
-
|
2208
|
-
|
2416
|
+
rparen = find_token(RParen)
|
2417
|
+
location = pin.location.to(rparen.location)
|
2418
|
+
|
2419
|
+
PinnedBegin.new(statement: bodystmt, location: location)
|
2420
|
+
else
|
2421
|
+
keyword = find_token(Kw, "begin")
|
2422
|
+
end_char =
|
2423
|
+
if bodystmt.rescue_clause || bodystmt.ensure_clause ||
|
2424
|
+
bodystmt.else_clause
|
2425
|
+
bodystmt.location.end_char
|
2426
|
+
else
|
2427
|
+
find_token(Kw, "end").location.end_char
|
2428
|
+
end
|
2429
|
+
|
2430
|
+
bodystmt.bind(keyword.location.end_char, end_char)
|
2431
|
+
location = keyword.location.to(bodystmt.location)
|
2432
|
+
|
2433
|
+
Begin.new(bodystmt: bodystmt, location: location)
|
2434
|
+
end
|
2209
2435
|
end
|
2210
2436
|
|
2211
2437
|
# Binary represents any expression that involves two sub-expressions with an
|
@@ -2252,11 +2478,14 @@ class SyntaxTree < Ripper
|
|
2252
2478
|
q.group do
|
2253
2479
|
q.group { q.format(left) }
|
2254
2480
|
q.text(" ") unless power
|
2255
|
-
q.text(operator)
|
2256
2481
|
|
2257
|
-
q.
|
2258
|
-
q.
|
2259
|
-
|
2482
|
+
q.group do
|
2483
|
+
q.text(operator)
|
2484
|
+
|
2485
|
+
q.indent do
|
2486
|
+
q.breakable(power ? "" : " ")
|
2487
|
+
q.format(right)
|
2488
|
+
end
|
2260
2489
|
end
|
2261
2490
|
end
|
2262
2491
|
end
|
@@ -2293,12 +2522,29 @@ class SyntaxTree < Ripper
|
|
2293
2522
|
# :call-seq:
|
2294
2523
|
# on_binary: (untyped left, (Op | Symbol) operator, untyped right) -> Binary
|
2295
2524
|
def on_binary(left, operator, right)
|
2296
|
-
|
2297
|
-
|
2298
|
-
|
2299
|
-
|
2300
|
-
|
2301
|
-
|
2525
|
+
if operator.is_a?(Symbol)
|
2526
|
+
# Here, we're going to search backward for the token that's between the
|
2527
|
+
# two operands that matches the operator so we can delete it from the
|
2528
|
+
# list.
|
2529
|
+
index =
|
2530
|
+
tokens.rindex do |token|
|
2531
|
+
location = token.location
|
2532
|
+
|
2533
|
+
token.is_a?(Op) &&
|
2534
|
+
token.value == operator.to_s &&
|
2535
|
+
location.start_char > left.location.end_char &&
|
2536
|
+
location.end_char < right.location.start_char
|
2537
|
+
end
|
2538
|
+
|
2539
|
+
tokens.delete_at(index) if index
|
2540
|
+
else
|
2541
|
+
# On most Ruby implementations, operator is a Symbol that represents that
|
2542
|
+
# operation being performed. For instance in the example `1 < 2`, the
|
2543
|
+
# `operator` object would be `:<`. However, on JRuby, it's an `@op` node,
|
2544
|
+
# so here we're going to explicitly convert it into the same normalized
|
2545
|
+
# form.
|
2546
|
+
operator = tokens.delete(operator).value
|
2547
|
+
end
|
2302
2548
|
|
2303
2549
|
Binary.new(
|
2304
2550
|
left: left,
|
@@ -2448,7 +2694,7 @@ class SyntaxTree < Ripper
|
|
2448
2694
|
# def method(&block); end
|
2449
2695
|
#
|
2450
2696
|
class BlockArg
|
2451
|
-
# [Ident] the name of the block argument
|
2697
|
+
# [nil | Ident] the name of the block argument
|
2452
2698
|
attr_reader :name
|
2453
2699
|
|
2454
2700
|
# [Location] the location of this node
|
@@ -2469,15 +2715,17 @@ class SyntaxTree < Ripper
|
|
2469
2715
|
|
2470
2716
|
def format(q)
|
2471
2717
|
q.text("&")
|
2472
|
-
q.format(name)
|
2718
|
+
q.format(name) if name
|
2473
2719
|
end
|
2474
2720
|
|
2475
2721
|
def pretty_print(q)
|
2476
2722
|
q.group(2, "(", ")") do
|
2477
2723
|
q.text("blockarg")
|
2478
2724
|
|
2479
|
-
|
2480
|
-
|
2725
|
+
if name
|
2726
|
+
q.breakable
|
2727
|
+
q.pp(name)
|
2728
|
+
end
|
2481
2729
|
|
2482
2730
|
q.pp(Comment::List.new(comments))
|
2483
2731
|
end
|
@@ -2495,7 +2743,10 @@ class SyntaxTree < Ripper
|
|
2495
2743
|
def on_blockarg(name)
|
2496
2744
|
operator = find_token(Op, "&")
|
2497
2745
|
|
2498
|
-
|
2746
|
+
location = operator.location
|
2747
|
+
location = location.to(name.location) if name
|
2748
|
+
|
2749
|
+
BlockArg.new(name: name, location: location)
|
2499
2750
|
end
|
2500
2751
|
|
2501
2752
|
# bodystmt can't actually determine its bounds appropriately because it
|
@@ -2685,59 +2936,137 @@ class SyntaxTree < Ripper
|
|
2685
2936
|
# [LBrace | Keyword] the node that opens the block
|
2686
2937
|
attr_reader :block_open
|
2687
2938
|
|
2939
|
+
# [String] the string that closes the block
|
2940
|
+
attr_reader :block_close
|
2941
|
+
|
2688
2942
|
# [BodyStmt | Statements] the statements inside the block
|
2689
2943
|
attr_reader :statements
|
2690
2944
|
|
2691
|
-
def initialize(node, block_open, statements)
|
2945
|
+
def initialize(node, block_open, block_close, statements)
|
2692
2946
|
@node = node
|
2693
2947
|
@block_open = block_open
|
2948
|
+
@block_close = block_close
|
2694
2949
|
@statements = statements
|
2695
2950
|
end
|
2696
2951
|
|
2697
2952
|
def format(q)
|
2698
|
-
|
2699
|
-
|
2953
|
+
# If this is nested anywhere inside of a Command or CommandCall node, then
|
2954
|
+
# we can't change which operators we're using for the bounds of the block.
|
2955
|
+
break_opening, break_closing, flat_opening, flat_closing =
|
2956
|
+
if unchangeable_bounds?(q)
|
2957
|
+
[block_open.value, block_close, block_open.value, block_close]
|
2958
|
+
elsif forced_do_end_bounds?(q)
|
2959
|
+
%w[do end do end]
|
2960
|
+
elsif forced_brace_bounds?(q)
|
2961
|
+
%w[{ } { }]
|
2962
|
+
else
|
2963
|
+
%w[do end { }]
|
2964
|
+
end
|
2700
2965
|
|
2701
|
-
|
2702
|
-
|
2966
|
+
# If the receiver of this block a Command or CommandCall node, then there
|
2967
|
+
# are no parentheses around the arguments to that command, so we need to
|
2968
|
+
# break the block.
|
2969
|
+
receiver = q.parent.call
|
2970
|
+
if receiver.is_a?(Command) || receiver.is_a?(CommandCall)
|
2971
|
+
q.break_parent
|
2972
|
+
format_break(q, break_opening, break_closing)
|
2973
|
+
return
|
2974
|
+
end
|
2703
2975
|
|
2704
|
-
|
2705
|
-
|
2706
|
-
|
2707
|
-
|
2976
|
+
q.group do
|
2977
|
+
q.if_break { format_break(q, break_opening, break_closing) }.if_flat do
|
2978
|
+
format_flat(q, flat_opening, flat_closing)
|
2979
|
+
end
|
2980
|
+
end
|
2981
|
+
end
|
2708
2982
|
|
2709
|
-
|
2710
|
-
q.indent do
|
2711
|
-
q.breakable
|
2712
|
-
q.format(statements)
|
2713
|
-
end
|
2714
|
-
end
|
2983
|
+
private
|
2715
2984
|
|
2716
|
-
|
2717
|
-
|
2718
|
-
|
2719
|
-
|
2985
|
+
# If this is nested anywhere inside certain nodes, then we can't change
|
2986
|
+
# which operators/keywords we're using for the bounds of the block.
|
2987
|
+
def unchangeable_bounds?(q)
|
2988
|
+
q.parents.any? do |parent|
|
2989
|
+
# If we hit a statements, then we're safe to use whatever since we
|
2990
|
+
# know for certain we're going to get split over multiple lines
|
2991
|
+
# anyway.
|
2992
|
+
break false if parent.is_a?(Statements)
|
2720
2993
|
|
2721
|
-
|
2722
|
-
|
2723
|
-
|
2724
|
-
q.breakable
|
2725
|
-
end
|
2994
|
+
[Command, CommandCall].include?(parent.class)
|
2995
|
+
end
|
2996
|
+
end
|
2726
2997
|
|
2727
|
-
|
2728
|
-
|
2729
|
-
|
2730
|
-
|
2731
|
-
|
2998
|
+
# If we're a sibling of a control-flow keyword, then we're going to have to
|
2999
|
+
# use the do..end bounds.
|
3000
|
+
def forced_do_end_bounds?(q)
|
3001
|
+
[Break, Next, Return, Super].include?(q.parent.call.class)
|
3002
|
+
end
|
2732
3003
|
|
2733
|
-
|
2734
|
-
|
3004
|
+
# If we're the predicate of a loop or conditional, then we're going to have
|
3005
|
+
# to go with the {..} bounds.
|
3006
|
+
def forced_brace_bounds?(q)
|
3007
|
+
parents = q.parents.to_a
|
3008
|
+
parents.each_with_index.any? do |parent, index|
|
3009
|
+
# If we hit certain breakpoints then we know we're safe.
|
3010
|
+
break false if [Paren, Statements].include?(parent.class)
|
3011
|
+
|
3012
|
+
[
|
3013
|
+
If,
|
3014
|
+
IfMod,
|
3015
|
+
IfOp,
|
3016
|
+
Unless,
|
3017
|
+
UnlessMod,
|
3018
|
+
While,
|
3019
|
+
WhileMod,
|
3020
|
+
Until,
|
3021
|
+
UntilMod
|
3022
|
+
].include?(parent.class) && parent.predicate == parents[index - 1]
|
2735
3023
|
end
|
2736
3024
|
end
|
2737
|
-
end
|
2738
3025
|
|
2739
|
-
|
2740
|
-
|
3026
|
+
def format_break(q, opening, closing)
|
3027
|
+
q.text(" ")
|
3028
|
+
q.format(BlockOpenFormatter.new(opening, block_open), stackable: false)
|
3029
|
+
|
3030
|
+
if node.block_var
|
3031
|
+
q.text(" ")
|
3032
|
+
q.format(node.block_var)
|
3033
|
+
end
|
3034
|
+
|
3035
|
+
unless statements.empty?
|
3036
|
+
q.indent do
|
3037
|
+
q.breakable
|
3038
|
+
q.format(statements)
|
3039
|
+
end
|
3040
|
+
end
|
3041
|
+
|
3042
|
+
q.breakable
|
3043
|
+
q.text(closing)
|
3044
|
+
end
|
3045
|
+
|
3046
|
+
def format_flat(q, opening, closing)
|
3047
|
+
q.text(" ")
|
3048
|
+
q.format(BlockOpenFormatter.new(opening, block_open), stackable: false)
|
3049
|
+
|
3050
|
+
if node.block_var
|
3051
|
+
q.breakable
|
3052
|
+
q.format(node.block_var)
|
3053
|
+
q.breakable
|
3054
|
+
end
|
3055
|
+
|
3056
|
+
if statements.empty?
|
3057
|
+
q.text(" ") if opening == "do"
|
3058
|
+
else
|
3059
|
+
q.breakable unless node.block_var
|
3060
|
+
q.format(statements)
|
3061
|
+
q.breakable
|
3062
|
+
end
|
3063
|
+
|
3064
|
+
q.text(closing)
|
3065
|
+
end
|
3066
|
+
end
|
3067
|
+
|
3068
|
+
# BraceBlock represents passing a block to a method call using the { }
|
3069
|
+
# operators.
|
2741
3070
|
#
|
2742
3071
|
# method { |variable| variable + 1 }
|
2743
3072
|
#
|
@@ -2770,7 +3099,7 @@ class SyntaxTree < Ripper
|
|
2770
3099
|
end
|
2771
3100
|
|
2772
3101
|
def format(q)
|
2773
|
-
BlockFormatter.new(self, lbrace, statements).format(q)
|
3102
|
+
BlockFormatter.new(self, lbrace, "}", statements).format(q)
|
2774
3103
|
end
|
2775
3104
|
|
2776
3105
|
def pretty_print(q)
|
@@ -2851,15 +3180,35 @@ class SyntaxTree < Ripper
|
|
2851
3180
|
q.text(keyword)
|
2852
3181
|
|
2853
3182
|
if arguments.parts.any?
|
2854
|
-
if arguments.parts.length == 1
|
2855
|
-
|
3183
|
+
if arguments.parts.length == 1
|
3184
|
+
part = arguments.parts.first
|
3185
|
+
|
3186
|
+
if part.is_a?(Paren)
|
3187
|
+
q.format(arguments)
|
3188
|
+
elsif part.is_a?(ArrayLiteral)
|
3189
|
+
q.text(" ")
|
3190
|
+
q.format(arguments)
|
3191
|
+
else
|
3192
|
+
format_arguments(q, "(", ")")
|
3193
|
+
end
|
2856
3194
|
else
|
2857
|
-
q
|
2858
|
-
q.nest(keyword.length + 1) { q.format(arguments) }
|
3195
|
+
format_arguments(q, " [", "]")
|
2859
3196
|
end
|
2860
3197
|
end
|
2861
3198
|
end
|
2862
3199
|
end
|
3200
|
+
|
3201
|
+
private
|
3202
|
+
|
3203
|
+
def format_arguments(q, opening, closing)
|
3204
|
+
q.if_break { q.text(opening) }
|
3205
|
+
q.indent do
|
3206
|
+
q.breakable(" ")
|
3207
|
+
q.format(node.arguments)
|
3208
|
+
end
|
3209
|
+
q.breakable("")
|
3210
|
+
q.if_break { q.text(closing) }
|
3211
|
+
end
|
2863
3212
|
end
|
2864
3213
|
|
2865
3214
|
# Break represents using the +break+ keyword.
|
@@ -2946,9 +3295,7 @@ class SyntaxTree < Ripper
|
|
2946
3295
|
end
|
2947
3296
|
end
|
2948
3297
|
|
2949
|
-
# Call represents a method call.
|
2950
|
-
# passed (if arguments are passed, this node will get nested under a
|
2951
|
-
# MethodAddArg node).
|
3298
|
+
# Call represents a method call.
|
2952
3299
|
#
|
2953
3300
|
# receiver.message
|
2954
3301
|
#
|
@@ -2962,32 +3309,64 @@ class SyntaxTree < Ripper
|
|
2962
3309
|
# [:call | Backtick | Const | Ident | Op] the message being sent
|
2963
3310
|
attr_reader :message
|
2964
3311
|
|
3312
|
+
# [nil | ArgParen | Args] the arguments to the method call
|
3313
|
+
attr_reader :arguments
|
3314
|
+
|
2965
3315
|
# [Location] the location of this node
|
2966
3316
|
attr_reader :location
|
2967
3317
|
|
2968
3318
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
2969
3319
|
attr_reader :comments
|
2970
3320
|
|
2971
|
-
def initialize(
|
3321
|
+
def initialize(
|
3322
|
+
receiver:,
|
3323
|
+
operator:,
|
3324
|
+
message:,
|
3325
|
+
arguments:,
|
3326
|
+
location:,
|
3327
|
+
comments: []
|
3328
|
+
)
|
2972
3329
|
@receiver = receiver
|
2973
3330
|
@operator = operator
|
2974
3331
|
@message = message
|
3332
|
+
@arguments = arguments
|
2975
3333
|
@location = location
|
2976
3334
|
@comments = comments
|
2977
3335
|
end
|
2978
3336
|
|
2979
3337
|
def child_nodes
|
2980
|
-
[
|
3338
|
+
[
|
3339
|
+
receiver,
|
3340
|
+
(operator if operator != :"::"),
|
3341
|
+
(message if message != :call),
|
3342
|
+
arguments
|
3343
|
+
]
|
2981
3344
|
end
|
2982
3345
|
|
2983
3346
|
def format(q)
|
3347
|
+
call_operator = CallOperatorFormatter.new(operator)
|
3348
|
+
|
2984
3349
|
q.group do
|
2985
3350
|
q.format(receiver)
|
3351
|
+
|
3352
|
+
# If there are trailing comments on the call operator, then we need to
|
3353
|
+
# use the trailing form as opposed to the leading form.
|
3354
|
+
q.format(call_operator) if call_operator.comments.any?
|
3355
|
+
|
2986
3356
|
q.group do
|
2987
3357
|
q.indent do
|
2988
|
-
|
3358
|
+
if receiver.comments.any? || call_operator.comments.any?
|
3359
|
+
q.breakable(force: true)
|
3360
|
+
end
|
3361
|
+
|
3362
|
+
if call_operator.comments.empty?
|
3363
|
+
q.format(call_operator, stackable: false)
|
3364
|
+
end
|
3365
|
+
|
2989
3366
|
q.format(message) if message != :call
|
2990
3367
|
end
|
3368
|
+
|
3369
|
+
q.format(arguments) if arguments
|
2991
3370
|
end
|
2992
3371
|
end
|
2993
3372
|
end
|
@@ -3005,6 +3384,11 @@ class SyntaxTree < Ripper
|
|
3005
3384
|
q.breakable
|
3006
3385
|
q.pp(message)
|
3007
3386
|
|
3387
|
+
if arguments
|
3388
|
+
q.breakable
|
3389
|
+
q.pp(arguments)
|
3390
|
+
end
|
3391
|
+
|
3008
3392
|
q.pp(Comment::List.new(comments))
|
3009
3393
|
end
|
3010
3394
|
end
|
@@ -3015,6 +3399,7 @@ class SyntaxTree < Ripper
|
|
3015
3399
|
receiver: receiver,
|
3016
3400
|
op: operator,
|
3017
3401
|
message: message,
|
3402
|
+
args: arguments,
|
3018
3403
|
loc: location,
|
3019
3404
|
cmts: comments
|
3020
3405
|
}.to_json(*opts)
|
@@ -3035,13 +3420,8 @@ class SyntaxTree < Ripper
|
|
3035
3420
|
receiver: receiver,
|
3036
3421
|
operator: operator,
|
3037
3422
|
message: message,
|
3038
|
-
|
3039
|
-
|
3040
|
-
start_line: receiver.location.start_line,
|
3041
|
-
start_char: receiver.location.start_char,
|
3042
|
-
end_line: [ending.location.end_line, receiver.location.end_line].max,
|
3043
|
-
end_char: ending.location.end_char
|
3044
|
-
)
|
3423
|
+
arguments: nil,
|
3424
|
+
location: receiver.location.to(ending.location)
|
3045
3425
|
)
|
3046
3426
|
end
|
3047
3427
|
|
@@ -3057,6 +3437,9 @@ class SyntaxTree < Ripper
|
|
3057
3437
|
# end
|
3058
3438
|
#
|
3059
3439
|
class Case
|
3440
|
+
# [Kw] the keyword that opens this expression
|
3441
|
+
attr_reader :keyword
|
3442
|
+
|
3060
3443
|
# [nil | untyped] optional value being switched on
|
3061
3444
|
attr_reader :value
|
3062
3445
|
|
@@ -3069,7 +3452,8 @@ class SyntaxTree < Ripper
|
|
3069
3452
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
3070
3453
|
attr_reader :comments
|
3071
3454
|
|
3072
|
-
def initialize(value:, consequent:, location:, comments: [])
|
3455
|
+
def initialize(keyword:, value:, consequent:, location:, comments: [])
|
3456
|
+
@keyword = keyword
|
3073
3457
|
@value = value
|
3074
3458
|
@consequent = consequent
|
3075
3459
|
@location = location
|
@@ -3077,11 +3461,13 @@ class SyntaxTree < Ripper
|
|
3077
3461
|
end
|
3078
3462
|
|
3079
3463
|
def child_nodes
|
3080
|
-
[value, consequent]
|
3464
|
+
[keyword, value, consequent]
|
3081
3465
|
end
|
3082
3466
|
|
3083
3467
|
def format(q)
|
3084
|
-
q.group
|
3468
|
+
q.group do
|
3469
|
+
q.format(keyword)
|
3470
|
+
|
3085
3471
|
if value
|
3086
3472
|
q.text(" ")
|
3087
3473
|
q.format(value)
|
@@ -3090,6 +3476,8 @@ class SyntaxTree < Ripper
|
|
3090
3476
|
q.breakable(force: true)
|
3091
3477
|
q.format(consequent)
|
3092
3478
|
q.breakable(force: true)
|
3479
|
+
|
3480
|
+
q.text("end")
|
3093
3481
|
end
|
3094
3482
|
end
|
3095
3483
|
|
@@ -3097,6 +3485,9 @@ class SyntaxTree < Ripper
|
|
3097
3485
|
q.group(2, "(", ")") do
|
3098
3486
|
q.text("case")
|
3099
3487
|
|
3488
|
+
q.breakable
|
3489
|
+
q.pp(keyword)
|
3490
|
+
|
3100
3491
|
if value
|
3101
3492
|
q.breakable
|
3102
3493
|
q.pp(value)
|
@@ -3204,6 +3595,7 @@ class SyntaxTree < Ripper
|
|
3204
3595
|
tokens.delete(keyword)
|
3205
3596
|
|
3206
3597
|
Case.new(
|
3598
|
+
keyword: keyword,
|
3207
3599
|
value: value,
|
3208
3600
|
consequent: consequent,
|
3209
3601
|
location: keyword.location.to(consequent.location)
|
@@ -3484,7 +3876,7 @@ class SyntaxTree < Ripper
|
|
3484
3876
|
# [Const | Ident | Op] the message being send
|
3485
3877
|
attr_reader :message
|
3486
3878
|
|
3487
|
-
# [Args] the arguments going along with the message
|
3879
|
+
# [nil | Args] the arguments going along with the message
|
3488
3880
|
attr_reader :arguments
|
3489
3881
|
|
3490
3882
|
# [Location] the location of this node
|
@@ -3515,16 +3907,16 @@ class SyntaxTree < Ripper
|
|
3515
3907
|
|
3516
3908
|
def format(q)
|
3517
3909
|
q.group do
|
3518
|
-
doc =
|
3519
|
-
|
3520
|
-
|
3521
|
-
|
3910
|
+
doc =
|
3911
|
+
q.nest(0) do
|
3912
|
+
q.format(receiver)
|
3913
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
3914
|
+
q.format(message)
|
3915
|
+
end
|
3522
3916
|
|
3523
|
-
|
3524
|
-
|
3525
|
-
q.
|
3526
|
-
else
|
3527
|
-
q.nest(width) { q.format(arguments) }
|
3917
|
+
if arguments
|
3918
|
+
q.text(" ")
|
3919
|
+
q.nest(argument_alignment(q, doc)) { q.format(arguments) }
|
3528
3920
|
end
|
3529
3921
|
end
|
3530
3922
|
end
|
@@ -3542,8 +3934,10 @@ class SyntaxTree < Ripper
|
|
3542
3934
|
q.breakable
|
3543
3935
|
q.pp(message)
|
3544
3936
|
|
3545
|
-
|
3546
|
-
|
3937
|
+
if arguments
|
3938
|
+
q.breakable
|
3939
|
+
q.pp(arguments)
|
3940
|
+
end
|
3547
3941
|
|
3548
3942
|
q.pp(Comment::List.new(comments))
|
3549
3943
|
end
|
@@ -3577,16 +3971,38 @@ class SyntaxTree < Ripper
|
|
3577
3971
|
when PrettyPrint::Text
|
3578
3972
|
width += doc.width
|
3579
3973
|
when PrettyPrint::Indent, PrettyPrint::Align, PrettyPrint::Group
|
3580
|
-
queue
|
3974
|
+
queue = doc.contents + queue
|
3581
3975
|
when PrettyPrint::IfBreak
|
3582
|
-
queue
|
3976
|
+
queue = doc.break_contents + queue
|
3583
3977
|
when PrettyPrint::Breakable
|
3584
|
-
width =
|
3978
|
+
width = 0
|
3585
3979
|
end
|
3586
3980
|
end
|
3587
3981
|
|
3588
3982
|
width
|
3589
3983
|
end
|
3984
|
+
|
3985
|
+
def argument_alignment(q, doc)
|
3986
|
+
# Very special handling case for rspec matchers. In general with rspec
|
3987
|
+
# matchers you expect to see something like:
|
3988
|
+
#
|
3989
|
+
# expect(foo).to receive(:bar).with(
|
3990
|
+
# 'one',
|
3991
|
+
# 'two',
|
3992
|
+
# 'three',
|
3993
|
+
# 'four',
|
3994
|
+
# 'five'
|
3995
|
+
# )
|
3996
|
+
#
|
3997
|
+
# In this case the arguments are aligned to the left side as opposed to
|
3998
|
+
# being aligned with the `receive` call.
|
3999
|
+
if %w[to not_to to_not].include?(message.value)
|
4000
|
+
0
|
4001
|
+
else
|
4002
|
+
width = doc_width(doc) + 1
|
4003
|
+
width > (q.maxwidth / 2) ? 0 : width
|
4004
|
+
end
|
4005
|
+
end
|
3590
4006
|
end
|
3591
4007
|
|
3592
4008
|
# :call-seq:
|
@@ -3594,7 +4010,7 @@ class SyntaxTree < Ripper
|
|
3594
4010
|
# untyped receiver,
|
3595
4011
|
# (:"::" | Op | Period) operator,
|
3596
4012
|
# (Const | Ident | Op) message,
|
3597
|
-
# Args arguments
|
4013
|
+
# (nil | Args) arguments
|
3598
4014
|
# ) -> CommandCall
|
3599
4015
|
def on_command_call(receiver, operator, message, arguments)
|
3600
4016
|
ending = arguments || message
|
@@ -3665,10 +4081,18 @@ class SyntaxTree < Ripper
|
|
3665
4081
|
@trailing
|
3666
4082
|
end
|
3667
4083
|
|
4084
|
+
def ignore?
|
4085
|
+
value[1..-1].strip == "stree-ignore"
|
4086
|
+
end
|
4087
|
+
|
3668
4088
|
def comments
|
3669
4089
|
[]
|
3670
4090
|
end
|
3671
4091
|
|
4092
|
+
def child_nodes
|
4093
|
+
[]
|
4094
|
+
end
|
4095
|
+
|
3672
4096
|
def format(q)
|
3673
4097
|
q.text(value)
|
3674
4098
|
end
|
@@ -4062,14 +4486,7 @@ class SyntaxTree < Ripper
|
|
4062
4486
|
q.group do
|
4063
4487
|
q.text("def ")
|
4064
4488
|
q.format(name)
|
4065
|
-
|
4066
|
-
if params.is_a?(Params) && !params.empty?
|
4067
|
-
q.text("(")
|
4068
|
-
q.format(params)
|
4069
|
-
q.text(")")
|
4070
|
-
else
|
4071
|
-
q.format(params)
|
4072
|
-
end
|
4489
|
+
q.format(params) if !params.is_a?(Params) || !params.empty?
|
4073
4490
|
end
|
4074
4491
|
|
4075
4492
|
unless bodystmt.empty?
|
@@ -4118,10 +4535,16 @@ class SyntaxTree < Ripper
|
|
4118
4535
|
# def method = result
|
4119
4536
|
#
|
4120
4537
|
class DefEndless
|
4538
|
+
# [untyped] the target where the method is being defined
|
4539
|
+
attr_reader :target
|
4540
|
+
|
4541
|
+
# [Op | Period] the operator being used to declare the method
|
4542
|
+
attr_reader :operator
|
4543
|
+
|
4121
4544
|
# [Backtick | Const | Ident | Kw | Op] the name of the method
|
4122
4545
|
attr_reader :name
|
4123
4546
|
|
4124
|
-
# [nil | Paren] the parameter declaration for the method
|
4547
|
+
# [nil | Params | Paren] the parameter declaration for the method
|
4125
4548
|
attr_reader :paren
|
4126
4549
|
|
4127
4550
|
# [untyped] the expression to be executed by the method
|
@@ -4133,7 +4556,17 @@ class SyntaxTree < Ripper
|
|
4133
4556
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
4134
4557
|
attr_reader :comments
|
4135
4558
|
|
4136
|
-
def initialize(
|
4559
|
+
def initialize(
|
4560
|
+
target:,
|
4561
|
+
operator:,
|
4562
|
+
name:,
|
4563
|
+
paren:,
|
4564
|
+
statement:,
|
4565
|
+
location:,
|
4566
|
+
comments: []
|
4567
|
+
)
|
4568
|
+
@target = target
|
4569
|
+
@operator = operator
|
4137
4570
|
@name = name
|
4138
4571
|
@paren = paren
|
4139
4572
|
@statement = statement
|
@@ -4142,14 +4575,26 @@ class SyntaxTree < Ripper
|
|
4142
4575
|
end
|
4143
4576
|
|
4144
4577
|
def child_nodes
|
4145
|
-
[name, paren, statement]
|
4578
|
+
[target, operator, name, paren, statement]
|
4146
4579
|
end
|
4147
4580
|
|
4148
4581
|
def format(q)
|
4149
4582
|
q.group do
|
4150
4583
|
q.text("def ")
|
4584
|
+
|
4585
|
+
if target
|
4586
|
+
q.format(target)
|
4587
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
4588
|
+
end
|
4589
|
+
|
4151
4590
|
q.format(name)
|
4152
|
-
|
4591
|
+
|
4592
|
+
if paren
|
4593
|
+
params = paren
|
4594
|
+
params = params.contents if params.is_a?(Paren)
|
4595
|
+
q.format(paren) unless params.empty?
|
4596
|
+
end
|
4597
|
+
|
4153
4598
|
q.text(" =")
|
4154
4599
|
q.group do
|
4155
4600
|
q.indent do
|
@@ -4164,6 +4609,14 @@ class SyntaxTree < Ripper
|
|
4164
4609
|
q.group(2, "(", ")") do
|
4165
4610
|
q.text("def_endless")
|
4166
4611
|
|
4612
|
+
if target
|
4613
|
+
q.breakable
|
4614
|
+
q.pp(target)
|
4615
|
+
|
4616
|
+
q.breakable
|
4617
|
+
q.pp(operator)
|
4618
|
+
end
|
4619
|
+
|
4167
4620
|
q.breakable
|
4168
4621
|
q.pp(name)
|
4169
4622
|
|
@@ -4206,19 +4659,6 @@ class SyntaxTree < Ripper
|
|
4206
4659
|
# and normal method definitions.
|
4207
4660
|
beginning = find_token(Kw, "def")
|
4208
4661
|
|
4209
|
-
# If we don't have a bodystmt node, then we have a single-line method
|
4210
|
-
unless bodystmt.is_a?(BodyStmt)
|
4211
|
-
node =
|
4212
|
-
DefEndless.new(
|
4213
|
-
name: name,
|
4214
|
-
paren: params,
|
4215
|
-
statement: bodystmt,
|
4216
|
-
location: beginning.location.to(bodystmt.location)
|
4217
|
-
)
|
4218
|
-
|
4219
|
-
return node
|
4220
|
-
end
|
4221
|
-
|
4222
4662
|
# If there aren't any params then we need to correct the params node
|
4223
4663
|
# location information
|
4224
4664
|
if params.is_a?(Params) && params.empty?
|
@@ -4234,18 +4674,35 @@ class SyntaxTree < Ripper
|
|
4234
4674
|
params = Params.new(location: location)
|
4235
4675
|
end
|
4236
4676
|
|
4237
|
-
ending = find_token(Kw, "end")
|
4238
|
-
bodystmt.bind(
|
4239
|
-
find_next_statement_start(params.location.end_char),
|
4240
|
-
ending.location.start_char
|
4241
|
-
)
|
4677
|
+
ending = find_token(Kw, "end", consume: false)
|
4242
4678
|
|
4243
|
-
|
4244
|
-
|
4245
|
-
|
4246
|
-
|
4247
|
-
|
4248
|
-
|
4679
|
+
if ending
|
4680
|
+
tokens.delete(ending)
|
4681
|
+
bodystmt.bind(
|
4682
|
+
find_next_statement_start(params.location.end_char),
|
4683
|
+
ending.location.start_char
|
4684
|
+
)
|
4685
|
+
|
4686
|
+
Def.new(
|
4687
|
+
name: name,
|
4688
|
+
params: params,
|
4689
|
+
bodystmt: bodystmt,
|
4690
|
+
location: beginning.location.to(ending.location)
|
4691
|
+
)
|
4692
|
+
else
|
4693
|
+
# In Ruby >= 3.1.0, this is a BodyStmt that wraps a single statement in
|
4694
|
+
# the statements list. Before, it was just the individual statement.
|
4695
|
+
statement = bodystmt.is_a?(BodyStmt) ? bodystmt.statements : bodystmt
|
4696
|
+
|
4697
|
+
DefEndless.new(
|
4698
|
+
target: nil,
|
4699
|
+
operator: nil,
|
4700
|
+
name: name,
|
4701
|
+
paren: params,
|
4702
|
+
statement: statement,
|
4703
|
+
location: beginning.location.to(bodystmt.location)
|
4704
|
+
)
|
4705
|
+
end
|
4249
4706
|
end
|
4250
4707
|
|
4251
4708
|
# Defined represents the use of the +defined?+ operator. It can be used with
|
@@ -4369,16 +4826,9 @@ class SyntaxTree < Ripper
|
|
4369
4826
|
q.group do
|
4370
4827
|
q.text("def ")
|
4371
4828
|
q.format(target)
|
4372
|
-
q.format(CallOperatorFormatter.new(operator))
|
4829
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
4373
4830
|
q.format(name)
|
4374
|
-
|
4375
|
-
if params.is_a?(Params) && !params.empty?
|
4376
|
-
q.text("(")
|
4377
|
-
q.format(params)
|
4378
|
-
q.text(")")
|
4379
|
-
else
|
4380
|
-
q.format(params)
|
4381
|
-
end
|
4831
|
+
q.format(params) if !params.is_a?(Params) || !params.empty?
|
4382
4832
|
end
|
4383
4833
|
|
4384
4834
|
unless bodystmt.empty?
|
@@ -4460,21 +4910,37 @@ class SyntaxTree < Ripper
|
|
4460
4910
|
end
|
4461
4911
|
|
4462
4912
|
beginning = find_token(Kw, "def")
|
4463
|
-
ending = find_token(Kw, "end")
|
4913
|
+
ending = find_token(Kw, "end", consume: false)
|
4464
4914
|
|
4465
|
-
|
4466
|
-
|
4467
|
-
|
4468
|
-
|
4915
|
+
if ending
|
4916
|
+
tokens.delete(ending)
|
4917
|
+
bodystmt.bind(
|
4918
|
+
find_next_statement_start(params.location.end_char),
|
4919
|
+
ending.location.start_char
|
4920
|
+
)
|
4469
4921
|
|
4470
|
-
|
4471
|
-
|
4472
|
-
|
4473
|
-
|
4474
|
-
|
4475
|
-
|
4476
|
-
|
4477
|
-
|
4922
|
+
Defs.new(
|
4923
|
+
target: target,
|
4924
|
+
operator: operator,
|
4925
|
+
name: name,
|
4926
|
+
params: params,
|
4927
|
+
bodystmt: bodystmt,
|
4928
|
+
location: beginning.location.to(ending.location)
|
4929
|
+
)
|
4930
|
+
else
|
4931
|
+
# In Ruby >= 3.1.0, this is a BodyStmt that wraps a single statement in
|
4932
|
+
# the statements list. Before, it was just the individual statement.
|
4933
|
+
statement = bodystmt.is_a?(BodyStmt) ? bodystmt.statements : bodystmt
|
4934
|
+
|
4935
|
+
DefEndless.new(
|
4936
|
+
target: target,
|
4937
|
+
operator: operator,
|
4938
|
+
name: name,
|
4939
|
+
paren: params,
|
4940
|
+
statement: statement,
|
4941
|
+
location: beginning.location.to(bodystmt.location)
|
4942
|
+
)
|
4943
|
+
end
|
4478
4944
|
end
|
4479
4945
|
|
4480
4946
|
# DoBlock represents passing a block to a method call using the +do+ and +end+
|
@@ -4512,7 +4978,7 @@ class SyntaxTree < Ripper
|
|
4512
4978
|
end
|
4513
4979
|
|
4514
4980
|
def format(q)
|
4515
|
-
BlockFormatter.new(self, keyword, bodystmt).format(q)
|
4981
|
+
BlockFormatter.new(self, keyword, "end", bodystmt).format(q)
|
4516
4982
|
end
|
4517
4983
|
|
4518
4984
|
def pretty_print(q)
|
@@ -4576,8 +5042,7 @@ class SyntaxTree < Ripper
|
|
4576
5042
|
end
|
4577
5043
|
|
4578
5044
|
def format(q)
|
4579
|
-
|
4580
|
-
space = parent.is_a?(If) || parent.is_a?(Unless)
|
5045
|
+
space = [If, IfMod, Unless, UnlessMod].include?(q.parent.class)
|
4581
5046
|
|
4582
5047
|
left = node.left
|
4583
5048
|
right = node.right
|
@@ -4890,9 +5355,9 @@ class SyntaxTree < Ripper
|
|
4890
5355
|
matching = Quotes.matching(quote[2])
|
4891
5356
|
pattern = /[\n#{Regexp.escape(matching)}'"]/
|
4892
5357
|
|
4893
|
-
if parts.any?
|
5358
|
+
if parts.any? { |part|
|
4894
5359
|
part.is_a?(TStringContent) && part.value.match?(pattern)
|
4895
|
-
|
5360
|
+
}
|
4896
5361
|
[quote, matching]
|
4897
5362
|
elsif Quotes.locked?(self)
|
4898
5363
|
["#{":" unless hash_key}'", "'"]
|
@@ -4917,7 +5382,7 @@ class SyntaxTree < Ripper
|
|
4917
5382
|
if find_token(SymBeg, consume: false)
|
4918
5383
|
# A normal dynamic symbol
|
4919
5384
|
symbeg = find_token(SymBeg)
|
4920
|
-
tstring_end = find_token(TStringEnd)
|
5385
|
+
tstring_end = find_token(TStringEnd, location: symbeg.location)
|
4921
5386
|
|
4922
5387
|
DynaSymbol.new(
|
4923
5388
|
quote: symbeg.value,
|
@@ -5009,6 +5474,7 @@ class SyntaxTree < Ripper
|
|
5009
5474
|
|
5010
5475
|
node = tokens[index]
|
5011
5476
|
ending = node.value == "end" ? tokens.delete_at(index) : node
|
5477
|
+
# ending = node
|
5012
5478
|
|
5013
5479
|
statements.bind(beginning.location.end_char, ending.location.start_char)
|
5014
5480
|
|
@@ -5155,6 +5621,10 @@ class SyntaxTree < Ripper
|
|
5155
5621
|
false
|
5156
5622
|
end
|
5157
5623
|
|
5624
|
+
def ignore?
|
5625
|
+
false
|
5626
|
+
end
|
5627
|
+
|
5158
5628
|
def comments
|
5159
5629
|
[]
|
5160
5630
|
end
|
@@ -5480,24 +5950,29 @@ class SyntaxTree < Ripper
|
|
5480
5950
|
# [Const | Ident] the name of the method
|
5481
5951
|
attr_reader :value
|
5482
5952
|
|
5953
|
+
# [nil | ArgParen | Args] the arguments to the method call
|
5954
|
+
attr_reader :arguments
|
5955
|
+
|
5483
5956
|
# [Location] the location of this node
|
5484
5957
|
attr_reader :location
|
5485
5958
|
|
5486
5959
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
5487
5960
|
attr_reader :comments
|
5488
5961
|
|
5489
|
-
def initialize(value:, location:, comments: [])
|
5962
|
+
def initialize(value:, arguments:, location:, comments: [])
|
5490
5963
|
@value = value
|
5964
|
+
@arguments = arguments
|
5491
5965
|
@location = location
|
5492
5966
|
@comments = comments
|
5493
5967
|
end
|
5494
5968
|
|
5495
5969
|
def child_nodes
|
5496
|
-
[value]
|
5970
|
+
[value, arguments]
|
5497
5971
|
end
|
5498
5972
|
|
5499
5973
|
def format(q)
|
5500
5974
|
q.format(value)
|
5975
|
+
q.format(arguments)
|
5501
5976
|
end
|
5502
5977
|
|
5503
5978
|
def pretty_print(q)
|
@@ -5507,21 +5982,30 @@ class SyntaxTree < Ripper
|
|
5507
5982
|
q.breakable
|
5508
5983
|
q.pp(value)
|
5509
5984
|
|
5985
|
+
if arguments
|
5986
|
+
q.breakable
|
5987
|
+
q.pp(arguments)
|
5988
|
+
end
|
5989
|
+
|
5510
5990
|
q.pp(Comment::List.new(comments))
|
5511
5991
|
end
|
5512
5992
|
end
|
5513
5993
|
|
5514
5994
|
def to_json(*opts)
|
5515
|
-
{
|
5516
|
-
|
5517
|
-
|
5995
|
+
{
|
5996
|
+
type: :fcall,
|
5997
|
+
value: value,
|
5998
|
+
args: arguments,
|
5999
|
+
loc: location,
|
6000
|
+
cmts: comments
|
6001
|
+
}.to_json(*opts)
|
5518
6002
|
end
|
5519
6003
|
end
|
5520
6004
|
|
5521
6005
|
# :call-seq:
|
5522
6006
|
# on_fcall: ((Const | Ident) value) -> FCall
|
5523
6007
|
def on_fcall(value)
|
5524
|
-
FCall.new(value: value, location: value.location)
|
6008
|
+
FCall.new(value: value, arguments: nil, location: value.location)
|
5525
6009
|
end
|
5526
6010
|
|
5527
6011
|
# Field is always the child of an assignment. It represents assigning to a
|
@@ -5560,7 +6044,7 @@ class SyntaxTree < Ripper
|
|
5560
6044
|
def format(q)
|
5561
6045
|
q.group do
|
5562
6046
|
q.format(parent)
|
5563
|
-
q.format(CallOperatorFormatter.new(operator))
|
6047
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
5564
6048
|
q.format(name)
|
5565
6049
|
end
|
5566
6050
|
end
|
@@ -5864,6 +6348,7 @@ class SyntaxTree < Ripper
|
|
5864
6348
|
# ) -> For
|
5865
6349
|
def on_for(index, collection, statements)
|
5866
6350
|
beginning = find_token(Kw, "for")
|
6351
|
+
in_keyword = find_token(Kw, "in")
|
5867
6352
|
ending = find_token(Kw, "end")
|
5868
6353
|
|
5869
6354
|
# Consume the do keyword if it exists so that it doesn't get confused for
|
@@ -5879,6 +6364,11 @@ class SyntaxTree < Ripper
|
|
5879
6364
|
ending.location.start_char
|
5880
6365
|
)
|
5881
6366
|
|
6367
|
+
if index.is_a?(MLHS)
|
6368
|
+
comma_range = index.location.end_char...in_keyword.location.start_char
|
6369
|
+
index.comma = true if source[comma_range].strip.start_with?(",")
|
6370
|
+
end
|
6371
|
+
|
5882
6372
|
For.new(
|
5883
6373
|
index: index,
|
5884
6374
|
collection: collection,
|
@@ -5947,6 +6437,9 @@ class SyntaxTree < Ripper
|
|
5947
6437
|
# { key => value }
|
5948
6438
|
#
|
5949
6439
|
class HashLiteral
|
6440
|
+
# [LBrace] the left brace that opens this hash
|
6441
|
+
attr_reader :lbrace
|
6442
|
+
|
5950
6443
|
# [Array[ AssocNew | AssocSplat ]] the optional contents of the hash
|
5951
6444
|
attr_reader :assocs
|
5952
6445
|
|
@@ -5956,28 +6449,27 @@ class SyntaxTree < Ripper
|
|
5956
6449
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
5957
6450
|
attr_reader :comments
|
5958
6451
|
|
5959
|
-
def initialize(assocs:, location:, comments: [])
|
6452
|
+
def initialize(lbrace:, assocs:, location:, comments: [])
|
6453
|
+
@lbrace = lbrace
|
5960
6454
|
@assocs = assocs
|
5961
6455
|
@location = location
|
5962
6456
|
@comments = comments
|
5963
6457
|
end
|
5964
6458
|
|
5965
6459
|
def child_nodes
|
5966
|
-
assocs
|
6460
|
+
[lbrace] + assocs
|
5967
6461
|
end
|
5968
6462
|
|
5969
6463
|
def format(q)
|
5970
|
-
|
5971
|
-
q
|
5972
|
-
|
5973
|
-
|
5974
|
-
q.format(HashFormatter.for(self))
|
5975
|
-
end
|
5976
|
-
q.breakable
|
5977
|
-
q.text("}")
|
6464
|
+
if q.parent.is_a?(Assoc)
|
6465
|
+
format_contents(q)
|
6466
|
+
else
|
6467
|
+
q.group { format_contents(q) }
|
5978
6468
|
end
|
6469
|
+
end
|
5979
6470
|
|
5980
|
-
|
6471
|
+
def format_key(q, key)
|
6472
|
+
(@key_formatter ||= HashKeyFormatter.for(self)).format_key(q, key)
|
5981
6473
|
end
|
5982
6474
|
|
5983
6475
|
def pretty_print(q)
|
@@ -5998,6 +6490,24 @@ class SyntaxTree < Ripper
|
|
5998
6490
|
*opts
|
5999
6491
|
)
|
6000
6492
|
end
|
6493
|
+
|
6494
|
+
private
|
6495
|
+
|
6496
|
+
def format_contents(q)
|
6497
|
+
q.format(lbrace)
|
6498
|
+
|
6499
|
+
if assocs.empty?
|
6500
|
+
q.breakable("")
|
6501
|
+
else
|
6502
|
+
q.indent do
|
6503
|
+
q.breakable
|
6504
|
+
q.seplist(assocs) { |assoc| q.format(assoc) }
|
6505
|
+
end
|
6506
|
+
q.breakable
|
6507
|
+
end
|
6508
|
+
|
6509
|
+
q.text("}")
|
6510
|
+
end
|
6001
6511
|
end
|
6002
6512
|
|
6003
6513
|
# :call-seq:
|
@@ -6007,6 +6517,7 @@ class SyntaxTree < Ripper
|
|
6007
6517
|
rbrace = find_token(RBrace)
|
6008
6518
|
|
6009
6519
|
HashLiteral.new(
|
6520
|
+
lbrace: lbrace,
|
6010
6521
|
assocs: assocs || [],
|
6011
6522
|
location: lbrace.location.to(rbrace.location)
|
6012
6523
|
)
|
@@ -6059,7 +6570,7 @@ class SyntaxTree < Ripper
|
|
6059
6570
|
q.group do
|
6060
6571
|
q.format(beginning)
|
6061
6572
|
|
6062
|
-
q.line_suffix do
|
6573
|
+
q.line_suffix(priority: Formatter::HEREDOC_PRIORITY) do
|
6063
6574
|
q.group do
|
6064
6575
|
breakable.call
|
6065
6576
|
|
@@ -6282,7 +6793,9 @@ class SyntaxTree < Ripper
|
|
6282
6793
|
def format(q)
|
6283
6794
|
parts = keywords.map { |(key, value)| KeywordFormatter.new(key, value) }
|
6284
6795
|
parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest
|
6285
|
-
contents = ->
|
6796
|
+
contents = -> do
|
6797
|
+
q.seplist(parts) { |part| q.format(part, stackable: false) }
|
6798
|
+
end
|
6286
6799
|
|
6287
6800
|
if constant
|
6288
6801
|
q.format(constant)
|
@@ -6293,7 +6806,7 @@ class SyntaxTree < Ripper
|
|
6293
6806
|
end
|
6294
6807
|
|
6295
6808
|
parent = q.parent
|
6296
|
-
if PATTERNS.
|
6809
|
+
if PATTERNS.include?(parent.class)
|
6297
6810
|
q.text("{ ")
|
6298
6811
|
contents.call
|
6299
6812
|
q.text(" }")
|
@@ -6428,6 +6941,23 @@ class SyntaxTree < Ripper
|
|
6428
6941
|
)
|
6429
6942
|
end
|
6430
6943
|
|
6944
|
+
# If the predicate of a conditional or loop contains an assignment (in which
|
6945
|
+
# case we can't know for certain that that assignment doesn't impact the
|
6946
|
+
# statements inside the conditional) then we can't use the modifier form
|
6947
|
+
# and we must use the block form.
|
6948
|
+
module ContainsAssignment
|
6949
|
+
def self.call(parent)
|
6950
|
+
queue = [parent]
|
6951
|
+
|
6952
|
+
while node = queue.shift
|
6953
|
+
return true if [Assign, MAssign, OpAssign].include?(node.class)
|
6954
|
+
queue += node.child_nodes
|
6955
|
+
end
|
6956
|
+
|
6957
|
+
false
|
6958
|
+
end
|
6959
|
+
end
|
6960
|
+
|
6431
6961
|
# Formats an If or Unless node.
|
6432
6962
|
class ConditionalFormatter
|
6433
6963
|
# [String] the keyword associated with this conditional
|
@@ -6442,38 +6972,50 @@ class SyntaxTree < Ripper
|
|
6442
6972
|
end
|
6443
6973
|
|
6444
6974
|
def format(q)
|
6445
|
-
|
6446
|
-
|
6447
|
-
|
6448
|
-
|
6975
|
+
# If the predicate of the conditional contains an assignment (in which
|
6976
|
+
# case we can't know for certain that that assignment doesn't impact the
|
6977
|
+
# statements inside the conditional) then we can't use the modifier form
|
6978
|
+
# and we must use the block form.
|
6979
|
+
if ContainsAssignment.call(node.predicate)
|
6980
|
+
format_break(q, force: true)
|
6981
|
+
return
|
6982
|
+
end
|
6449
6983
|
|
6450
|
-
|
6451
|
-
|
6452
|
-
|
6453
|
-
|
6984
|
+
if node.consequent || node.statements.empty?
|
6985
|
+
q.group { format_break(q, force: true) }
|
6986
|
+
else
|
6987
|
+
q.group do
|
6988
|
+
q.if_break { format_break(q, force: false) }.if_flat do
|
6989
|
+
Parentheses.flat(q) do
|
6990
|
+
q.format(node.statements)
|
6991
|
+
q.text(" #{keyword} ")
|
6992
|
+
q.format(node.predicate)
|
6993
|
+
end
|
6454
6994
|
end
|
6455
6995
|
end
|
6996
|
+
end
|
6997
|
+
end
|
6998
|
+
|
6999
|
+
private
|
7000
|
+
|
7001
|
+
def format_break(q, force:)
|
7002
|
+
q.text("#{keyword} ")
|
7003
|
+
q.nest(keyword.length + 1) { q.format(node.predicate) }
|
6456
7004
|
|
6457
|
-
|
7005
|
+
unless node.statements.empty?
|
7006
|
+
q.indent do
|
6458
7007
|
q.breakable(force: force)
|
6459
|
-
q.format(node.
|
7008
|
+
q.format(node.statements)
|
6460
7009
|
end
|
7010
|
+
end
|
6461
7011
|
|
7012
|
+
if node.consequent
|
6462
7013
|
q.breakable(force: force)
|
6463
|
-
q.
|
7014
|
+
q.format(node.consequent)
|
6464
7015
|
end
|
6465
7016
|
|
6466
|
-
|
6467
|
-
|
6468
|
-
else
|
6469
|
-
q.group do
|
6470
|
-
q.if_break { break_format.call(force: false) }.if_flat do
|
6471
|
-
q.format(node.statements)
|
6472
|
-
q.text(" #{keyword} ")
|
6473
|
-
q.format(node.predicate)
|
6474
|
-
end
|
6475
|
-
end
|
6476
|
-
end
|
7017
|
+
q.breakable(force: force)
|
7018
|
+
q.text("end")
|
6477
7019
|
end
|
6478
7020
|
end
|
6479
7021
|
|
@@ -6604,38 +7146,19 @@ class SyntaxTree < Ripper
|
|
6604
7146
|
end
|
6605
7147
|
|
6606
7148
|
def format(q)
|
6607
|
-
|
6608
|
-
|
6609
|
-
|
6610
|
-
|
6611
|
-
|
6612
|
-
|
6613
|
-
q.breakable
|
6614
|
-
q.format(truthy)
|
6615
|
-
end
|
6616
|
-
|
6617
|
-
q.breakable
|
6618
|
-
q.text("else")
|
6619
|
-
|
6620
|
-
q.indent do
|
6621
|
-
q.breakable
|
6622
|
-
q.format(falsy)
|
6623
|
-
end
|
6624
|
-
|
6625
|
-
q.breakable
|
6626
|
-
q.text("end")
|
6627
|
-
end.if_flat do
|
6628
|
-
q.format(predicate)
|
6629
|
-
q.text(" ?")
|
6630
|
-
|
6631
|
-
q.breakable
|
6632
|
-
q.format(truthy)
|
6633
|
-
q.text(" :")
|
7149
|
+
force_flat = [
|
7150
|
+
Alias, Assign, Break, Command, CommandCall, Heredoc, If, IfMod, IfOp,
|
7151
|
+
Lambda, MAssign, Next, OpAssign, RescueMod, Return, Return0, Super,
|
7152
|
+
Undef, Unless, UnlessMod, UntilMod, VarAlias, VoidStmt, WhileMod, Yield,
|
7153
|
+
Yield0, ZSuper
|
7154
|
+
]
|
6634
7155
|
|
6635
|
-
|
6636
|
-
|
6637
|
-
|
7156
|
+
if force_flat.include?(truthy.class) || force_flat.include?(falsy.class)
|
7157
|
+
q.group { format_flat(q) }
|
7158
|
+
return
|
6638
7159
|
end
|
7160
|
+
|
7161
|
+
q.group { q.if_break { format_break(q) }.if_flat { format_flat(q) } }
|
6639
7162
|
end
|
6640
7163
|
|
6641
7164
|
def pretty_print(q)
|
@@ -6665,6 +7188,43 @@ class SyntaxTree < Ripper
|
|
6665
7188
|
cmts: comments
|
6666
7189
|
}.to_json(*opts)
|
6667
7190
|
end
|
7191
|
+
|
7192
|
+
private
|
7193
|
+
|
7194
|
+
def format_break(q)
|
7195
|
+
Parentheses.break(q) do
|
7196
|
+
q.text("if ")
|
7197
|
+
q.nest("if ".length) { q.format(predicate) }
|
7198
|
+
|
7199
|
+
q.indent do
|
7200
|
+
q.breakable
|
7201
|
+
q.format(truthy)
|
7202
|
+
end
|
7203
|
+
|
7204
|
+
q.breakable
|
7205
|
+
q.text("else")
|
7206
|
+
|
7207
|
+
q.indent do
|
7208
|
+
q.breakable
|
7209
|
+
q.format(falsy)
|
7210
|
+
end
|
7211
|
+
|
7212
|
+
q.breakable
|
7213
|
+
q.text("end")
|
7214
|
+
end
|
7215
|
+
end
|
7216
|
+
|
7217
|
+
def format_flat(q)
|
7218
|
+
q.format(predicate)
|
7219
|
+
q.text(" ?")
|
7220
|
+
|
7221
|
+
q.breakable
|
7222
|
+
q.format(truthy)
|
7223
|
+
q.text(" :")
|
7224
|
+
|
7225
|
+
q.breakable
|
7226
|
+
q.format(falsy)
|
7227
|
+
end
|
6668
7228
|
end
|
6669
7229
|
|
6670
7230
|
# :call-seq:
|
@@ -6692,21 +7252,31 @@ class SyntaxTree < Ripper
|
|
6692
7252
|
end
|
6693
7253
|
|
6694
7254
|
def format(q)
|
6695
|
-
|
6696
|
-
q.
|
6697
|
-
|
6698
|
-
|
6699
|
-
|
6700
|
-
|
6701
|
-
|
6702
|
-
|
6703
|
-
|
6704
|
-
|
6705
|
-
|
6706
|
-
|
6707
|
-
|
6708
|
-
|
6709
|
-
|
7255
|
+
if ContainsAssignment.call(node.statement)
|
7256
|
+
q.group { format_flat(q) }
|
7257
|
+
else
|
7258
|
+
q.group { q.if_break { format_break(q) }.if_flat { format_flat(q) } }
|
7259
|
+
end
|
7260
|
+
end
|
7261
|
+
|
7262
|
+
private
|
7263
|
+
|
7264
|
+
def format_break(q)
|
7265
|
+
q.text("#{keyword} ")
|
7266
|
+
q.nest(keyword.length + 1) { q.format(node.predicate) }
|
7267
|
+
q.indent do
|
7268
|
+
q.breakable
|
7269
|
+
q.format(node.statement)
|
7270
|
+
end
|
7271
|
+
q.breakable
|
7272
|
+
q.text("end")
|
7273
|
+
end
|
7274
|
+
|
7275
|
+
def format_flat(q)
|
7276
|
+
Parentheses.flat(q) do
|
7277
|
+
q.format(node.statement)
|
7278
|
+
q.text(" #{keyword} ")
|
7279
|
+
q.format(node.predicate)
|
6710
7280
|
end
|
6711
7281
|
end
|
6712
7282
|
end
|
@@ -6944,8 +7514,14 @@ class SyntaxTree < Ripper
|
|
6944
7514
|
beginning = find_token(Kw, "in")
|
6945
7515
|
ending = consequent || find_token(Kw, "end")
|
6946
7516
|
|
7517
|
+
statements_start = pattern
|
7518
|
+
if token = find_token(Kw, "then", consume: false)
|
7519
|
+
tokens.delete(token)
|
7520
|
+
statements_start = token
|
7521
|
+
end
|
7522
|
+
|
6947
7523
|
statements.bind(
|
6948
|
-
find_next_statement_start(
|
7524
|
+
find_next_statement_start(statements_start.location.end_char),
|
6949
7525
|
ending.location.start_char
|
6950
7526
|
)
|
6951
7527
|
|
@@ -6982,7 +7558,7 @@ class SyntaxTree < Ripper
|
|
6982
7558
|
end
|
6983
7559
|
|
6984
7560
|
def format(q)
|
6985
|
-
if !value.start_with?(
|
7561
|
+
if !value.start_with?(/\+?0/) && value.length >= 5 && !value.include?("_")
|
6986
7562
|
# If it's a plain integer and it doesn't have any underscores separating
|
6987
7563
|
# the values, then we're going to insert them every 3 characters
|
6988
7564
|
# starting from the right.
|
@@ -7330,9 +7906,11 @@ class SyntaxTree < Ripper
|
|
7330
7906
|
if params.is_a?(Paren)
|
7331
7907
|
q.format(params) unless params.contents.empty?
|
7332
7908
|
elsif !params.empty?
|
7333
|
-
q.
|
7334
|
-
|
7335
|
-
|
7909
|
+
q.group do
|
7910
|
+
q.text("(")
|
7911
|
+
q.format(params)
|
7912
|
+
q.text(")")
|
7913
|
+
end
|
7336
7914
|
end
|
7337
7915
|
|
7338
7916
|
q.text(" ")
|
@@ -7391,8 +7969,11 @@ class SyntaxTree < Ripper
|
|
7391
7969
|
def on_lambda(params, statements)
|
7392
7970
|
beginning = find_token(TLambda)
|
7393
7971
|
|
7394
|
-
if
|
7395
|
-
|
7972
|
+
if tokens.any? { |token|
|
7973
|
+
token.is_a?(TLamBeg) &&
|
7974
|
+
token.location.start_char > beginning.location.start_char
|
7975
|
+
}
|
7976
|
+
opening = find_token(TLamBeg)
|
7396
7977
|
closing = find_token(RBrace)
|
7397
7978
|
else
|
7398
7979
|
opening = find_token(Kw, "do")
|
@@ -7472,9 +8053,38 @@ class SyntaxTree < Ripper
|
|
7472
8053
|
# [Location] the location of this node
|
7473
8054
|
attr_reader :location
|
7474
8055
|
|
7475
|
-
|
8056
|
+
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
8057
|
+
attr_reader :comments
|
8058
|
+
|
8059
|
+
def initialize(value:, location:, comments: [])
|
7476
8060
|
@value = value
|
7477
8061
|
@location = location
|
8062
|
+
@comments = comments
|
8063
|
+
end
|
8064
|
+
|
8065
|
+
def child_nodes
|
8066
|
+
[]
|
8067
|
+
end
|
8068
|
+
|
8069
|
+
def format(q)
|
8070
|
+
q.text(value)
|
8071
|
+
end
|
8072
|
+
|
8073
|
+
def pretty_print(q)
|
8074
|
+
q.group(2, "(", ")") do
|
8075
|
+
q.text("lbracket")
|
8076
|
+
|
8077
|
+
q.breakable
|
8078
|
+
q.pp(value)
|
8079
|
+
|
8080
|
+
q.pp(Comment::List.new(comments))
|
8081
|
+
end
|
8082
|
+
end
|
8083
|
+
|
8084
|
+
def to_json(*opts)
|
8085
|
+
{ type: :lbracket, value: value, loc: location, cmts: comments }.to_json(
|
8086
|
+
*opts
|
8087
|
+
)
|
7478
8088
|
end
|
7479
8089
|
end
|
7480
8090
|
|
@@ -7565,104 +8175,12 @@ class SyntaxTree < Ripper
|
|
7565
8175
|
#
|
7566
8176
|
# first, = value
|
7567
8177
|
#
|
7568
|
-
class MAssign
|
7569
|
-
# [MLHS | MLHSParen] the target of the multiple assignment
|
7570
|
-
attr_reader :target
|
7571
|
-
|
7572
|
-
# [untyped] the value being assigned
|
7573
|
-
attr_reader :value
|
7574
|
-
|
7575
|
-
# [Location] the location of this node
|
7576
|
-
attr_reader :location
|
7577
|
-
|
7578
|
-
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
7579
|
-
attr_reader :comments
|
7580
|
-
|
7581
|
-
def initialize(target:, value:, location:, comments: [])
|
7582
|
-
@target = target
|
7583
|
-
@value = value
|
7584
|
-
@location = location
|
7585
|
-
@comments = comments
|
7586
|
-
end
|
7587
|
-
|
7588
|
-
def child_nodes
|
7589
|
-
[target, value]
|
7590
|
-
end
|
7591
|
-
|
7592
|
-
def format(q)
|
7593
|
-
q.group do
|
7594
|
-
q.group do
|
7595
|
-
q.format(target)
|
7596
|
-
q.text(",") if target.is_a?(MLHS) && target.comma
|
7597
|
-
end
|
7598
|
-
|
7599
|
-
q.text(" =")
|
7600
|
-
q.indent do
|
7601
|
-
q.breakable
|
7602
|
-
q.format(value)
|
7603
|
-
end
|
7604
|
-
end
|
7605
|
-
end
|
7606
|
-
|
7607
|
-
def pretty_print(q)
|
7608
|
-
q.group(2, "(", ")") do
|
7609
|
-
q.text("massign")
|
7610
|
-
|
7611
|
-
q.breakable
|
7612
|
-
q.pp(target)
|
7613
|
-
|
7614
|
-
q.breakable
|
7615
|
-
q.pp(value)
|
7616
|
-
|
7617
|
-
q.pp(Comment::List.new(comments))
|
7618
|
-
end
|
7619
|
-
end
|
7620
|
-
|
7621
|
-
def to_json(*opts)
|
7622
|
-
{
|
7623
|
-
type: :massign,
|
7624
|
-
target: target,
|
7625
|
-
value: value,
|
7626
|
-
loc: location,
|
7627
|
-
cmts: comments
|
7628
|
-
}.to_json(*opts)
|
7629
|
-
end
|
7630
|
-
end
|
7631
|
-
|
7632
|
-
# :call-seq:
|
7633
|
-
# on_massign: ((MLHS | MLHSParen) target, untyped value) -> MAssign
|
7634
|
-
def on_massign(target, value)
|
7635
|
-
comma_range = target.location.end_char...value.location.start_char
|
7636
|
-
target.comma = true if source[comma_range].strip.start_with?(",")
|
7637
|
-
|
7638
|
-
MAssign.new(
|
7639
|
-
target: target,
|
7640
|
-
value: value,
|
7641
|
-
location: target.location.to(value.location)
|
7642
|
-
)
|
7643
|
-
end
|
7644
|
-
|
7645
|
-
# MethodAddArg represents a method call with arguments and parentheses.
|
7646
|
-
#
|
7647
|
-
# method(argument)
|
7648
|
-
#
|
7649
|
-
# MethodAddArg can also represent with a method on an object, as in:
|
7650
|
-
#
|
7651
|
-
# object.method(argument)
|
7652
|
-
#
|
7653
|
-
# Finally, MethodAddArg can represent calling a method with no receiver that
|
7654
|
-
# ends in a ?. In this case, the parser knows it's a method call and not a
|
7655
|
-
# local variable, so it uses a MethodAddArg node as opposed to a VCall node,
|
7656
|
-
# as in:
|
7657
|
-
#
|
7658
|
-
# method?
|
7659
|
-
#
|
7660
|
-
class MethodAddArg
|
7661
|
-
# [Call | FCall] the method call
|
7662
|
-
attr_reader :call
|
8178
|
+
class MAssign
|
8179
|
+
# [MLHS | MLHSParen] the target of the multiple assignment
|
8180
|
+
attr_reader :target
|
7663
8181
|
|
7664
|
-
# [
|
7665
|
-
attr_reader :
|
8182
|
+
# [untyped] the value being assigned
|
8183
|
+
attr_reader :value
|
7666
8184
|
|
7667
8185
|
# [Location] the location of this node
|
7668
8186
|
attr_reader :location
|
@@ -7670,32 +8188,37 @@ class SyntaxTree < Ripper
|
|
7670
8188
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
7671
8189
|
attr_reader :comments
|
7672
8190
|
|
7673
|
-
def initialize(
|
7674
|
-
@
|
7675
|
-
@
|
8191
|
+
def initialize(target:, value:, location:, comments: [])
|
8192
|
+
@target = target
|
8193
|
+
@value = value
|
7676
8194
|
@location = location
|
7677
8195
|
@comments = comments
|
7678
8196
|
end
|
7679
8197
|
|
7680
8198
|
def child_nodes
|
7681
|
-
[
|
8199
|
+
[target, value]
|
7682
8200
|
end
|
7683
8201
|
|
7684
8202
|
def format(q)
|
7685
|
-
q.
|
7686
|
-
|
7687
|
-
|
8203
|
+
q.group do
|
8204
|
+
q.group { q.format(target) }
|
8205
|
+
q.text(" =")
|
8206
|
+
q.indent do
|
8207
|
+
q.breakable
|
8208
|
+
q.format(value)
|
8209
|
+
end
|
8210
|
+
end
|
7688
8211
|
end
|
7689
8212
|
|
7690
8213
|
def pretty_print(q)
|
7691
8214
|
q.group(2, "(", ")") do
|
7692
|
-
q.text("
|
8215
|
+
q.text("massign")
|
7693
8216
|
|
7694
8217
|
q.breakable
|
7695
|
-
q.pp(
|
8218
|
+
q.pp(target)
|
7696
8219
|
|
7697
8220
|
q.breakable
|
7698
|
-
q.pp(
|
8221
|
+
q.pp(value)
|
7699
8222
|
|
7700
8223
|
q.pp(Comment::List.new(comments))
|
7701
8224
|
end
|
@@ -7703,25 +8226,48 @@ class SyntaxTree < Ripper
|
|
7703
8226
|
|
7704
8227
|
def to_json(*opts)
|
7705
8228
|
{
|
7706
|
-
type: :
|
7707
|
-
|
7708
|
-
|
8229
|
+
type: :massign,
|
8230
|
+
target: target,
|
8231
|
+
value: value,
|
7709
8232
|
loc: location,
|
7710
8233
|
cmts: comments
|
7711
8234
|
}.to_json(*opts)
|
7712
8235
|
end
|
7713
8236
|
end
|
7714
8237
|
|
8238
|
+
# :call-seq:
|
8239
|
+
# on_massign: ((MLHS | MLHSParen) target, untyped value) -> MAssign
|
8240
|
+
def on_massign(target, value)
|
8241
|
+
comma_range = target.location.end_char...value.location.start_char
|
8242
|
+
target.comma = true if source[comma_range].strip.start_with?(",")
|
8243
|
+
|
8244
|
+
MAssign.new(
|
8245
|
+
target: target,
|
8246
|
+
value: value,
|
8247
|
+
location: target.location.to(value.location)
|
8248
|
+
)
|
8249
|
+
end
|
8250
|
+
|
7715
8251
|
# :call-seq:
|
7716
8252
|
# on_method_add_arg: (
|
7717
8253
|
# (Call | FCall) call,
|
7718
8254
|
# (ArgParen | Args) arguments
|
7719
|
-
# ) ->
|
8255
|
+
# ) -> Call | FCall
|
7720
8256
|
def on_method_add_arg(call, arguments)
|
7721
8257
|
location = call.location
|
7722
|
-
location = location.to(arguments.location)
|
8258
|
+
location = location.to(arguments.location) if arguments.is_a?(ArgParen)
|
7723
8259
|
|
7724
|
-
|
8260
|
+
if call.is_a?(FCall)
|
8261
|
+
FCall.new(value: call.value, arguments: arguments, location: location)
|
8262
|
+
else
|
8263
|
+
Call.new(
|
8264
|
+
receiver: call.receiver,
|
8265
|
+
operator: call.operator,
|
8266
|
+
message: call.message,
|
8267
|
+
arguments: arguments,
|
8268
|
+
location: location
|
8269
|
+
)
|
8270
|
+
end
|
7725
8271
|
end
|
7726
8272
|
|
7727
8273
|
# MethodAddBlock represents a method call with a block argument.
|
@@ -7729,7 +8275,7 @@ class SyntaxTree < Ripper
|
|
7729
8275
|
# method {}
|
7730
8276
|
#
|
7731
8277
|
class MethodAddBlock
|
7732
|
-
# [Call | Command | CommandCall | FCall
|
8278
|
+
# [Call | Command | CommandCall | FCall] the method call
|
7733
8279
|
attr_reader :call
|
7734
8280
|
|
7735
8281
|
# [BraceBlock | DoBlock] the block being sent with the method call
|
@@ -7784,7 +8330,7 @@ class SyntaxTree < Ripper
|
|
7784
8330
|
|
7785
8331
|
# :call-seq:
|
7786
8332
|
# on_method_add_block: (
|
7787
|
-
# (Call | Command | CommandCall | FCall
|
8333
|
+
# (Call | Command | CommandCall | FCall) call,
|
7788
8334
|
# (BraceBlock | DoBlock) block
|
7789
8335
|
# ) -> MethodAddBlock
|
7790
8336
|
def on_method_add_block(call, block)
|
@@ -7809,7 +8355,6 @@ class SyntaxTree < Ripper
|
|
7809
8355
|
# list, which impacts destructuring. It's an attr_accessor so that while
|
7810
8356
|
# the syntax tree is being built it can be set by its parent node
|
7811
8357
|
attr_accessor :comma
|
7812
|
-
alias comma? comma
|
7813
8358
|
|
7814
8359
|
# [Location] the location of this node
|
7815
8360
|
attr_reader :location
|
@@ -7830,6 +8375,7 @@ class SyntaxTree < Ripper
|
|
7830
8375
|
|
7831
8376
|
def format(q)
|
7832
8377
|
q.seplist(parts) { |part| q.format(part) }
|
8378
|
+
q.text(",") if comma
|
7833
8379
|
end
|
7834
8380
|
|
7835
8381
|
def pretty_print(q)
|
@@ -7932,7 +8478,6 @@ class SyntaxTree < Ripper
|
|
7932
8478
|
q.indent do
|
7933
8479
|
q.breakable("")
|
7934
8480
|
q.format(contents)
|
7935
|
-
q.text(",") if contents.is_a?(MLHS) && contents.comma?
|
7936
8481
|
end
|
7937
8482
|
|
7938
8483
|
q.breakable("")
|
@@ -8400,6 +8945,61 @@ class SyntaxTree < Ripper
|
|
8400
8945
|
)
|
8401
8946
|
end
|
8402
8947
|
|
8948
|
+
# If you have a modifier statement (for instance a modifier if statement or a
|
8949
|
+
# modifier while loop) there are times when you need to wrap the entire
|
8950
|
+
# statement in parentheses. This occurs when you have something like:
|
8951
|
+
#
|
8952
|
+
# foo[:foo] =
|
8953
|
+
# if bar?
|
8954
|
+
# baz
|
8955
|
+
# end
|
8956
|
+
#
|
8957
|
+
# Normally we would shorten this to an inline version, which would result in:
|
8958
|
+
#
|
8959
|
+
# foo[:foo] = baz if bar?
|
8960
|
+
#
|
8961
|
+
# but this actually has different semantic meaning. The first example will
|
8962
|
+
# result in a nil being inserted into the hash for the :foo key, whereas the
|
8963
|
+
# second example will result in an empty hash because the if statement applies
|
8964
|
+
# to the entire assignment.
|
8965
|
+
#
|
8966
|
+
# We can fix this in a couple of ways. We can use the then keyword, as in:
|
8967
|
+
#
|
8968
|
+
# foo[:foo] = if bar? then baz end
|
8969
|
+
#
|
8970
|
+
# But this isn't used very often. We can also just leave it as is with the
|
8971
|
+
# multi-line version, but for a short predicate and short value it looks
|
8972
|
+
# verbose. The last option and the one used here is to add parentheses on
|
8973
|
+
# both sides of the expression, as in:
|
8974
|
+
#
|
8975
|
+
# foo[:foo] = (baz if bar?)
|
8976
|
+
#
|
8977
|
+
# This approach maintains the nice conciseness of the inline version, while
|
8978
|
+
# keeping the correct semantic meaning.
|
8979
|
+
module Parentheses
|
8980
|
+
NODES = [Args, Assign, Assoc, Binary, Call, Defined, MAssign, OpAssign]
|
8981
|
+
|
8982
|
+
def self.flat(q)
|
8983
|
+
return yield unless NODES.include?(q.parent.class)
|
8984
|
+
|
8985
|
+
q.text("(")
|
8986
|
+
yield
|
8987
|
+
q.text(")")
|
8988
|
+
end
|
8989
|
+
|
8990
|
+
def self.break(q)
|
8991
|
+
return yield unless NODES.include?(q.parent.class)
|
8992
|
+
|
8993
|
+
q.text("(")
|
8994
|
+
q.indent do
|
8995
|
+
q.breakable("")
|
8996
|
+
yield
|
8997
|
+
end
|
8998
|
+
q.breakable("")
|
8999
|
+
q.text(")")
|
9000
|
+
end
|
9001
|
+
end
|
9002
|
+
|
8403
9003
|
# def on_operator_ambiguous(value)
|
8404
9004
|
# value
|
8405
9005
|
# end
|
@@ -8459,7 +9059,7 @@ class SyntaxTree < Ripper
|
|
8459
9059
|
end
|
8460
9060
|
|
8461
9061
|
class KeywordRestFormatter
|
8462
|
-
# [:nil | KwRestParam] the value of the parameter
|
9062
|
+
# [:nil | ArgsForward | KwRestParam] the value of the parameter
|
8463
9063
|
attr_reader :value
|
8464
9064
|
|
8465
9065
|
def initialize(value)
|
@@ -8538,9 +9138,7 @@ class SyntaxTree < Ripper
|
|
8538
9138
|
# it's missing.
|
8539
9139
|
def empty?
|
8540
9140
|
requireds.empty? && optionals.empty? && !rest && posts.empty? &&
|
8541
|
-
keywords.empty? &&
|
8542
|
-
!keyword_rest &&
|
8543
|
-
!block
|
9141
|
+
keywords.empty? && !keyword_rest && !block
|
8544
9142
|
end
|
8545
9143
|
|
8546
9144
|
def child_nodes
|
@@ -8571,10 +9169,22 @@ class SyntaxTree < Ripper
|
|
8571
9169
|
parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest
|
8572
9170
|
parts << block if block
|
8573
9171
|
|
8574
|
-
|
9172
|
+
contents = -> do
|
8575
9173
|
q.seplist(parts) { |part| q.format(part) }
|
8576
9174
|
q.format(rest) if rest && rest.is_a?(ExcessedComma)
|
8577
9175
|
end
|
9176
|
+
|
9177
|
+
if [Def, Defs, DefEndless].include?(q.parent.class)
|
9178
|
+
q.group(0, "(", ")") do
|
9179
|
+
q.indent do
|
9180
|
+
q.breakable("")
|
9181
|
+
contents.call
|
9182
|
+
end
|
9183
|
+
q.breakable("")
|
9184
|
+
end
|
9185
|
+
else
|
9186
|
+
q.nest(0, &contents)
|
9187
|
+
end
|
8578
9188
|
end
|
8579
9189
|
|
8580
9190
|
def pretty_print(q)
|
@@ -8664,8 +9274,8 @@ class SyntaxTree < Ripper
|
|
8664
9274
|
# (nil | ArgsForward | ExcessedComma | RestParam) rest,
|
8665
9275
|
# (nil | Array[Ident]) posts,
|
8666
9276
|
# (nil | Array[[Ident, nil | untyped]]) keywords,
|
8667
|
-
# (nil | :nil | KwRestParam) keyword_rest,
|
8668
|
-
# (nil | BlockArg) block
|
9277
|
+
# (nil | :nil | ArgsForward | KwRestParam) keyword_rest,
|
9278
|
+
# (nil | :& | BlockArg) block
|
8669
9279
|
# ) -> Params
|
8670
9280
|
def on_params(
|
8671
9281
|
requireds,
|
@@ -8676,16 +9286,15 @@ class SyntaxTree < Ripper
|
|
8676
9286
|
keyword_rest,
|
8677
9287
|
block
|
8678
9288
|
)
|
8679
|
-
parts =
|
8680
|
-
|
8681
|
-
|
8682
|
-
|
8683
|
-
|
8684
|
-
|
8685
|
-
|
8686
|
-
|
8687
|
-
|
8688
|
-
].compact
|
9289
|
+
parts = [
|
9290
|
+
*requireds,
|
9291
|
+
*optionals&.flatten(1),
|
9292
|
+
rest,
|
9293
|
+
*posts,
|
9294
|
+
*keywords&.flat_map { |(key, value)| [key, value || nil] },
|
9295
|
+
(keyword_rest if keyword_rest != :nil),
|
9296
|
+
(block if block != :&)
|
9297
|
+
].compact
|
8689
9298
|
|
8690
9299
|
location =
|
8691
9300
|
if parts.any?
|
@@ -8701,7 +9310,7 @@ class SyntaxTree < Ripper
|
|
8701
9310
|
posts: posts || [],
|
8702
9311
|
keywords: keywords || [],
|
8703
9312
|
keyword_rest: keyword_rest,
|
8704
|
-
block: block,
|
9313
|
+
block: (block if block != :&),
|
8705
9314
|
location: location
|
8706
9315
|
)
|
8707
9316
|
end
|
@@ -8716,7 +9325,7 @@ class SyntaxTree < Ripper
|
|
8716
9325
|
# [LParen] the left parenthesis that opened this statement
|
8717
9326
|
attr_reader :lparen
|
8718
9327
|
|
8719
|
-
# [untyped] the expression inside the parentheses
|
9328
|
+
# [nil | untyped] the expression inside the parentheses
|
8720
9329
|
attr_reader :contents
|
8721
9330
|
|
8722
9331
|
# [Location] the location of this node
|
@@ -8740,7 +9349,7 @@ class SyntaxTree < Ripper
|
|
8740
9349
|
q.group do
|
8741
9350
|
q.format(lparen)
|
8742
9351
|
|
8743
|
-
if !contents.is_a?(Params) || !contents.empty?
|
9352
|
+
if contents && (!contents.is_a?(Params) || !contents.empty?)
|
8744
9353
|
q.indent do
|
8745
9354
|
q.breakable("")
|
8746
9355
|
q.format(contents)
|
@@ -8805,7 +9414,7 @@ class SyntaxTree < Ripper
|
|
8805
9414
|
|
8806
9415
|
Paren.new(
|
8807
9416
|
lparen: lparen,
|
8808
|
-
contents: contents,
|
9417
|
+
contents: contents || nil,
|
8809
9418
|
location: lparen.location.to(rparen.location)
|
8810
9419
|
)
|
8811
9420
|
end
|
@@ -8896,7 +9505,11 @@ class SyntaxTree < Ripper
|
|
8896
9505
|
|
8897
9506
|
def format(q)
|
8898
9507
|
q.format(statements)
|
8899
|
-
|
9508
|
+
|
9509
|
+
# We're going to put a newline on the end so that it always has one unless
|
9510
|
+
# it ends with the special __END__ syntax. In that case we want to
|
9511
|
+
# replicate the text exactly so we will just let it be.
|
9512
|
+
q.breakable(force: true) unless statements.body.last.is_a?(EndContent)
|
8900
9513
|
end
|
8901
9514
|
|
8902
9515
|
def pretty_print(q)
|
@@ -9035,6 +9648,9 @@ class SyntaxTree < Ripper
|
|
9035
9648
|
# %i[one two three]
|
9036
9649
|
#
|
9037
9650
|
class QSymbols
|
9651
|
+
# [QSymbolsBeg] the token that opens this array literal
|
9652
|
+
attr_reader :beginning
|
9653
|
+
|
9038
9654
|
# [Array[ TStringContent ]] the elements of the array
|
9039
9655
|
attr_reader :elements
|
9040
9656
|
|
@@ -9044,7 +9660,8 @@ class SyntaxTree < Ripper
|
|
9044
9660
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
9045
9661
|
attr_reader :comments
|
9046
9662
|
|
9047
|
-
def initialize(elements:, location:, comments: [])
|
9663
|
+
def initialize(beginning:, elements:, location:, comments: [])
|
9664
|
+
@beginning = beginning
|
9048
9665
|
@elements = elements
|
9049
9666
|
@location = location
|
9050
9667
|
@comments = comments
|
@@ -9055,7 +9672,14 @@ class SyntaxTree < Ripper
|
|
9055
9672
|
end
|
9056
9673
|
|
9057
9674
|
def format(q)
|
9058
|
-
|
9675
|
+
opening, closing = "%i[", "]"
|
9676
|
+
|
9677
|
+
if elements.any? { |element| element.match?(/[\[\]]/) }
|
9678
|
+
opening = beginning.value
|
9679
|
+
closing = Quotes.matching(opening[2])
|
9680
|
+
end
|
9681
|
+
|
9682
|
+
q.group(0, opening, closing) do
|
9059
9683
|
q.indent do
|
9060
9684
|
q.breakable("")
|
9061
9685
|
q.seplist(elements, -> { q.breakable }) do |element|
|
@@ -9091,6 +9715,7 @@ class SyntaxTree < Ripper
|
|
9091
9715
|
# on_qsymbols_add: (QSymbols qsymbols, TStringContent element) -> QSymbols
|
9092
9716
|
def on_qsymbols_add(qsymbols, element)
|
9093
9717
|
QSymbols.new(
|
9718
|
+
beginning: qsymbols.beginning,
|
9094
9719
|
elements: qsymbols.elements << element,
|
9095
9720
|
location: qsymbols.location.to(element.location)
|
9096
9721
|
)
|
@@ -9132,9 +9757,13 @@ class SyntaxTree < Ripper
|
|
9132
9757
|
# :call-seq:
|
9133
9758
|
# on_qsymbols_new: () -> QSymbols
|
9134
9759
|
def on_qsymbols_new
|
9135
|
-
|
9760
|
+
beginning = find_token(QSymbolsBeg)
|
9136
9761
|
|
9137
|
-
QSymbols.new(
|
9762
|
+
QSymbols.new(
|
9763
|
+
beginning: beginning,
|
9764
|
+
elements: [],
|
9765
|
+
location: beginning.location
|
9766
|
+
)
|
9138
9767
|
end
|
9139
9768
|
|
9140
9769
|
# QWords represents a string literal array without interpolation.
|
@@ -9142,6 +9771,9 @@ class SyntaxTree < Ripper
|
|
9142
9771
|
# %w[one two three]
|
9143
9772
|
#
|
9144
9773
|
class QWords
|
9774
|
+
# [QWordsBeg] the token that opens this array literal
|
9775
|
+
attr_reader :beginning
|
9776
|
+
|
9145
9777
|
# [Array[ TStringContent ]] the elements of the array
|
9146
9778
|
attr_reader :elements
|
9147
9779
|
|
@@ -9151,7 +9783,8 @@ class SyntaxTree < Ripper
|
|
9151
9783
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
9152
9784
|
attr_reader :comments
|
9153
9785
|
|
9154
|
-
def initialize(elements:, location:, comments: [])
|
9786
|
+
def initialize(beginning:, elements:, location:, comments: [])
|
9787
|
+
@beginning = beginning
|
9155
9788
|
@elements = elements
|
9156
9789
|
@location = location
|
9157
9790
|
@comments = comments
|
@@ -9162,7 +9795,14 @@ class SyntaxTree < Ripper
|
|
9162
9795
|
end
|
9163
9796
|
|
9164
9797
|
def format(q)
|
9165
|
-
|
9798
|
+
opening, closing = "%w[", "]"
|
9799
|
+
|
9800
|
+
if elements.any? { |element| element.match?(/[\[\]]/) }
|
9801
|
+
opening = beginning.value
|
9802
|
+
closing = Quotes.matching(opening[2])
|
9803
|
+
end
|
9804
|
+
|
9805
|
+
q.group(0, opening, closing) do
|
9166
9806
|
q.indent do
|
9167
9807
|
q.breakable("")
|
9168
9808
|
q.seplist(elements, -> { q.breakable }) do |element|
|
@@ -9195,6 +9835,7 @@ class SyntaxTree < Ripper
|
|
9195
9835
|
# on_qwords_add: (QWords qwords, TStringContent element) -> QWords
|
9196
9836
|
def on_qwords_add(qwords, element)
|
9197
9837
|
QWords.new(
|
9838
|
+
beginning: qwords.beginning,
|
9198
9839
|
elements: qwords.elements << element,
|
9199
9840
|
location: qwords.location.to(element.location)
|
9200
9841
|
)
|
@@ -9236,9 +9877,9 @@ class SyntaxTree < Ripper
|
|
9236
9877
|
# :call-seq:
|
9237
9878
|
# on_qwords_new: () -> QWords
|
9238
9879
|
def on_qwords_new
|
9239
|
-
|
9880
|
+
beginning = find_token(QWordsBeg)
|
9240
9881
|
|
9241
|
-
QWords.new(elements: [], location:
|
9882
|
+
QWords.new(beginning: beginning, elements: [], location: beginning.location)
|
9242
9883
|
end
|
9243
9884
|
|
9244
9885
|
# RationalLiteral represents the use of a rational number literal.
|
@@ -9550,11 +10191,32 @@ class SyntaxTree < Ripper
|
|
9550
10191
|
q.format_each(parts)
|
9551
10192
|
q.text(ending)
|
9552
10193
|
end
|
10194
|
+
elsif braces
|
10195
|
+
q.group do
|
10196
|
+
q.text("%r{")
|
10197
|
+
|
10198
|
+
if beginning == "/"
|
10199
|
+
# If we're changing from a forward slash to a %r{, then we can
|
10200
|
+
# replace any escaped forward slashes with regular forward slashes.
|
10201
|
+
parts.each do |part|
|
10202
|
+
if part.is_a?(TStringContent)
|
10203
|
+
q.text(part.value.gsub("\\/", "/"))
|
10204
|
+
else
|
10205
|
+
q.format(part)
|
10206
|
+
end
|
10207
|
+
end
|
10208
|
+
else
|
10209
|
+
q.format_each(parts)
|
10210
|
+
end
|
10211
|
+
|
10212
|
+
q.text("}")
|
10213
|
+
q.text(ending[1..-1])
|
10214
|
+
end
|
9553
10215
|
else
|
9554
10216
|
q.group do
|
9555
|
-
q.text(
|
10217
|
+
q.text("/")
|
9556
10218
|
q.format_each(parts)
|
9557
|
-
q.text(
|
10219
|
+
q.text("/")
|
9558
10220
|
q.text(ending[1..-1])
|
9559
10221
|
end
|
9560
10222
|
end
|
@@ -10285,21 +10947,6 @@ class SyntaxTree < Ripper
|
|
10285
10947
|
# value
|
10286
10948
|
# end
|
10287
10949
|
|
10288
|
-
# stmts_add is a parser event that represents a single statement inside a
|
10289
|
-
# list of statements within any lexical block. It accepts as arguments the
|
10290
|
-
# parent stmts node as well as an stmt which can be any expression in
|
10291
|
-
# Ruby.
|
10292
|
-
def on_stmts_add(statements, statement)
|
10293
|
-
location =
|
10294
|
-
if statements.body.empty?
|
10295
|
-
statement.location
|
10296
|
-
else
|
10297
|
-
statements.location.to(statement.location)
|
10298
|
-
end
|
10299
|
-
|
10300
|
-
Statements.new(self, body: statements.body << statement, location: location)
|
10301
|
-
end
|
10302
|
-
|
10303
10950
|
# Everything that has a block of code inside of it has a list of statements.
|
10304
10951
|
# Normally we would just track those as a node that has an array body, but we
|
10305
10952
|
# have some special handling in order to handle empty statement lists. They
|
@@ -10379,12 +11026,22 @@ class SyntaxTree < Ripper
|
|
10379
11026
|
# the only value is a comment. In that case a lot of nodes like
|
10380
11027
|
# brace_block will attempt to format as a single line, but since that
|
10381
11028
|
# wouldn't work with a comment, we intentionally break the parent group.
|
10382
|
-
if body.length == 2
|
10383
|
-
|
10384
|
-
|
10385
|
-
|
11029
|
+
if body.length == 2
|
11030
|
+
void_stmt, comment = body
|
11031
|
+
|
11032
|
+
if void_stmt.is_a?(VoidStmt) && comment.is_a?(Comment)
|
11033
|
+
q.format(comment)
|
11034
|
+
q.break_parent
|
11035
|
+
return
|
11036
|
+
end
|
10386
11037
|
end
|
10387
11038
|
|
11039
|
+
access_controls =
|
11040
|
+
Hash.new do |hash, node|
|
11041
|
+
hash[node] = node.is_a?(VCall) &&
|
11042
|
+
%w[private protected public].include?(node.value.value)
|
11043
|
+
end
|
11044
|
+
|
10388
11045
|
body.each_with_index do |statement, index|
|
10389
11046
|
next if statement.is_a?(VoidStmt)
|
10390
11047
|
|
@@ -10394,7 +11051,7 @@ class SyntaxTree < Ripper
|
|
10394
11051
|
q.breakable(force: true)
|
10395
11052
|
q.breakable(force: true)
|
10396
11053
|
q.format(statement)
|
10397
|
-
elsif statement
|
11054
|
+
elsif access_controls[statement] || access_controls[body[index - 1]]
|
10398
11055
|
q.breakable(force: true)
|
10399
11056
|
q.breakable(force: true)
|
10400
11057
|
q.format(statement)
|
@@ -10446,7 +11103,7 @@ class SyntaxTree < Ripper
|
|
10446
11103
|
location = comment.location
|
10447
11104
|
|
10448
11105
|
if !comment.inline? && (start_char <= location.start_char) &&
|
10449
|
-
(end_char >= location.end_char)
|
11106
|
+
(end_char >= location.end_char) && !comment.ignore?
|
10450
11107
|
parser_comments.delete_at(comment_index)
|
10451
11108
|
|
10452
11109
|
while (node = body[body_index]) &&
|
@@ -10465,6 +11122,21 @@ class SyntaxTree < Ripper
|
|
10465
11122
|
end
|
10466
11123
|
end
|
10467
11124
|
|
11125
|
+
# stmts_add is a parser event that represents a single statement inside a
|
11126
|
+
# list of statements within any lexical block. It accepts as arguments the
|
11127
|
+
# parent stmts node as well as an stmt which can be any expression in
|
11128
|
+
# Ruby.
|
11129
|
+
def on_stmts_add(statements, statement)
|
11130
|
+
location =
|
11131
|
+
if statements.body.empty?
|
11132
|
+
statement.location
|
11133
|
+
else
|
11134
|
+
statements.location.to(statement.location)
|
11135
|
+
end
|
11136
|
+
|
11137
|
+
Statements.new(self, body: statements.body << statement, location: location)
|
11138
|
+
end
|
11139
|
+
|
10468
11140
|
# :call-seq:
|
10469
11141
|
# on_stmts_new: () -> Statements
|
10470
11142
|
def on_stmts_new
|
@@ -10736,10 +11408,18 @@ class SyntaxTree < Ripper
|
|
10736
11408
|
embexpr_end.location.start_char
|
10737
11409
|
)
|
10738
11410
|
|
10739
|
-
|
10740
|
-
|
10741
|
-
|
10742
|
-
|
11411
|
+
location =
|
11412
|
+
Location.new(
|
11413
|
+
start_line: embexpr_beg.location.start_line,
|
11414
|
+
start_char: embexpr_beg.location.start_char,
|
11415
|
+
end_line: [
|
11416
|
+
embexpr_end.location.end_line,
|
11417
|
+
statements.location.end_line
|
11418
|
+
].max,
|
11419
|
+
end_char: embexpr_end.location.end_char
|
11420
|
+
)
|
11421
|
+
|
11422
|
+
StringEmbExpr.new(statements: statements, location: location)
|
10743
11423
|
end
|
10744
11424
|
|
10745
11425
|
# StringLiteral represents a string literal.
|
@@ -10839,12 +11519,23 @@ class SyntaxTree < Ripper
|
|
10839
11519
|
)
|
10840
11520
|
else
|
10841
11521
|
tstring_beg = find_token(TStringBeg)
|
10842
|
-
tstring_end = find_token(TStringEnd)
|
11522
|
+
tstring_end = find_token(TStringEnd, location: tstring_beg.location)
|
11523
|
+
|
11524
|
+
location =
|
11525
|
+
Location.new(
|
11526
|
+
start_line: tstring_beg.location.start_line,
|
11527
|
+
start_char: tstring_beg.location.start_char,
|
11528
|
+
end_line: [
|
11529
|
+
tstring_end.location.end_line,
|
11530
|
+
string.location.end_line
|
11531
|
+
].max,
|
11532
|
+
end_char: tstring_end.location.end_char
|
11533
|
+
)
|
10843
11534
|
|
10844
11535
|
StringLiteral.new(
|
10845
11536
|
parts: string.parts,
|
10846
11537
|
quote: tstring_beg.value,
|
10847
|
-
location:
|
11538
|
+
location: location
|
10848
11539
|
)
|
10849
11540
|
end
|
10850
11541
|
end
|
@@ -11066,6 +11757,9 @@ class SyntaxTree < Ripper
|
|
11066
11757
|
# %I[one two three]
|
11067
11758
|
#
|
11068
11759
|
class Symbols
|
11760
|
+
# [SymbolsBeg] the token that opens this array literal
|
11761
|
+
attr_reader :beginning
|
11762
|
+
|
11069
11763
|
# [Array[ Word ]] the words in the symbol array literal
|
11070
11764
|
attr_reader :elements
|
11071
11765
|
|
@@ -11075,7 +11769,8 @@ class SyntaxTree < Ripper
|
|
11075
11769
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
11076
11770
|
attr_reader :comments
|
11077
11771
|
|
11078
|
-
def initialize(elements:, location:, comments: [])
|
11772
|
+
def initialize(beginning:, elements:, location:, comments: [])
|
11773
|
+
@beginning = beginning
|
11079
11774
|
@elements = elements
|
11080
11775
|
@location = location
|
11081
11776
|
@comments = comments
|
@@ -11086,7 +11781,14 @@ class SyntaxTree < Ripper
|
|
11086
11781
|
end
|
11087
11782
|
|
11088
11783
|
def format(q)
|
11089
|
-
|
11784
|
+
opening, closing = "%I[", "]"
|
11785
|
+
|
11786
|
+
if elements.any? { |element| element.match?(/[\[\]]/) }
|
11787
|
+
opening = beginning.value
|
11788
|
+
closing = Quotes.matching(opening[2])
|
11789
|
+
end
|
11790
|
+
|
11791
|
+
q.group(0, opening, closing) do
|
11090
11792
|
q.indent do
|
11091
11793
|
q.breakable("")
|
11092
11794
|
q.seplist(elements, -> { q.breakable }) do |element|
|
@@ -11122,6 +11824,7 @@ class SyntaxTree < Ripper
|
|
11122
11824
|
# on_symbols_add: (Symbols symbols, Word word) -> Symbols
|
11123
11825
|
def on_symbols_add(symbols, word)
|
11124
11826
|
Symbols.new(
|
11827
|
+
beginning: symbols.beginning,
|
11125
11828
|
elements: symbols.elements << word,
|
11126
11829
|
location: symbols.location.to(word.location)
|
11127
11830
|
)
|
@@ -11164,9 +11867,13 @@ class SyntaxTree < Ripper
|
|
11164
11867
|
# :call-seq:
|
11165
11868
|
# on_symbols_new: () -> Symbols
|
11166
11869
|
def on_symbols_new
|
11167
|
-
|
11870
|
+
beginning = find_token(SymbolsBeg)
|
11168
11871
|
|
11169
|
-
Symbols.new(
|
11872
|
+
Symbols.new(
|
11873
|
+
beginning: beginning,
|
11874
|
+
elements: [],
|
11875
|
+
location: beginning.location
|
11876
|
+
)
|
11170
11877
|
end
|
11171
11878
|
|
11172
11879
|
# TLambda represents the beginning of a lambda literal.
|
@@ -11258,6 +11965,11 @@ class SyntaxTree < Ripper
|
|
11258
11965
|
[constant]
|
11259
11966
|
end
|
11260
11967
|
|
11968
|
+
def format(q)
|
11969
|
+
q.text("::")
|
11970
|
+
q.format(constant)
|
11971
|
+
end
|
11972
|
+
|
11261
11973
|
def pretty_print(q)
|
11262
11974
|
q.group(2, "(", ")") do
|
11263
11975
|
q.text("top_const_field")
|
@@ -11412,6 +12124,10 @@ class SyntaxTree < Ripper
|
|
11412
12124
|
@comments = comments
|
11413
12125
|
end
|
11414
12126
|
|
12127
|
+
def match?(pattern)
|
12128
|
+
value.match?(pattern)
|
12129
|
+
end
|
12130
|
+
|
11415
12131
|
def child_nodes
|
11416
12132
|
[]
|
11417
12133
|
end
|
@@ -11914,23 +12630,35 @@ class SyntaxTree < Ripper
|
|
11914
12630
|
end
|
11915
12631
|
|
11916
12632
|
def format(q)
|
12633
|
+
if ContainsAssignment.call(node.predicate)
|
12634
|
+
format_break(q)
|
12635
|
+
q.break_parent
|
12636
|
+
return
|
12637
|
+
end
|
12638
|
+
|
11917
12639
|
q.group do
|
11918
|
-
q.if_break do
|
11919
|
-
|
11920
|
-
q.nest(keyword.length + 1) { q.format(node.predicate) }
|
11921
|
-
q.indent do
|
11922
|
-
q.breakable("")
|
12640
|
+
q.if_break { format_break(q) }.if_flat do
|
12641
|
+
Parentheses.flat(q) do
|
11923
12642
|
q.format(statements)
|
12643
|
+
q.text(" #{keyword} ")
|
12644
|
+
q.format(node.predicate)
|
11924
12645
|
end
|
11925
|
-
q.breakable("")
|
11926
|
-
q.text("end")
|
11927
|
-
end.if_flat do
|
11928
|
-
q.format(statements)
|
11929
|
-
q.text(" #{keyword} ")
|
11930
|
-
q.format(node.predicate)
|
11931
12646
|
end
|
11932
12647
|
end
|
11933
12648
|
end
|
12649
|
+
|
12650
|
+
private
|
12651
|
+
|
12652
|
+
def format_break(q)
|
12653
|
+
q.text("#{keyword} ")
|
12654
|
+
q.nest(keyword.length + 1) { q.format(node.predicate) }
|
12655
|
+
q.indent do
|
12656
|
+
q.breakable("")
|
12657
|
+
q.format(statements)
|
12658
|
+
end
|
12659
|
+
q.breakable("")
|
12660
|
+
q.text("end")
|
12661
|
+
end
|
11934
12662
|
end
|
11935
12663
|
|
11936
12664
|
# Until represents an +until+ loop.
|
@@ -12055,7 +12783,27 @@ class SyntaxTree < Ripper
|
|
12055
12783
|
end
|
12056
12784
|
|
12057
12785
|
def format(q)
|
12058
|
-
|
12786
|
+
# If we're in the modifier form and we're modifying a `begin`, then this
|
12787
|
+
# is a special case where we need to explicitly use the modifier form
|
12788
|
+
# because otherwise the semantic meaning changes. This looks like:
|
12789
|
+
#
|
12790
|
+
# begin
|
12791
|
+
# foo
|
12792
|
+
# end until bar
|
12793
|
+
#
|
12794
|
+
# Also, if the statement of the modifier includes an assignment, then we
|
12795
|
+
# can't know for certain that it won't impact the predicate, so we need to
|
12796
|
+
# force it to stay as it is. This looks like:
|
12797
|
+
#
|
12798
|
+
# foo = bar until foo
|
12799
|
+
#
|
12800
|
+
if statement.is_a?(Begin) || ContainsAssignment.call(statement)
|
12801
|
+
q.format(statement)
|
12802
|
+
q.text(" until ")
|
12803
|
+
q.format(predicate)
|
12804
|
+
else
|
12805
|
+
LoopFormatter.new("until", self, statement).format(q)
|
12806
|
+
end
|
12059
12807
|
end
|
12060
12808
|
|
12061
12809
|
def pretty_print(q)
|
@@ -12285,19 +13033,17 @@ class SyntaxTree < Ripper
|
|
12285
13033
|
end
|
12286
13034
|
end
|
12287
13035
|
|
12288
|
-
#
|
12289
|
-
#
|
12290
|
-
def on_var_ref(value)
|
12291
|
-
VarRef.new(value: value, location: value.location)
|
12292
|
-
end
|
12293
|
-
|
12294
|
-
# AccessCtrl represents a call to a method visibility control, i.e., +public+,
|
12295
|
-
# +protected+, or +private+.
|
13036
|
+
# PinnedVarRef represents a pinned variable reference within a pattern
|
13037
|
+
# matching pattern.
|
12296
13038
|
#
|
12297
|
-
#
|
13039
|
+
# case value
|
13040
|
+
# in ^variable
|
13041
|
+
# end
|
12298
13042
|
#
|
12299
|
-
|
12300
|
-
|
13043
|
+
# This can be a plain local variable like the example above. It can also be a
|
13044
|
+
# a class variable, a global variable, or an instance variable.
|
13045
|
+
class PinnedVarRef
|
13046
|
+
# [VarRef] the value of this node
|
12301
13047
|
attr_reader :value
|
12302
13048
|
|
12303
13049
|
# [Location] the location of this node
|
@@ -12317,12 +13063,15 @@ class SyntaxTree < Ripper
|
|
12317
13063
|
end
|
12318
13064
|
|
12319
13065
|
def format(q)
|
12320
|
-
q.
|
13066
|
+
q.group do
|
13067
|
+
q.text("^")
|
13068
|
+
q.format(value)
|
13069
|
+
end
|
12321
13070
|
end
|
12322
13071
|
|
12323
13072
|
def pretty_print(q)
|
12324
13073
|
q.group(2, "(", ")") do
|
12325
|
-
q.text("
|
13074
|
+
q.text("pinned_var_ref")
|
12326
13075
|
|
12327
13076
|
q.breakable
|
12328
13077
|
q.pp(value)
|
@@ -12332,12 +13081,21 @@ class SyntaxTree < Ripper
|
|
12332
13081
|
end
|
12333
13082
|
|
12334
13083
|
def to_json(*opts)
|
12335
|
-
{
|
12336
|
-
|
12337
|
-
|
12338
|
-
|
12339
|
-
|
12340
|
-
|
13084
|
+
{ type: :pinned_var_ref, value: value, loc: location, cmts: comments }
|
13085
|
+
.to_json(*opts)
|
13086
|
+
end
|
13087
|
+
end
|
13088
|
+
|
13089
|
+
# :call-seq:
|
13090
|
+
# on_var_ref: ((Const | CVar | GVar | Ident | IVar | Kw) value) -> VarRef
|
13091
|
+
def on_var_ref(value)
|
13092
|
+
pin = find_token(Op, "^", consume: false)
|
13093
|
+
|
13094
|
+
if pin && pin.location.start_char == value.location.start_char - 1
|
13095
|
+
tokens.delete(pin)
|
13096
|
+
PinnedVarRef.new(value: value, location: pin.location.to(value.location))
|
13097
|
+
else
|
13098
|
+
VarRef.new(value: value, location: value.location)
|
12341
13099
|
end
|
12342
13100
|
end
|
12343
13101
|
|
@@ -12389,19 +13147,9 @@ class SyntaxTree < Ripper
|
|
12389
13147
|
end
|
12390
13148
|
|
12391
13149
|
# :call-seq:
|
12392
|
-
# on_vcall: (Ident ident) ->
|
13150
|
+
# on_vcall: (Ident ident) -> VCall
|
12393
13151
|
def on_vcall(ident)
|
12394
|
-
|
12395
|
-
|
12396
|
-
if @controls.include?(ident.value) && ident.value == lines[lineno - 1].strip
|
12397
|
-
# Access controls like private, protected, and public are reported as
|
12398
|
-
# vcall nodes since they're technically method calls. We want to be able
|
12399
|
-
# add new lines around them as necessary, so here we're going to
|
12400
|
-
# explicitly track those as a different node type.
|
12401
|
-
AccessCtrl.new(value: ident, location: ident.location)
|
12402
|
-
else
|
12403
|
-
VCall.new(value: ident, location: ident.location)
|
12404
|
-
end
|
13152
|
+
VCall.new(value: ident, location: ident.location)
|
12405
13153
|
end
|
12406
13154
|
|
12407
13155
|
# VoidStmt represents an empty lexical block of code.
|
@@ -12420,6 +13168,10 @@ class SyntaxTree < Ripper
|
|
12420
13168
|
@comments = comments
|
12421
13169
|
end
|
12422
13170
|
|
13171
|
+
def child_nodes
|
13172
|
+
[]
|
13173
|
+
end
|
13174
|
+
|
12423
13175
|
def format(q)
|
12424
13176
|
end
|
12425
13177
|
|
@@ -12494,6 +13246,14 @@ class SyntaxTree < Ripper
|
|
12494
13246
|
separator = -> { q.group { q.comma_breakable } }
|
12495
13247
|
q.seplist(arguments.parts, separator) { |part| q.format(part) }
|
12496
13248
|
end
|
13249
|
+
|
13250
|
+
# Very special case here. If you're inside of a when clause and the
|
13251
|
+
# last argument to the predicate is and endless range, then you are
|
13252
|
+
# forced to use the "then" keyword to make it parse properly.
|
13253
|
+
last = arguments.parts.last
|
13254
|
+
if (last.is_a?(Dot2) || last.is_a?(Dot3)) && !last.right
|
13255
|
+
q.text(" then")
|
13256
|
+
end
|
12497
13257
|
end
|
12498
13258
|
end
|
12499
13259
|
|
@@ -12552,7 +13312,16 @@ class SyntaxTree < Ripper
|
|
12552
13312
|
beginning = find_token(Kw, "when")
|
12553
13313
|
ending = consequent || find_token(Kw, "end")
|
12554
13314
|
|
12555
|
-
|
13315
|
+
statements_start = arguments
|
13316
|
+
if token = find_token(Kw, "then", consume: false)
|
13317
|
+
tokens.delete(token)
|
13318
|
+
statements_start = token
|
13319
|
+
end
|
13320
|
+
|
13321
|
+
statements.bind(
|
13322
|
+
find_next_statement_start(statements_start.location.end_char),
|
13323
|
+
ending.location.start_char
|
13324
|
+
)
|
12556
13325
|
|
12557
13326
|
When.new(
|
12558
13327
|
arguments: arguments,
|
@@ -12684,7 +13453,27 @@ class SyntaxTree < Ripper
|
|
12684
13453
|
end
|
12685
13454
|
|
12686
13455
|
def format(q)
|
12687
|
-
|
13456
|
+
# If we're in the modifier form and we're modifying a `begin`, then this
|
13457
|
+
# is a special case where we need to explicitly use the modifier form
|
13458
|
+
# because otherwise the semantic meaning changes. This looks like:
|
13459
|
+
#
|
13460
|
+
# begin
|
13461
|
+
# foo
|
13462
|
+
# end while bar
|
13463
|
+
#
|
13464
|
+
# Also, if the statement of the modifier includes an assignment, then we
|
13465
|
+
# can't know for certain that it won't impact the predicate, so we need to
|
13466
|
+
# force it to stay as it is. This looks like:
|
13467
|
+
#
|
13468
|
+
# foo = bar while foo
|
13469
|
+
#
|
13470
|
+
if statement.is_a?(Begin) || ContainsAssignment.call(statement)
|
13471
|
+
q.format(statement)
|
13472
|
+
q.text(" while ")
|
13473
|
+
q.format(predicate)
|
13474
|
+
else
|
13475
|
+
LoopFormatter.new("while", self, statement).format(q)
|
13476
|
+
end
|
12688
13477
|
end
|
12689
13478
|
|
12690
13479
|
def pretty_print(q)
|
@@ -12748,6 +13537,10 @@ class SyntaxTree < Ripper
|
|
12748
13537
|
@comments = comments
|
12749
13538
|
end
|
12750
13539
|
|
13540
|
+
def match?(pattern)
|
13541
|
+
parts.any? { |part| part.is_a?(TStringContent) && part.match?(pattern) }
|
13542
|
+
end
|
13543
|
+
|
12751
13544
|
def child_nodes
|
12752
13545
|
parts
|
12753
13546
|
end
|
@@ -12797,6 +13590,9 @@ class SyntaxTree < Ripper
|
|
12797
13590
|
# %W[one two three]
|
12798
13591
|
#
|
12799
13592
|
class Words
|
13593
|
+
# [WordsBeg] the token that opens this array literal
|
13594
|
+
attr_reader :beginning
|
13595
|
+
|
12800
13596
|
# [Array[ Word ]] the elements of this array
|
12801
13597
|
attr_reader :elements
|
12802
13598
|
|
@@ -12806,7 +13602,8 @@ class SyntaxTree < Ripper
|
|
12806
13602
|
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
12807
13603
|
attr_reader :comments
|
12808
13604
|
|
12809
|
-
def initialize(elements:, location:, comments: [])
|
13605
|
+
def initialize(beginning:, elements:, location:, comments: [])
|
13606
|
+
@beginning = beginning
|
12810
13607
|
@elements = elements
|
12811
13608
|
@location = location
|
12812
13609
|
@comments = comments
|
@@ -12817,7 +13614,14 @@ class SyntaxTree < Ripper
|
|
12817
13614
|
end
|
12818
13615
|
|
12819
13616
|
def format(q)
|
12820
|
-
|
13617
|
+
opening, closing = "%W[", "]"
|
13618
|
+
|
13619
|
+
if elements.any? { |element| element.match?(/[\[\]]/) }
|
13620
|
+
opening = beginning.value
|
13621
|
+
closing = Quotes.matching(opening[2])
|
13622
|
+
end
|
13623
|
+
|
13624
|
+
q.group(0, opening, closing) do
|
12821
13625
|
q.indent do
|
12822
13626
|
q.breakable("")
|
12823
13627
|
q.seplist(elements, -> { q.breakable }) do |element|
|
@@ -12850,6 +13654,7 @@ class SyntaxTree < Ripper
|
|
12850
13654
|
# on_words_add: (Words words, Word word) -> Words
|
12851
13655
|
def on_words_add(words, word)
|
12852
13656
|
Words.new(
|
13657
|
+
beginning: words.beginning,
|
12853
13658
|
elements: words.elements << word,
|
12854
13659
|
location: words.location.to(word.location)
|
12855
13660
|
)
|
@@ -12892,9 +13697,9 @@ class SyntaxTree < Ripper
|
|
12892
13697
|
# :call-seq:
|
12893
13698
|
# on_words_new: () -> Words
|
12894
13699
|
def on_words_new
|
12895
|
-
|
13700
|
+
beginning = find_token(WordsBeg)
|
12896
13701
|
|
12897
|
-
Words.new(elements: [], location:
|
13702
|
+
Words.new(beginning: beginning, elements: [], location: beginning.location)
|
12898
13703
|
end
|
12899
13704
|
|
12900
13705
|
# def on_words_sep(value)
|
@@ -13011,7 +13816,7 @@ class SyntaxTree < Ripper
|
|
13011
13816
|
location: heredoc.location
|
13012
13817
|
)
|
13013
13818
|
else
|
13014
|
-
ending = find_token(TStringEnd)
|
13819
|
+
ending = find_token(TStringEnd, location: xstring.location)
|
13015
13820
|
|
13016
13821
|
XStringLiteral.new(
|
13017
13822
|
parts: xstring.parts,
|
@@ -13047,8 +13852,18 @@ class SyntaxTree < Ripper
|
|
13047
13852
|
def format(q)
|
13048
13853
|
q.group do
|
13049
13854
|
q.text("yield")
|
13050
|
-
|
13051
|
-
|
13855
|
+
|
13856
|
+
if arguments.is_a?(Paren)
|
13857
|
+
q.format(arguments)
|
13858
|
+
else
|
13859
|
+
q.if_break { q.text("(") }.if_flat { q.text(" ") }
|
13860
|
+
q.indent do
|
13861
|
+
q.breakable("")
|
13862
|
+
q.format(arguments)
|
13863
|
+
end
|
13864
|
+
q.breakable("")
|
13865
|
+
q.if_break { q.text(")") }
|
13866
|
+
end
|
13052
13867
|
end
|
13053
13868
|
end
|
13054
13869
|
|