syntax_tree 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: adfcb697cee07c1c1fa0ad15538079a1fb7f40cfe622317b4cff91546555e463
4
- data.tar.gz: 27fc9fe177d07ba066314a8b80f542ae8e6e94fff85f9edaf82cefc32616a7d1
3
+ metadata.gz: 5db5f5652e0786475f56ea361b693340998d3f1505d57e2300182d2cc811bcf3
4
+ data.tar.gz: 3ee5db660541f5355e80fe7d63c4995e30c5089b45286ab1e7ac29ccc62c8bab
5
5
  SHA512:
6
- metadata.gz: 975dc25af26617665dec05c4a902732b693045be67917dba0f0b1e4c8a7e3016a8c69d1556f850c8be2b9f2d6fb07d06d6ee0d69245cd71ab5067f4d0690e909
7
- data.tar.gz: 302d3744abc6cea5ce13d094322ff4071dc3c9487442f7c74fccd2a1ff7bb93ddc98017a5b5997369795df80b098981f8cbbe34c54315db4ccffc7a8e92f546c
6
+ metadata.gz: 9be4902085676d0c4c76eaa29c5b00878916409f2b63a519376ab1a9521cf6204c7aa0a540e61cfa607079ebe17ef42c960c63ab752da300fbdf653be279e4e8
7
+ data.tar.gz: b2923a9ed6e61e3e14b353b200e49ffe39556081a25fb409cf3af4bd8de4e14aa6229c44c20bb76e67b0efbf63b463cdb0a307ff43975ca5a08a362e6af8aac6
data/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.3.0] - 2022-04-22
10
+
11
+ ### Added
12
+
13
+ - [#52](https://github.com/ruby-syntax-tree/syntax_tree/pull/52) - `SyntaxTree::Formatter.format` for formatting an already parsed node.
14
+ - [#56](https://github.com/ruby-syntax-tree/syntax_tree/pull/56) - `if` and `unless` can now be transformed into ternaries if they're simple enough.
15
+ - [#56](https://github.com/ruby-syntax-tree/syntax_tree/pull/56) - Nicely format call chains by one indentation.
16
+ - [#56](https://github.com/ruby-syntax-tree/syntax_tree/pull/56) - Handle trailing operators in call chains when they are necessary because of comments.
17
+ - [#56](https://github.com/ruby-syntax-tree/syntax_tree/pull/56) - Add some specialized formatting for Sorbet `sig` blocks to make them appear nicer.
18
+
19
+ ### Changed
20
+
21
+ - [#53](https://github.com/ruby-syntax-tree/syntax_tree/pull/53) - Optional keyword arguments on method declarations have a value of `nil` now instead of `false`. This makes it easier to use the visitor.
22
+ - [#54](https://github.com/ruby-syntax-tree/syntax_tree/pull/54) - Flow control operators can now skip parentheses for simple, individual arguments. e.g., `break(1)` becomes `break 1`.
23
+ - [#54](https://github.com/ruby-syntax-tree/syntax_tree/pull/54) - Don't allow modifier conditionals to modify ternaries.
24
+ - [#55](https://github.com/ruby-syntax-tree/syntax_tree/pull/55) - Skip parentheses and brackets on arrays for flow control operators. e.g., `break([1, 2, 3])` becomes `break 1, 2, 3`.
25
+ - [#56](https://github.com/ruby-syntax-tree/syntax_tree/pull/56) - Don't add parentheses to method calls if you don't need them.
26
+ - [#56](https://github.com/ruby-syntax-tree/syntax_tree/pull/56) - Format comments on empty parameter sets. e.g., `def foo # bar` should keeps its comment.
27
+ - [#56](https://github.com/ruby-syntax-tree/syntax_tree/pull/56) - `%s[]` symbols on assignments should not indent to the next line.
28
+ - [#56](https://github.com/ruby-syntax-tree/syntax_tree/pull/56) - Empty hash and array literals with comments inside of them should be formatted correctly.
29
+
9
30
  ## [2.2.0] - 2022-04-19
10
31
 
11
32
  ### Added
@@ -166,7 +187,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
166
187
 
167
188
  - 🎉 Initial release! 🎉
168
189
 
169
- [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.2.0...HEAD
190
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.3.0...HEAD
191
+ [2.3.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.2.0...v2.3.0
170
192
  [2.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.1.1...v2.2.0
171
193
  [2.1.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.1.0...v2.1.1
172
194
  [2.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.1...v2.1.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree (2.2.0)
4
+ syntax_tree (2.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -17,7 +17,11 @@ GEM
17
17
  simplecov_json_formatter (0.1.4)
18
18
 
19
19
  PLATFORMS
20
+ arm64-darwin-21
21
+ ruby
22
+ x86_64-darwin-19
20
23
  x86_64-darwin-21
24
+ x86_64-linux
21
25
 
22
26
  DEPENDENCIES
23
27
  bundler
@@ -17,6 +17,13 @@ module SyntaxTree
17
17
  @quote = "\""
18
18
  end
19
19
 
20
+ def self.format(source, node)
21
+ formatter = new(source, [])
22
+ node.format(formatter)
23
+ formatter.flush
24
+ formatter.output.join
25
+ end
26
+
20
27
  def format(node, stackable: true)
21
28
  stack << node if stackable
22
29
  doc = nil
@@ -43,7 +50,7 @@ module SyntaxTree
43
50
  # Print all comments that were found after the node.
44
51
  trailing.each do |comment|
45
52
  line_suffix(priority: COMMENT_PRIORITY) do
46
- text(" ")
53
+ comment.inline? ? text(" ") : breakable
47
54
  comment.format(self)
48
55
  break_parent
49
56
  end
@@ -816,6 +816,29 @@ module SyntaxTree
816
816
  end
817
817
  end
818
818
 
819
+ class EmptyWithCommentsFormatter
820
+ # [LBracket] the opening bracket
821
+ attr_reader :lbracket
822
+
823
+ def initialize(lbracket)
824
+ @lbracket = lbracket
825
+ end
826
+
827
+ def format(q)
828
+ q.group do
829
+ q.text("[")
830
+ q.indent do
831
+ lbracket.comments.each do |comment|
832
+ q.breakable(force: true)
833
+ comment.format(q)
834
+ end
835
+ end
836
+ q.breakable(force: true)
837
+ q.text("]")
838
+ end
839
+ end
840
+ end
841
+
819
842
  # [LBracket] the bracket that opens this array
820
843
  attr_reader :lbracket
821
844
 
@@ -867,6 +890,11 @@ module SyntaxTree
867
890
  return
868
891
  end
869
892
 
893
+ if empty_with_comments?
894
+ EmptyWithCommentsFormatter.new(lbracket).format(q)
895
+ return
896
+ end
897
+
870
898
  q.group do
871
899
  q.format(lbracket)
872
900
 
@@ -919,6 +947,12 @@ module SyntaxTree
919
947
  q.maxwidth * 2
920
948
  )
921
949
  end
950
+
951
+ # If we have an empty array that contains only comments, then we're going
952
+ # to do some special printing to ensure they get indented correctly.
953
+ def empty_with_comments?
954
+ contents.nil? && lbracket.comments.any? && lbracket.comments.none?(&:inline?)
955
+ end
922
956
  end
923
957
 
924
958
  # AryPtn represents matching against an array pattern using the Ruby 2.7+
@@ -1042,17 +1076,16 @@ module SyntaxTree
1042
1076
  # Determins if the following value should be indented or not.
1043
1077
  module AssignFormatting
1044
1078
  def self.skip_indent?(value)
1045
- (value.is_a?(Call) && skip_indent?(value.receiver)) ||
1046
- [
1047
- ArrayLiteral,
1048
- HashLiteral,
1049
- Heredoc,
1050
- Lambda,
1051
- QSymbols,
1052
- QWords,
1053
- Symbols,
1054
- Words
1055
- ].include?(value.class)
1079
+ case value
1080
+ in ArrayLiteral | HashLiteral | Heredoc | Lambda | QSymbols | QWords | Symbols | Words
1081
+ true
1082
+ in Call[receiver:]
1083
+ skip_indent?(receiver)
1084
+ in DynaSymbol[quote:]
1085
+ quote.start_with?("%s")
1086
+ else
1087
+ false
1088
+ end
1056
1089
  end
1057
1090
  end
1058
1091
 
@@ -1552,12 +1585,12 @@ module SyntaxTree
1552
1585
  q.text(" ") unless power
1553
1586
 
1554
1587
  if operator == :<<
1555
- q.text(operator)
1588
+ q.text(operator.to_s)
1556
1589
  q.text(" ")
1557
1590
  q.format(right)
1558
1591
  else
1559
1592
  q.group do
1560
- q.text(operator)
1593
+ q.text(operator.to_s)
1561
1594
 
1562
1595
  q.indent do
1563
1596
  q.breakable(power ? "" : " ")
@@ -2042,12 +2075,12 @@ module SyntaxTree
2042
2075
  end
2043
2076
  end
2044
2077
 
2045
- # Formats either a Break or Next node.
2078
+ # Formats either a Break, Next, or Return node.
2046
2079
  class FlowControlFormatter
2047
2080
  # [String] the keyword to print
2048
2081
  attr_reader :keyword
2049
2082
 
2050
- # [Break | Next] the node being formatted
2083
+ # [Break | Next | Return] the node being formatted
2051
2084
  attr_reader :node
2052
2085
 
2053
2086
  def initialize(keyword, node)
@@ -2056,32 +2089,119 @@ module SyntaxTree
2056
2089
  end
2057
2090
 
2058
2091
  def format(q)
2059
- arguments = node.arguments
2060
-
2061
2092
  q.group do
2062
2093
  q.text(keyword)
2063
2094
 
2064
- if arguments.parts.any?
2065
- if arguments.parts.length == 1
2066
- part = arguments.parts.first
2067
-
2068
- if part.is_a?(Paren)
2069
- q.format(arguments)
2070
- elsif part.is_a?(ArrayLiteral)
2071
- q.text(" ")
2072
- q.format(arguments)
2073
- else
2074
- format_arguments(q, "(", ")")
2075
- end
2076
- else
2077
- format_arguments(q, " [", "]")
2078
- end
2095
+ case node.arguments.parts
2096
+ in []
2097
+ # Here there are no arguments at all, so we're not going to print
2098
+ # anything. This would be like if we had:
2099
+ #
2100
+ # break
2101
+ #
2102
+ in [Paren[contents: { body: [ArrayLiteral[contents: { parts: [_, _, *] }] => array] }]]
2103
+ # Here we have a single argument that is a set of parentheses wrapping
2104
+ # an array literal that has at least 2 elements. We're going to print
2105
+ # the contents of the array directly. This would be like if we had:
2106
+ #
2107
+ # break([1, 2, 3])
2108
+ #
2109
+ # which we will print as:
2110
+ #
2111
+ # break 1, 2, 3
2112
+ #
2113
+ q.text(" ")
2114
+ format_array_contents(q, array)
2115
+ in [Paren[contents: { body: [ArrayLiteral => statement] }]]
2116
+ # Here we have a single argument that is a set of parentheses wrapping
2117
+ # an array literal that has 0 or 1 elements. We're going to skip the
2118
+ # parentheses but print the array itself. This would be like if we
2119
+ # had:
2120
+ #
2121
+ # break([1])
2122
+ #
2123
+ # which we will print as:
2124
+ #
2125
+ # break [1]
2126
+ #
2127
+ q.text(" ")
2128
+ q.format(statement)
2129
+ in [Paren[contents: { body: [statement] }]] if skip_parens?(statement)
2130
+ # Here we have a single argument that is a set of parentheses that
2131
+ # themselves contain a single statement. That statement is a simple
2132
+ # value that we can skip the parentheses for. This would be like if we
2133
+ # had:
2134
+ #
2135
+ # break(1)
2136
+ #
2137
+ # which we will print as:
2138
+ #
2139
+ # break 1
2140
+ #
2141
+ q.text(" ")
2142
+ q.format(statement)
2143
+ in [Paren => part]
2144
+ # Here we have a single argument that is a set of parentheses. We're
2145
+ # going to print the parentheses themselves as if they were the set of
2146
+ # arguments. This would be like if we had:
2147
+ #
2148
+ # break(foo.bar)
2149
+ #
2150
+ q.format(part)
2151
+ in [ArrayLiteral[contents: { parts: [_, _, *] }] => array]
2152
+ # Here there is a single argument that is an array literal with at
2153
+ # least two elements. We skip directly into the array literal's
2154
+ # elements in order to print the contents. This would be like if we
2155
+ # had:
2156
+ #
2157
+ # break [1, 2, 3]
2158
+ #
2159
+ # which we will print as:
2160
+ #
2161
+ # break 1, 2, 3
2162
+ #
2163
+ q.text(" ")
2164
+ format_array_contents(q, array)
2165
+ in [ArrayLiteral => part]
2166
+ # Here there is a single argument that is an array literal with 0 or 1
2167
+ # elements. In this case we're going to print the array as it is
2168
+ # because skipping the brackets would change the remaining. This would
2169
+ # be like if we had:
2170
+ #
2171
+ # break []
2172
+ # break [1]
2173
+ #
2174
+ q.text(" ")
2175
+ q.format(part)
2176
+ in [_]
2177
+ # Here there is a single argument that hasn't matched one of our
2178
+ # previous cases. We're going to print the argument as it is. This
2179
+ # would be like if we had:
2180
+ #
2181
+ # break foo
2182
+ #
2183
+ format_arguments(q, "(", ")")
2184
+ else
2185
+ # If there are multiple arguments, format them all. If the line is
2186
+ # going to break into multiple, then use brackets to start and end the
2187
+ # expression.
2188
+ format_arguments(q, " [", "]")
2079
2189
  end
2080
2190
  end
2081
2191
  end
2082
2192
 
2083
2193
  private
2084
2194
 
2195
+ def format_array_contents(q, array)
2196
+ q.if_break { q.text("[") }
2197
+ q.indent do
2198
+ q.breakable("")
2199
+ q.format(array.contents)
2200
+ end
2201
+ q.breakable("")
2202
+ q.if_break { q.text("]") }
2203
+ end
2204
+
2085
2205
  def format_arguments(q, opening, closing)
2086
2206
  q.if_break { q.text(opening) }
2087
2207
  q.indent do
@@ -2091,6 +2211,17 @@ module SyntaxTree
2091
2211
  q.breakable("")
2092
2212
  q.if_break { q.text(closing) }
2093
2213
  end
2214
+
2215
+ def skip_parens?(node)
2216
+ case node
2217
+ in FloatLiteral | Imaginary | Int | RationalLiteral
2218
+ true
2219
+ in VarRef[value: Const | CVar | GVar | IVar | Kw]
2220
+ true
2221
+ else
2222
+ false
2223
+ end
2224
+ end
2094
2225
  end
2095
2226
 
2096
2227
  # Break represents using the +break+ keyword.
@@ -2156,6 +2287,181 @@ module SyntaxTree
2156
2287
  end
2157
2288
  end
2158
2289
 
2290
+ class CallChainFormatter
2291
+ # [Call | MethodAddBlock] the top of the call chain
2292
+ attr_reader :node
2293
+
2294
+ def initialize(node)
2295
+ @node = node
2296
+ end
2297
+
2298
+ def format(q)
2299
+ children = [node]
2300
+ threshold = 3
2301
+
2302
+ # First, walk down the chain until we get to the point where we're not
2303
+ # longer at a chainable node.
2304
+ while true
2305
+ case children.last
2306
+ in Call[receiver: Call]
2307
+ children << children.last.receiver
2308
+ in Call[receiver: MethodAddBlock[call: Call]]
2309
+ children << children.last.receiver
2310
+ in MethodAddBlock[call: Call]
2311
+ children << children.last.call
2312
+ else
2313
+ break
2314
+ end
2315
+ end
2316
+
2317
+ # Here, we have very specialized behavior where if we're within a sig
2318
+ # block, then we're going to assume we're creating a Sorbet type
2319
+ # signature. In that case, we really want the threshold to be lowered so
2320
+ # that we create method chains off of any two method calls within the
2321
+ # block. For more details, see
2322
+ # https://github.com/prettier/plugin-ruby/issues/863.
2323
+ parents = q.parents.take(4)
2324
+ if parent = parents[2]
2325
+ # If we're at a do_block, then we want to go one more level up. This is
2326
+ # because do blocks have BodyStmt nodes instead of just Statements
2327
+ # nodes.
2328
+ parent = parents[3] if parent.is_a?(DoBlock)
2329
+
2330
+ case parent
2331
+ in MethodAddBlock[call: FCall[value: { value: "sig" }]]
2332
+ threshold = 2
2333
+ else
2334
+ end
2335
+ end
2336
+
2337
+ if children.length >= threshold
2338
+ q.group { q.if_break { format_chain(q, children) }.if_flat { node.format_contents(q) } }
2339
+ else
2340
+ node.format_contents(q)
2341
+ end
2342
+ end
2343
+
2344
+ def format_chain(q, children)
2345
+ # We're going to have some specialized behavior for if it's an entire
2346
+ # chain of calls without arguments except for the last one. This is common
2347
+ # enough in Ruby source code that it's worth the extra complexity here.
2348
+ empty_except_last =
2349
+ children.drop(1).all? do |child|
2350
+ child.is_a?(Call) && child.arguments.nil?
2351
+ end
2352
+
2353
+ # Here, we're going to add all of the children onto the stack of the
2354
+ # formatter so it's as if we had descending normally into them. This is
2355
+ # necessary so they can check their parents as normal.
2356
+ q.stack.concat(children)
2357
+ q.format(children.last.receiver)
2358
+
2359
+ q.group do
2360
+ if attach_directly?(children.last)
2361
+ format_child(q, children.pop)
2362
+ q.stack.pop
2363
+ end
2364
+
2365
+ q.indent do
2366
+ # We track another variable that checks if you need to move the
2367
+ # operator to the previous line in case there are trailing comments
2368
+ # and a trailing operator.
2369
+ skip_operator = false
2370
+
2371
+ while child = children.pop
2372
+ case child
2373
+ in Call[receiver: Call[message: { value: "where" }], message: { value: "not" }]
2374
+ # This is very specialized behavior wherein we group
2375
+ # .where.not calls together because it looks better. For more
2376
+ # information, see
2377
+ # https://github.com/prettier/plugin-ruby/issues/862.
2378
+ in Call
2379
+ # If we're at a Call node and not a MethodAddBlock node in the
2380
+ # chain then we're going to add a newline so it indents properly.
2381
+ q.breakable("")
2382
+ else
2383
+ end
2384
+
2385
+ format_child(
2386
+ q,
2387
+ child,
2388
+ skip_comments: children.empty?,
2389
+ skip_operator: skip_operator,
2390
+ skip_attached: empty_except_last && children.empty?
2391
+ )
2392
+
2393
+ # If the parent call node has a comment on the message then we need
2394
+ # to print the operator trailing in order to keep it working.
2395
+ case children.last
2396
+ in Call[message: { comments: [_, *] }, operator:]
2397
+ q.format(CallOperatorFormatter.new(operator))
2398
+ skip_operator = true
2399
+ else
2400
+ skip_operator = false
2401
+ end
2402
+
2403
+ # Pop off the formatter's stack so that it aligns with what would
2404
+ # have happened if we had been formatting normally.
2405
+ q.stack.pop
2406
+ end
2407
+ end
2408
+ end
2409
+
2410
+ if empty_except_last
2411
+ case node
2412
+ in Call
2413
+ node.format_arguments(q)
2414
+ in MethodAddBlock[block:]
2415
+ q.format(block)
2416
+ end
2417
+ end
2418
+ end
2419
+
2420
+ def self.chained?(node)
2421
+ case node
2422
+ in Call | MethodAddBlock[call: Call]
2423
+ true
2424
+ else
2425
+ false
2426
+ end
2427
+ end
2428
+
2429
+ private
2430
+
2431
+ # For certain nodes, we want to attach directly to the end and don't
2432
+ # want to indent the first call. So we'll pop off the first children and
2433
+ # format it separately here.
2434
+ def attach_directly?(node)
2435
+ [ArrayLiteral, HashLiteral, Heredoc, If, Unless, XStringLiteral]
2436
+ .include?(node.receiver.class)
2437
+ end
2438
+
2439
+ def format_child(q, child, skip_comments: false, skip_operator: false, skip_attached: false)
2440
+ # First, format the actual contents of the child.
2441
+ case child
2442
+ in Call
2443
+ q.group do
2444
+ q.format(CallOperatorFormatter.new(child.operator)) unless skip_operator
2445
+ q.format(child.message) if child.message != :call
2446
+ child.format_arguments(q) unless skip_attached
2447
+ end
2448
+ in MethodAddBlock
2449
+ q.format(child.block) unless skip_attached
2450
+ end
2451
+
2452
+ # If there are any comments on this node then we need to explicitly print
2453
+ # them out here since we're bypassing the normal comment printing.
2454
+ if child.comments.any? && !skip_comments
2455
+ child.comments.each do |comment|
2456
+ comment.inline? ? q.text(" ") : q.breakable
2457
+ comment.format(q)
2458
+ end
2459
+
2460
+ q.break_parent
2461
+ end
2462
+ end
2463
+ end
2464
+
2159
2465
  # Call represents a method call.
2160
2466
  #
2161
2467
  # receiver.message
@@ -2219,6 +2525,29 @@ module SyntaxTree
2219
2525
  end
2220
2526
 
2221
2527
  def format(q)
2528
+ # If we're at the top of a call chain, then we're going to do some
2529
+ # specialized printing in case we can print it nicely. We _only_ do this
2530
+ # at the top of the chain to avoid weird recursion issues.
2531
+ if !CallChainFormatter.chained?(q.parent) && CallChainFormatter.chained?(receiver)
2532
+ q.group { q.if_break { CallChainFormatter.new(self).format(q) }.if_flat { format_contents(q) } }
2533
+ else
2534
+ format_contents(q)
2535
+ end
2536
+ end
2537
+
2538
+ def format_arguments(q)
2539
+ case arguments
2540
+ in ArgParen
2541
+ q.format(arguments)
2542
+ in Args
2543
+ q.text(" ")
2544
+ q.format(arguments)
2545
+ else
2546
+ # Do nothing if there are no arguments.
2547
+ end
2548
+ end
2549
+
2550
+ def format_contents(q)
2222
2551
  call_operator = CallOperatorFormatter.new(operator)
2223
2552
 
2224
2553
  q.group do
@@ -2241,7 +2570,7 @@ module SyntaxTree
2241
2570
  q.format(message) if message != :call
2242
2571
  end
2243
2572
 
2244
- q.format(arguments) if arguments
2573
+ format_arguments(q)
2245
2574
  end
2246
2575
  end
2247
2576
  end
@@ -2507,7 +2836,7 @@ module SyntaxTree
2507
2836
  end
2508
2837
 
2509
2838
  alias deconstruct child_nodes
2510
-
2839
+
2511
2840
  def deconstruct_keys(keys)
2512
2841
  { value: value, location: location }
2513
2842
  end
@@ -2558,26 +2887,24 @@ module SyntaxTree
2558
2887
  def format(q)
2559
2888
  q.group do
2560
2889
  q.format(message)
2561
- q.text(" ")
2562
-
2563
- if align?(self)
2564
- q.nest(message.value.length + 1) { q.format(arguments) }
2565
- else
2566
- q.format(arguments)
2567
- end
2890
+ align(q, self) { q.format(arguments) }
2568
2891
  end
2569
2892
  end
2570
2893
 
2571
2894
  private
2572
2895
 
2573
- def align?(node)
2896
+ def align(q, node, &block)
2574
2897
  case node.arguments
2575
2898
  in Args[parts: [Def | Defs | DefEndless]]
2576
- false
2899
+ q.text(" ")
2900
+ yield
2901
+ in Args[parts: [IfOp]]
2902
+ yield
2577
2903
  in Args[parts: [Command => command]]
2578
- align?(command)
2904
+ align(q, command, &block)
2579
2905
  else
2580
- true
2906
+ q.text(" ")
2907
+ q.nest(message.value.length + 1) { yield }
2581
2908
  end
2582
2909
  end
2583
2910
  end
@@ -2649,9 +2976,15 @@ module SyntaxTree
2649
2976
  q.format(message)
2650
2977
  end
2651
2978
 
2652
- if arguments
2979
+ case arguments
2980
+ in Args[parts: [IfOp]]
2981
+ q.if_flat { q.text(" ") }
2982
+ q.format(arguments)
2983
+ in Args
2653
2984
  q.text(" ")
2654
2985
  q.nest(argument_alignment(q, doc)) { q.format(arguments) }
2986
+ else
2987
+ # If there are no arguments, print nothing.
2655
2988
  end
2656
2989
  end
2657
2990
  end
@@ -3037,7 +3370,10 @@ module SyntaxTree
3037
3370
  q.group do
3038
3371
  q.text("def ")
3039
3372
  q.format(name)
3040
- q.format(params) if !params.is_a?(Params) || !params.empty?
3373
+
3374
+ if !params.is_a?(Params) || !params.empty? || params.comments.any?
3375
+ q.format(params)
3376
+ end
3041
3377
  end
3042
3378
 
3043
3379
  unless bodystmt.empty?
@@ -3257,7 +3593,10 @@ module SyntaxTree
3257
3593
  q.format(target)
3258
3594
  q.format(CallOperatorFormatter.new(operator), stackable: false)
3259
3595
  q.format(name)
3260
- q.format(params) if !params.is_a?(Params) || !params.empty?
3596
+
3597
+ if !params.is_a?(Params) || !params.empty? || params.comments.any?
3598
+ q.format(params)
3599
+ end
3261
3600
  end
3262
3601
 
3263
3602
  unless bodystmt.empty?
@@ -3792,7 +4131,7 @@ module SyntaxTree
3792
4131
  end
3793
4132
 
3794
4133
  alias deconstruct child_nodes
3795
-
4134
+
3796
4135
  def deconstruct_keys(keys)
3797
4136
  { value: value, location: location }
3798
4137
  end
@@ -3822,7 +4161,7 @@ module SyntaxTree
3822
4161
  end
3823
4162
 
3824
4163
  alias deconstruct child_nodes
3825
-
4164
+
3826
4165
  def deconstruct_keys(keys)
3827
4166
  { value: value, location: location }
3828
4167
  end
@@ -3854,7 +4193,7 @@ module SyntaxTree
3854
4193
  end
3855
4194
 
3856
4195
  alias deconstruct child_nodes
3857
-
4196
+
3858
4197
  def deconstruct_keys(keys)
3859
4198
  { value: value, location: location }
3860
4199
  end
@@ -4002,7 +4341,15 @@ module SyntaxTree
4002
4341
 
4003
4342
  def format(q)
4004
4343
  q.format(value)
4005
- q.format(arguments)
4344
+
4345
+ if arguments.is_a?(ArgParen) && arguments.arguments.nil? && !value.is_a?(Const)
4346
+ # If you're using an explicit set of parentheses on something that looks
4347
+ # like a constant, then we need to match that in order to maintain valid
4348
+ # Ruby. For example, you could do something like Foo(), on which we
4349
+ # would need to keep the parentheses to make it look like a method call.
4350
+ else
4351
+ q.format(arguments)
4352
+ end
4006
4353
  end
4007
4354
  end
4008
4355
 
@@ -4275,6 +4622,29 @@ module SyntaxTree
4275
4622
  # { key => value }
4276
4623
  #
4277
4624
  class HashLiteral < Node
4625
+ class EmptyWithCommentsFormatter
4626
+ # [LBrace] the opening brace
4627
+ attr_reader :lbrace
4628
+
4629
+ def initialize(lbrace)
4630
+ @lbrace = lbrace
4631
+ end
4632
+
4633
+ def format(q)
4634
+ q.group do
4635
+ q.text("{")
4636
+ q.indent do
4637
+ lbrace.comments.each do |comment|
4638
+ q.breakable(force: true)
4639
+ comment.format(q)
4640
+ end
4641
+ end
4642
+ q.breakable(force: true)
4643
+ q.text("}")
4644
+ end
4645
+ end
4646
+ end
4647
+
4278
4648
  # [LBrace] the left brace that opens this hash
4279
4649
  attr_reader :lbrace
4280
4650
 
@@ -4319,7 +4689,18 @@ module SyntaxTree
4319
4689
 
4320
4690
  private
4321
4691
 
4692
+ # If we have an empty hash that contains only comments, then we're going
4693
+ # to do some special printing to ensure they get indented correctly.
4694
+ def empty_with_comments?
4695
+ assocs.empty? && lbrace.comments.any? && lbrace.comments.none?(&:inline?)
4696
+ end
4697
+
4322
4698
  def format_contents(q)
4699
+ if empty_with_comments?
4700
+ EmptyWithCommentsFormatter.new(lbrace).format(q)
4701
+ return
4702
+ end
4703
+
4323
4704
  q.format(lbrace)
4324
4705
 
4325
4706
  if assocs.empty?
@@ -4641,6 +5022,62 @@ module SyntaxTree
4641
5022
  end
4642
5023
  end
4643
5024
 
5025
+ # In order for an `if` or `unless` expression to be shortened to a ternary,
5026
+ # there has to be one and only one consequent clause which is an Else. Both
5027
+ # the body of the main node and the body of the Else node must have only one
5028
+ # statement, and that statement must not be on the denied list of potential
5029
+ # statements.
5030
+ module Ternaryable
5031
+ class << self
5032
+ def call(q, node)
5033
+ case q.parents.take(2)[1]
5034
+ in Paren[contents: Statements[body: [node]]]
5035
+ # If this is a conditional inside of a parentheses as the only
5036
+ # content, then we don't want to transform it into a ternary.
5037
+ # Presumably the user wanted it to be an explicit conditional because
5038
+ # there are parentheses around it. So we'll just leave it in place.
5039
+ false
5040
+ else
5041
+ # Otherwise, we're going to check the conditional for certain cases.
5042
+ case node
5043
+ in { predicate: Assign | Command | CommandCall | MAssign | OpAssign }
5044
+ false
5045
+ in { statements: { body: [truthy] }, consequent: Else[statements: { body: [falsy] }] }
5046
+ ternaryable?(truthy) && ternaryable?(falsy)
5047
+ else
5048
+ false
5049
+ end
5050
+ end
5051
+ end
5052
+
5053
+ private
5054
+
5055
+ # Certain expressions cannot be reduced to a ternary without adding
5056
+ # parentheses around them. In this case we say they cannot be ternaried and
5057
+ # default instead to breaking them into multiple lines.
5058
+ def ternaryable?(statement)
5059
+ # This is a list of nodes that should not be allowed to be a part of a
5060
+ # ternary clause.
5061
+ no_ternary = [
5062
+ Alias, Assign, Break, Command, CommandCall, Heredoc, If, IfMod, IfOp,
5063
+ Lambda, MAssign, Next, OpAssign, RescueMod, Return, Return0, Super,
5064
+ Undef, Unless, UnlessMod, Until, UntilMod, VarAlias, VoidStmt, While,
5065
+ WhileMod, Yield, Yield0, ZSuper
5066
+ ]
5067
+
5068
+ # Here we're going to check that the only statement inside the
5069
+ # statements node is no a part of our denied list of nodes that can be
5070
+ # ternaries.
5071
+ #
5072
+ # If the user is using one of the lower precedence "and" or "or"
5073
+ # operators, then we can't use a ternary expression as it would break
5074
+ # the flow control.
5075
+ !no_ternary.include?(statement.class) &&
5076
+ !(statement.is_a?(Binary) && %i[and or].include?(statement.operator))
5077
+ end
5078
+ end
5079
+ end
5080
+
4644
5081
  # Formats an If or Unless node.
4645
5082
  class ConditionalFormatter
4646
5083
  # [String] the keyword associated with this conditional
@@ -4655,6 +5092,13 @@ module SyntaxTree
4655
5092
  end
4656
5093
 
4657
5094
  def format(q)
5095
+ # If we can transform this node into a ternary, then we're going to print
5096
+ # a special version that uses the ternary operator if it fits on one line.
5097
+ if Ternaryable.call(q, node)
5098
+ format_ternary(q)
5099
+ return
5100
+ end
5101
+
4658
5102
  # If the predicate of the conditional contains an assignment (in which
4659
5103
  # case we can't know for certain that that assignment doesn't impact the
4660
5104
  # statements inside the conditional) then we can't use the modifier form
@@ -4664,7 +5108,7 @@ module SyntaxTree
4664
5108
  return
4665
5109
  end
4666
5110
 
4667
- if node.consequent || node.statements.empty?
5111
+ if node.consequent || node.statements.empty? || contains_conditional?
4668
5112
  q.group { format_break(q, force: true) }
4669
5113
  else
4670
5114
  q.group do
@@ -4700,6 +5144,59 @@ module SyntaxTree
4700
5144
  q.breakable(force: force)
4701
5145
  q.text("end")
4702
5146
  end
5147
+
5148
+ def format_ternary(q)
5149
+ q.group do
5150
+ q.if_break do
5151
+ q.text("#{keyword} ")
5152
+ q.nest(keyword.length + 1) { q.format(node.predicate) }
5153
+
5154
+ q.indent do
5155
+ q.breakable
5156
+ q.format(node.statements)
5157
+ end
5158
+
5159
+ q.breakable
5160
+ q.group do
5161
+ q.format(node.consequent.keyword)
5162
+ q.indent do
5163
+ # This is a very special case of breakable where we want to force
5164
+ # it into the output but we _don't_ want to explicitly break the
5165
+ # parent. If a break-parent shows up in the tree, then it's going
5166
+ # to force it all the way up to the tree, which is going to negate
5167
+ # the ternary. Maybe this should be an option in prettyprint? As
5168
+ # in force: :no_break_parent or something.
5169
+ q.target << PrettyPrint::Breakable.new(" ", 1, force: true)
5170
+ q.format(node.consequent.statements)
5171
+ end
5172
+ end
5173
+
5174
+ q.breakable
5175
+ q.text("end")
5176
+ end.if_flat do
5177
+ Parentheses.flat(q) do
5178
+ q.format(node.predicate)
5179
+ q.text(" ? ")
5180
+
5181
+ statements = [node.statements, node.consequent.statements]
5182
+ statements.reverse! if keyword == "unless"
5183
+
5184
+ q.format(statements[0])
5185
+ q.text(" : ")
5186
+ q.format(statements[1])
5187
+ end
5188
+ end
5189
+ end
5190
+ end
5191
+
5192
+ def contains_conditional?
5193
+ case node
5194
+ in { statements: { body: [If | IfMod | IfOp | Unless | UnlessMod] } }
5195
+ true
5196
+ else
5197
+ false
5198
+ end
5199
+ end
4703
5200
  end
4704
5201
 
4705
5202
  # If represents the first clause in an +if+ chain.
@@ -4812,7 +5309,7 @@ module SyntaxTree
4812
5309
  Yield0, ZSuper
4813
5310
  ]
4814
5311
 
4815
- if force_flat.include?(truthy.class) || force_flat.include?(falsy.class)
5312
+ if q.parent.is_a?(Paren) || force_flat.include?(truthy.class) || force_flat.include?(falsy.class)
4816
5313
  q.group { format_flat(q) }
4817
5314
  return
4818
5315
  end
@@ -5285,7 +5782,7 @@ module SyntaxTree
5285
5782
  end
5286
5783
 
5287
5784
  alias deconstruct child_nodes
5288
-
5785
+
5289
5786
  def deconstruct_keys(keys)
5290
5787
  { value: value, location: location }
5291
5788
  end
@@ -5559,6 +6056,17 @@ module SyntaxTree
5559
6056
  end
5560
6057
 
5561
6058
  def format(q)
6059
+ # If we're at the top of a call chain, then we're going to do some
6060
+ # specialized printing in case we can print it nicely. We _only_ do this
6061
+ # at the top of the chain to avoid weird recursion issues.
6062
+ if !CallChainFormatter.chained?(q.parent) && CallChainFormatter.chained?(call)
6063
+ q.group { q.if_break { CallChainFormatter.new(self).format(q) }.if_flat { format_contents(q) } }
6064
+ else
6065
+ format_contents(q)
6066
+ end
6067
+ end
6068
+
6069
+ def format_contents(q)
5562
6070
  q.format(call)
5563
6071
  q.format(block)
5564
6072
  end
@@ -6180,7 +6688,9 @@ module SyntaxTree
6180
6688
  q.format(rest) if rest && rest.is_a?(ExcessedComma)
6181
6689
  end
6182
6690
 
6183
- if [Def, Defs, DefEndless].include?(q.parent.class)
6691
+ if ![Def, Defs, DefEndless].include?(q.parent.class) || parts.empty?
6692
+ q.nest(0, &contents)
6693
+ else
6184
6694
  q.group(0, "(", ")") do
6185
6695
  q.indent do
6186
6696
  q.breakable("")
@@ -6188,8 +6698,6 @@ module SyntaxTree
6188
6698
  end
6189
6699
  q.breakable("")
6190
6700
  end
6191
- else
6192
- q.nest(0, &contents)
6193
6701
  end
6194
6702
  end
6195
6703
  end
@@ -6410,7 +6918,7 @@ module SyntaxTree
6410
6918
  end
6411
6919
 
6412
6920
  alias deconstruct child_nodes
6413
-
6921
+
6414
6922
  def deconstruct_keys(keys)
6415
6923
  { value: value, location: location }
6416
6924
  end
@@ -6510,7 +7018,7 @@ module SyntaxTree
6510
7018
  end
6511
7019
 
6512
7020
  alias deconstruct child_nodes
6513
-
7021
+
6514
7022
  def deconstruct_keys(keys)
6515
7023
  { value: value, location: location }
6516
7024
  end
@@ -6571,7 +7079,7 @@ module SyntaxTree
6571
7079
  end
6572
7080
 
6573
7081
  alias deconstruct child_nodes
6574
-
7082
+
6575
7083
  def deconstruct_keys(keys)
6576
7084
  { value: value, location: location }
6577
7085
  end
@@ -6596,7 +7104,7 @@ module SyntaxTree
6596
7104
  end
6597
7105
 
6598
7106
  alias deconstruct child_nodes
6599
-
7107
+
6600
7108
  def deconstruct_keys(keys)
6601
7109
  { value: value, location: location }
6602
7110
  end
@@ -6667,7 +7175,7 @@ module SyntaxTree
6667
7175
  end
6668
7176
 
6669
7177
  alias deconstruct child_nodes
6670
-
7178
+
6671
7179
  def deconstruct_keys(keys)
6672
7180
  { beginning: beginning, parts: parts, location: location }
6673
7181
  end
@@ -6700,7 +7208,7 @@ module SyntaxTree
6700
7208
  end
6701
7209
 
6702
7210
  alias deconstruct child_nodes
6703
-
7211
+
6704
7212
  def deconstruct_keys(keys)
6705
7213
  { value: value, location: location }
6706
7214
  end
@@ -6734,7 +7242,7 @@ module SyntaxTree
6734
7242
  end
6735
7243
 
6736
7244
  alias deconstruct child_nodes
6737
-
7245
+
6738
7246
  def deconstruct_keys(keys)
6739
7247
  { value: value, location: location }
6740
7248
  end
@@ -7220,7 +7728,7 @@ module SyntaxTree
7220
7728
  @value = value
7221
7729
  @location = location
7222
7730
  end
7223
-
7731
+
7224
7732
  def accept(visitor)
7225
7733
  visitor.visit_rparen(self)
7226
7734
  end
@@ -7230,7 +7738,7 @@ module SyntaxTree
7230
7738
  end
7231
7739
 
7232
7740
  alias deconstruct child_nodes
7233
-
7741
+
7234
7742
  def deconstruct_keys(keys)
7235
7743
  { value: value, location: location }
7236
7744
  end
@@ -7442,19 +7950,20 @@ module SyntaxTree
7442
7950
  comment = parser_comments[comment_index]
7443
7951
  location = comment.location
7444
7952
 
7445
- if !comment.inline? && (start_char <= location.start_char) &&
7446
- (end_char >= location.end_char) && !comment.ignore?
7447
- parser_comments.delete_at(comment_index)
7448
-
7449
- while (node = body[body_index]) &&
7450
- (
7451
- node.is_a?(VoidStmt) ||
7452
- node.location.start_char < location.start_char
7453
- )
7953
+ if !comment.inline? && (start_char <= location.start_char) && (end_char >= location.end_char) && !comment.ignore?
7954
+ while (node = body[body_index]) && (node.is_a?(VoidStmt) || node.location.start_char < location.start_char)
7454
7955
  body_index += 1
7455
7956
  end
7456
7957
 
7457
- body.insert(body_index, comment)
7958
+ if body_index != 0 && body[body_index - 1].location.start_char < location.start_char && body[body_index - 1].location.end_char > location.start_char
7959
+ # The previous node entirely encapsules the comment, so we don't
7960
+ # want to attach it here since it will get attached normally. This
7961
+ # is mostly in the case of hash and array literals.
7962
+ comment_index += 1
7963
+ else
7964
+ parser_comments.delete_at(comment_index)
7965
+ body.insert(body_index, comment)
7966
+ end
7458
7967
  else
7459
7968
  comment_index += 1
7460
7969
  end
@@ -7485,7 +7994,7 @@ module SyntaxTree
7485
7994
  end
7486
7995
 
7487
7996
  alias deconstruct child_nodes
7488
-
7997
+
7489
7998
  def deconstruct_keys(keys)
7490
7999
  { parts: parts, location: location }
7491
8000
  end
@@ -7782,7 +8291,7 @@ module SyntaxTree
7782
8291
  end
7783
8292
 
7784
8293
  alias deconstruct child_nodes
7785
-
8294
+
7786
8295
  def deconstruct_keys(keys)
7787
8296
  { value: value, location: location }
7788
8297
  end
@@ -7812,7 +8321,7 @@ module SyntaxTree
7812
8321
  end
7813
8322
 
7814
8323
  alias deconstruct child_nodes
7815
-
8324
+
7816
8325
  def deconstruct_keys(keys)
7817
8326
  { value: value, location: location }
7818
8327
  end
@@ -7943,7 +8452,7 @@ module SyntaxTree
7943
8452
  end
7944
8453
 
7945
8454
  alias deconstruct child_nodes
7946
-
8455
+
7947
8456
  def deconstruct_keys(keys)
7948
8457
  { value: value, location: location }
7949
8458
  end
@@ -7972,7 +8481,7 @@ module SyntaxTree
7972
8481
  end
7973
8482
 
7974
8483
  alias deconstruct child_nodes
7975
-
8484
+
7976
8485
  def deconstruct_keys(keys)
7977
8486
  { value: value, location: location }
7978
8487
  end
@@ -8002,7 +8511,7 @@ module SyntaxTree
8002
8511
  end
8003
8512
 
8004
8513
  alias deconstruct child_nodes
8005
-
8514
+
8006
8515
  def deconstruct_keys(keys)
8007
8516
  { value: value, location: location }
8008
8517
  end
@@ -8113,7 +8622,7 @@ module SyntaxTree
8113
8622
  end
8114
8623
 
8115
8624
  alias deconstruct child_nodes
8116
-
8625
+
8117
8626
  def deconstruct_keys(keys)
8118
8627
  { value: value, location: location }
8119
8628
  end
@@ -8191,7 +8700,7 @@ module SyntaxTree
8191
8700
  end
8192
8701
 
8193
8702
  alias deconstruct child_nodes
8194
-
8703
+
8195
8704
  def deconstruct_keys(keys)
8196
8705
  { value: value, location: location }
8197
8706
  end
@@ -8238,9 +8747,26 @@ module SyntaxTree
8238
8747
  end
8239
8748
 
8240
8749
  def format(q)
8241
- q.text(parentheses ? "not(" : "not ")
8750
+ parent = q.parents.take(2)[1]
8751
+ ternary = (parent.is_a?(If) || parent.is_a?(Unless)) && Ternaryable.call(q, parent)
8752
+
8753
+ q.text("not")
8754
+
8755
+ if parentheses
8756
+ q.text("(")
8757
+ elsif ternary
8758
+ q.if_break { q.text(" ") }.if_flat { q.text("(") }
8759
+ else
8760
+ q.text(" ")
8761
+ end
8762
+
8242
8763
  q.format(statement) if statement
8243
- q.text(")") if parentheses
8764
+
8765
+ if parentheses
8766
+ q.text(")")
8767
+ elsif ternary
8768
+ q.if_flat { q.text(")") }
8769
+ end
8244
8770
  end
8245
8771
  end
8246
8772
 
@@ -9216,7 +9742,7 @@ module SyntaxTree
9216
9742
  end
9217
9743
 
9218
9744
  alias deconstruct child_nodes
9219
-
9745
+
9220
9746
  def deconstruct_keys(keys)
9221
9747
  { value: value, location: location }
9222
9748
  end
@@ -9245,7 +9771,7 @@ module SyntaxTree
9245
9771
  end
9246
9772
 
9247
9773
  alias deconstruct child_nodes
9248
-
9774
+
9249
9775
  def deconstruct_keys(keys)
9250
9776
  { parts: parts, location: location }
9251
9777
  end
@@ -2079,12 +2079,16 @@ module SyntaxTree
2079
2079
  keyword_rest,
2080
2080
  block
2081
2081
  )
2082
+ # This is to make it so that required keyword arguments
2083
+ # have a `nil` for the value instead of a `false`.
2084
+ keywords&.map! { |(key, value)| [key, value || nil] }
2085
+
2082
2086
  parts = [
2083
2087
  *requireds,
2084
2088
  *optionals&.flatten(1),
2085
2089
  rest,
2086
2090
  *posts,
2087
- *keywords&.flat_map { |(key, value)| [key, value || nil] },
2091
+ *keywords&.flatten(1),
2088
2092
  (keyword_rest if keyword_rest != :nil),
2089
2093
  (block if block != :&)
2090
2094
  ].compact
@@ -79,7 +79,7 @@ class PrettyPrint
79
79
  end
80
80
 
81
81
  def pretty_print(q)
82
- q.group(2, "align([", "])") do
82
+ q.group(2, "align#{indent}([", "])") do
83
83
  q.seplist(contents) { |content| q.pp(content) }
84
84
  end
85
85
  end
@@ -161,7 +161,7 @@ class PrettyPrint
161
161
  end
162
162
 
163
163
  def pretty_print(q)
164
- q.group(2, "group([", "])") do
164
+ q.group(2, break? ? "breakGroup([" : "group([", "])") do
165
165
  q.seplist(contents) { |content| q.pp(content) }
166
166
  end
167
167
  end
@@ -458,6 +458,10 @@ class PrettyPrint
458
458
  IfBreakBuilder.new
459
459
  end
460
460
 
461
+ # Also effectively unnecessary, but here for compatibility.
462
+ def if_flat
463
+ end
464
+
461
465
  # A noop that immediately yields.
462
466
  def indent
463
467
  yield
@@ -759,9 +763,7 @@ class PrettyPrint
759
763
 
760
764
  # This is a linear stack instead of a mutually recursive call defined on
761
765
  # the individual doc nodes for efficiency.
762
- while commands.any?
763
- indent, mode, doc = commands.pop
764
-
766
+ while (indent, mode, doc = commands.pop)
765
767
  case doc
766
768
  when Text
767
769
  doc.objects.each { |object| buffer << object }
@@ -789,10 +791,10 @@ class PrettyPrint
789
791
  end
790
792
  end
791
793
  when IfBreak
792
- if mode == MODE_BREAK
793
- commands << [indent, mode, doc.break_contents] if doc.break_contents
794
- elsif mode == MODE_FLAT
795
- commands << [indent, mode, doc.flat_contents] if doc.flat_contents
794
+ if mode == MODE_BREAK && doc.break_contents.any?
795
+ commands << [indent, mode, doc.break_contents]
796
+ elsif mode == MODE_FLAT && doc.flat_contents.any?
797
+ commands << [indent, mode, doc.flat_contents]
796
798
  end
797
799
  when LineSuffix
798
800
  line_suffixes << [indent, mode, doc.contents, doc.priority]
@@ -1011,6 +1013,16 @@ class PrettyPrint
1011
1013
  IfBreakBuilder.new(self, doc)
1012
1014
  end
1013
1015
 
1016
+ # This is similar to if_break in that it also inserts an IfBreak node into the
1017
+ # print tree, however it's starting from the flat contents, and cannot be used
1018
+ # to build the break contents.
1019
+ def if_flat
1020
+ doc = IfBreak.new
1021
+ target << doc
1022
+
1023
+ with_target(doc.flat_contents) { yield }
1024
+ end
1025
+
1014
1026
  # Very similar to the #nest method, this indents the nested content by one
1015
1027
  # level by inserting an Indent node into the print tree. The contents of the
1016
1028
  # node are determined by the block.
@@ -1116,10 +1128,10 @@ class PrettyPrint
1116
1128
  when Group
1117
1129
  commands << [indent, doc.break? ? MODE_BREAK : mode, doc.contents]
1118
1130
  when IfBreak
1119
- if mode == MODE_BREAK
1120
- commands << [indent, mode, doc.break_contents] if doc.break_contents
1121
- else
1122
- commands << [indent, mode, doc.flat_contents] if doc.flat_contents
1131
+ if mode == MODE_BREAK && doc.break_contents.any?
1132
+ commands << [indent, mode, doc.break_contents]
1133
+ elsif mode == MODE_FLAT && doc.flat_contents.any?
1134
+ commands << [indent, mode, doc.flat_contents]
1123
1135
  end
1124
1136
  when Breakable
1125
1137
  if mode == MODE_FLAT && !doc.force?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
- VERSION = "2.2.0"
4
+ VERSION = "2.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syntax_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-19 00:00:00.000000000 Z
11
+ date: 2022-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler