syntax_tree 0.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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::Assign isn't defined, then we haven't gotten the updated
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
- @start + byteindex
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
- @indices[byteindex]
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
- attr_reader :stack, :quote
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
- doc = node.format(self)
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
- @line_counts[lineno - 1][column]
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
- message = "Cannot find expected #{value == :any ? type : value}"
311
- raise ParseError.new(message, lineno, column)
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
- q.text(value)
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: lines[lineno..-1].join("\n"),
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
- q.breakable
1154
- q.pp(value)
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
- return arguments unless block
1226
+ operator = find_token(Op, "&", consume: false)
1174
1227
 
1175
- arg_block =
1176
- ArgBlock.new(
1177
- value: block,
1178
- location: find_token(Op, "&").location.to(block.location)
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(arg_block.location)
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
- q.format(part.parts.first)
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.group(0, "[", "]") do
1416
- q.indent do
1417
- q.breakable("")
1418
- q.format(contents)
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
- contents && contents.comments.empty? && contents.parts.length > 1 &&
1554
+ lbracket.comments.empty? && contents && contents.comments.empty? &&
1555
+ contents.parts.length > 1 &&
1445
1556
  contents.parts.all? do |part|
1446
- part.is_a?(StringLiteral) && part.comments.empty? &&
1447
- part.parts.length == 1 &&
1448
- part.parts.first.is_a?(TStringContent) &&
1449
- !part.parts.first.value.match?(/[\s\\\]]/)
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
- contents && contents.comments.empty? && contents.parts.length > 1 &&
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 = find_token(TStringEnd)
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, *required, rest, *posts]
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.format(constant)
1567
- q.text("[")
1568
- q.seplist(parts) { |part| q.format(part) }
1569
- q.text("]")
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.any? { |pattern| parent.is_a?(pattern) }
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.is_a?(ARefField) || value.is_a?(ArrayLiteral) ||
1721
- value.is_a?(HashLiteral) ||
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
- contents = -> do
1772
- q.parent.format_key(q, key)
1773
- q.indent do
1774
- q.breakable
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
- q.breakable
1790
- q.pp(value)
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
- Assoc.new(key: key, value: value, location: key.location.to(value.location))
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 HashFormatter
1994
- class Base
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 < Base
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(container)
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.format(HashFormatter.for(self))
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: (BodyStmt bodystmt) -> Begin
2408
+ # on_begin: (untyped bodystmt) -> Begin | PinnedBegin
2193
2409
  def on_begin(bodystmt)
2194
- keyword = find_token(Kw, "begin")
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.bind(keyword.location.end_char, end_char)
2412
+ if pin && pin.location.start_char < bodystmt.location.start_char
2413
+ tokens.delete(pin)
2414
+ find_token(LParen)
2204
2415
 
2205
- Begin.new(
2206
- bodystmt: bodystmt,
2207
- location: keyword.location.to(bodystmt.location)
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.indent do
2258
- q.breakable(power ? "" : " ")
2259
- q.format(right)
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
- # On most Ruby implementations, operator is a Symbol that represents that
2297
- # operation being performed. For instance in the example `1 < 2`, the
2298
- # `operator` object would be `:<`. However, on JRuby, it's an `@op` node,
2299
- # so here we're going to explicitly convert it into the same normalized
2300
- # form.
2301
- operator = tokens.delete(operator).value unless operator.is_a?(Symbol)
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
- q.breakable
2480
- q.pp(name)
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
- BlockArg.new(name: name, location: operator.location.to(name.location))
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
- q.group do
2699
- q.text(" ")
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
- q.if_break do
2702
- q.format(BlockOpenFormatter.new("do", block_open))
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
- if node.block_var
2705
- q.text(" ")
2706
- q.format(node.block_var)
2707
- end
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
- unless statements.empty?
2710
- q.indent do
2711
- q.breakable
2712
- q.format(statements)
2713
- end
2714
- end
2983
+ private
2715
2984
 
2716
- q.breakable
2717
- q.text("end")
2718
- end.if_flat do
2719
- q.format(BlockOpenFormatter.new("{", block_open))
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
- if node.block_var
2722
- q.breakable
2723
- q.format(node.block_var)
2724
- q.breakable
2725
- end
2994
+ [Command, CommandCall].include?(parent.class)
2995
+ end
2996
+ end
2726
2997
 
2727
- unless statements.empty?
2728
- q.breakable unless node.block_var
2729
- q.format(statements)
2730
- q.breakable
2731
- end
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
- q.text("}")
2734
- end
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
- # BraceBlock represents passing a block to a method call using the { }
2740
- # operators.
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 && arguments.parts.first.is_a?(Paren)
2855
- q.format(arguments)
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.text(" ")
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. This node doesn't contain the arguments being
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(receiver:, operator:, message:, location:, comments: [])
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
- [receiver, (operator if operator != :"::"), (message if message != :call)]
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
- q.format(CallOperatorFormatter.new(operator))
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
- location:
3039
- Location.new(
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(0, "case", "end") do
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 = q.format(receiver)
3519
- q.format(CallOperatorFormatter.new(operator))
3520
- q.format(message)
3521
- q.text(" ")
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
- width = doc_width(doc)
3524
- if width > (q.maxwidth / 2) || width < 2
3525
- q.indent { q.format(arguments) }
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
- q.breakable
3546
- q.pp(arguments)
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 += doc.contents.reverse
3974
+ queue = doc.contents + queue
3581
3975
  when PrettyPrint::IfBreak
3582
- queue += doc.flat_contents.reverse
3976
+ queue = doc.break_contents + queue
3583
3977
  when PrettyPrint::Breakable
3584
- width = doc.force? ? 0 : width + doc.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(name:, paren:, statement:, location:, comments: [])
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
- q.format(paren) if paren && !paren.contents.empty?
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
- Def.new(
4244
- name: name,
4245
- params: params,
4246
- bodystmt: bodystmt,
4247
- location: beginning.location.to(ending.location)
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
- bodystmt.bind(
4466
- find_next_statement_start(params.location.end_char),
4467
- ending.location.start_char
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
- Defs.new(
4471
- target: target,
4472
- operator: operator,
4473
- name: name,
4474
- params: params,
4475
- bodystmt: bodystmt,
4476
- location: beginning.location.to(ending.location)
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
- parent = q.parent
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? do |part|
5358
+ if parts.any? { |part|
4894
5359
  part.is_a?(TStringContent) && part.value.match?(pattern)
4895
- end
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
- { type: :fcall, value: value, loc: location, cmts: comments }.to_json(
5516
- *opts
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
- contents = -> do
5971
- q.text("{")
5972
- q.indent do
5973
- q.breakable
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
- q.parent.is_a?(Assoc) ? contents.call : q.group(&contents)
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 = -> { q.seplist(parts) { |part| q.format(part) } }
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.any? { |pattern| parent.is_a?(pattern) }
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
- statements = node.statements
6446
- break_format = ->(force:) do
6447
- q.text("#{keyword} ")
6448
- q.nest(keyword.length + 1) { q.format(node.predicate) }
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
- unless statements.empty?
6451
- q.indent do
6452
- q.breakable(force: force)
6453
- q.format(statements)
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
- if node.consequent
7005
+ unless node.statements.empty?
7006
+ q.indent do
6458
7007
  q.breakable(force: force)
6459
- q.format(node.consequent)
7008
+ q.format(node.statements)
6460
7009
  end
7010
+ end
6461
7011
 
7012
+ if node.consequent
6462
7013
  q.breakable(force: force)
6463
- q.text("end")
7014
+ q.format(node.consequent)
6464
7015
  end
6465
7016
 
6466
- if node.consequent || statements.empty?
6467
- q.group { break_format.call(force: true) }
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
- q.group do
6608
- q.if_break do
6609
- q.text("if ")
6610
- q.nest("if ".length) { q.format(predicate) }
6611
-
6612
- q.indent do
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
- q.breakable
6636
- q.format(falsy)
6637
- end
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
- q.group do
6696
- q.if_break do
6697
- q.text("#{keyword} ")
6698
- q.nest(keyword.length + 1) { q.format(node.predicate) }
6699
- q.indent do
6700
- q.breakable
6701
- q.format(node.statement)
6702
- end
6703
- q.breakable
6704
- q.text("end")
6705
- end.if_flat do
6706
- q.format(node.statement)
6707
- q.text(" #{keyword} ")
6708
- q.format(node.predicate)
6709
- end
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(pattern.location.end_char),
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?("0") && value.length >= 5 && !value.include?("_")
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.text("(")
7334
- q.format(params)
7335
- q.text(")")
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 token = find_token(TLamBeg, consume: false)
7395
- opening = tokens.delete(token)
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
- def initialize(value:, location:)
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
- # [ArgParen | Args] the arguments to the method call
7665
- attr_reader :arguments
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(call:, arguments:, location:, comments: [])
7674
- @call = call
7675
- @arguments = arguments
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
- [call, arguments]
8199
+ [target, value]
7682
8200
  end
7683
8201
 
7684
8202
  def format(q)
7685
- q.format(call)
7686
- q.text(" ") if !arguments.is_a?(ArgParen) && arguments.parts.any?
7687
- q.format(arguments)
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("method_add_arg")
8215
+ q.text("massign")
7693
8216
 
7694
8217
  q.breakable
7695
- q.pp(call)
8218
+ q.pp(target)
7696
8219
 
7697
8220
  q.breakable
7698
- q.pp(arguments)
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: :method_add_arg,
7707
- call: call,
7708
- args: arguments,
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
- # ) -> MethodAddArg
8255
+ # ) -> Call | FCall
7720
8256
  def on_method_add_arg(call, arguments)
7721
8257
  location = call.location
7722
- location = location.to(arguments.location) unless arguments.is_a?(Args)
8258
+ location = location.to(arguments.location) if arguments.is_a?(ArgParen)
7723
8259
 
7724
- MethodAddArg.new(call: call, arguments: arguments, location: location)
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 | MethodAddArg] the method call
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 | MethodAddArg) call,
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
- q.nest(0) do
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
- *requireds,
8682
- *optionals&.flatten(1),
8683
- rest,
8684
- *posts,
8685
- *keywords&.flat_map { |(key, value)| [key, value || nil] },
8686
- (keyword_rest if keyword_rest != :nil),
8687
- block
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
- q.breakable(force: true)
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
- q.group(0, "%i[", "]") do
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
- qsymbols_beg = find_token(QSymbolsBeg)
9760
+ beginning = find_token(QSymbolsBeg)
9136
9761
 
9137
- QSymbols.new(elements: [], location: qsymbols_beg.location)
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
- q.group(0, "%w[", "]") do
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
- qwords_beg = find_token(QWordsBeg)
9880
+ beginning = find_token(QWordsBeg)
9240
9881
 
9241
- QWords.new(elements: [], location: qwords_beg.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(braces ? "%r{" : "/")
10217
+ q.text("/")
9556
10218
  q.format_each(parts)
9557
- q.text(braces ? "}" : "/")
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 && body.first.is_a?(VoidStmt)
10383
- q.format(body.last)
10384
- q.break_parent
10385
- return
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.is_a?(AccessCtrl) || body[index - 1].is_a?(AccessCtrl)
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
- StringEmbExpr.new(
10740
- statements: statements,
10741
- location: embexpr_beg.location.to(embexpr_end.location)
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: tstring_beg.location.to(tstring_end.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
- q.group(0, "%I[", "]") do
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
- symbols_beg = find_token(SymbolsBeg)
11870
+ beginning = find_token(SymbolsBeg)
11168
11871
 
11169
- Symbols.new(elements: [], location: symbols_beg.location)
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
- q.text("#{keyword} ")
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
- LoopFormatter.new("until", self, statement).format(q)
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
- # :call-seq:
12289
- # on_var_ref: ((Const | CVar | GVar | Ident | IVar | Kw) value) -> VarRef
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
- # private
13039
+ # case value
13040
+ # in ^variable
13041
+ # end
12298
13042
  #
12299
- class AccessCtrl
12300
- # [Ident] the value of this expression
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.format(value)
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("access_ctrl")
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
- type: :access_ctrl,
12337
- value: value,
12338
- loc: location,
12339
- cmts: comments
12340
- }.to_json(*opts)
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) -> AccessCtrl | VCall
13150
+ # on_vcall: (Ident ident) -> VCall
12393
13151
  def on_vcall(ident)
12394
- @controls ||= %w[private protected public].freeze
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
- statements.bind(arguments.location.end_char, ending.location.start_char)
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
- LoopFormatter.new("while", self, statement).format(q)
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
- q.group(0, "%W[", "]") do
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
- words_beg = find_token(WordsBeg)
13700
+ beginning = find_token(WordsBeg)
12896
13701
 
12897
- Words.new(elements: [], location: words_beg.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
- q.text(" ") if arguments.is_a?(Args)
13051
- q.format(arguments)
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