syntax_tree 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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