syntax_tree 0.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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