syntax_tree 2.1.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.
@@ -35,6 +35,21 @@ module SyntaxTree
35
35
  )
36
36
  end
37
37
 
38
+ def deconstruct
39
+ [start_line, start_char, start_column, end_line, end_char, end_column]
40
+ end
41
+
42
+ def deconstruct_keys(keys)
43
+ {
44
+ start_line: start_line,
45
+ start_char: start_char,
46
+ start_column: start_column,
47
+ end_line: end_line,
48
+ end_char: end_char,
49
+ end_column: end_column
50
+ }
51
+ end
52
+
38
53
  def self.token(line:, char:, column:, size:)
39
54
  new(
40
55
  start_line: line,
@@ -801,6 +816,29 @@ module SyntaxTree
801
816
  end
802
817
  end
803
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
+
804
842
  # [LBracket] the bracket that opens this array
805
843
  attr_reader :lbracket
806
844
 
@@ -852,6 +890,11 @@ module SyntaxTree
852
890
  return
853
891
  end
854
892
 
893
+ if empty_with_comments?
894
+ EmptyWithCommentsFormatter.new(lbracket).format(q)
895
+ return
896
+ end
897
+
855
898
  q.group do
856
899
  q.format(lbracket)
857
900
 
@@ -904,6 +947,12 @@ module SyntaxTree
904
947
  q.maxwidth * 2
905
948
  )
906
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
907
956
  end
908
957
 
909
958
  # AryPtn represents matching against an array pattern using the Ruby 2.7+
@@ -1027,17 +1076,16 @@ module SyntaxTree
1027
1076
  # Determins if the following value should be indented or not.
1028
1077
  module AssignFormatting
1029
1078
  def self.skip_indent?(value)
1030
- (value.is_a?(Call) && skip_indent?(value.receiver)) ||
1031
- [
1032
- ArrayLiteral,
1033
- HashLiteral,
1034
- Heredoc,
1035
- Lambda,
1036
- QSymbols,
1037
- QWords,
1038
- Symbols,
1039
- Words
1040
- ].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
1041
1089
  end
1042
1090
  end
1043
1091
 
@@ -1537,12 +1585,12 @@ module SyntaxTree
1537
1585
  q.text(" ") unless power
1538
1586
 
1539
1587
  if operator == :<<
1540
- q.text(operator)
1588
+ q.text(operator.to_s)
1541
1589
  q.text(" ")
1542
1590
  q.format(right)
1543
1591
  else
1544
1592
  q.group do
1545
- q.text(operator)
1593
+ q.text(operator.to_s)
1546
1594
 
1547
1595
  q.indent do
1548
1596
  q.breakable(power ? "" : " ")
@@ -2027,12 +2075,12 @@ module SyntaxTree
2027
2075
  end
2028
2076
  end
2029
2077
 
2030
- # Formats either a Break or Next node.
2078
+ # Formats either a Break, Next, or Return node.
2031
2079
  class FlowControlFormatter
2032
2080
  # [String] the keyword to print
2033
2081
  attr_reader :keyword
2034
2082
 
2035
- # [Break | Next] the node being formatted
2083
+ # [Break | Next | Return] the node being formatted
2036
2084
  attr_reader :node
2037
2085
 
2038
2086
  def initialize(keyword, node)
@@ -2041,32 +2089,119 @@ module SyntaxTree
2041
2089
  end
2042
2090
 
2043
2091
  def format(q)
2044
- arguments = node.arguments
2045
-
2046
2092
  q.group do
2047
2093
  q.text(keyword)
2048
2094
 
2049
- if arguments.parts.any?
2050
- if arguments.parts.length == 1
2051
- part = arguments.parts.first
2052
-
2053
- if part.is_a?(Paren)
2054
- q.format(arguments)
2055
- elsif part.is_a?(ArrayLiteral)
2056
- q.text(" ")
2057
- q.format(arguments)
2058
- else
2059
- format_arguments(q, "(", ")")
2060
- end
2061
- else
2062
- format_arguments(q, " [", "]")
2063
- 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, " [", "]")
2064
2189
  end
2065
2190
  end
2066
2191
  end
2067
2192
 
2068
2193
  private
2069
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
+
2070
2205
  def format_arguments(q, opening, closing)
2071
2206
  q.if_break { q.text(opening) }
2072
2207
  q.indent do
@@ -2076,6 +2211,17 @@ module SyntaxTree
2076
2211
  q.breakable("")
2077
2212
  q.if_break { q.text(closing) }
2078
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
2079
2225
  end
2080
2226
 
2081
2227
  # Break represents using the +break+ keyword.
@@ -2141,6 +2287,181 @@ module SyntaxTree
2141
2287
  end
2142
2288
  end
2143
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
+
2144
2465
  # Call represents a method call.
2145
2466
  #
2146
2467
  # receiver.message
@@ -2204,6 +2525,29 @@ module SyntaxTree
2204
2525
  end
2205
2526
 
2206
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)
2207
2551
  call_operator = CallOperatorFormatter.new(operator)
2208
2552
 
2209
2553
  q.group do
@@ -2226,7 +2570,7 @@ module SyntaxTree
2226
2570
  q.format(message) if message != :call
2227
2571
  end
2228
2572
 
2229
- q.format(arguments) if arguments
2573
+ format_arguments(q)
2230
2574
  end
2231
2575
  end
2232
2576
  end
@@ -2492,7 +2836,7 @@ module SyntaxTree
2492
2836
  end
2493
2837
 
2494
2838
  alias deconstruct child_nodes
2495
-
2839
+
2496
2840
  def deconstruct_keys(keys)
2497
2841
  { value: value, location: location }
2498
2842
  end
@@ -2543,26 +2887,24 @@ module SyntaxTree
2543
2887
  def format(q)
2544
2888
  q.group do
2545
2889
  q.format(message)
2546
- q.text(" ")
2547
-
2548
- if align?(self)
2549
- q.nest(message.value.length + 1) { q.format(arguments) }
2550
- else
2551
- q.format(arguments)
2552
- end
2890
+ align(q, self) { q.format(arguments) }
2553
2891
  end
2554
2892
  end
2555
2893
 
2556
2894
  private
2557
2895
 
2558
- def align?(node)
2896
+ def align(q, node, &block)
2559
2897
  case node.arguments
2560
2898
  in Args[parts: [Def | Defs | DefEndless]]
2561
- false
2899
+ q.text(" ")
2900
+ yield
2901
+ in Args[parts: [IfOp]]
2902
+ yield
2562
2903
  in Args[parts: [Command => command]]
2563
- align?(command)
2904
+ align(q, command, &block)
2564
2905
  else
2565
- true
2906
+ q.text(" ")
2907
+ q.nest(message.value.length + 1) { yield }
2566
2908
  end
2567
2909
  end
2568
2910
  end
@@ -2634,9 +2976,15 @@ module SyntaxTree
2634
2976
  q.format(message)
2635
2977
  end
2636
2978
 
2637
- if arguments
2979
+ case arguments
2980
+ in Args[parts: [IfOp]]
2981
+ q.if_flat { q.text(" ") }
2982
+ q.format(arguments)
2983
+ in Args
2638
2984
  q.text(" ")
2639
2985
  q.nest(argument_alignment(q, doc)) { q.format(arguments) }
2986
+ else
2987
+ # If there are no arguments, print nothing.
2640
2988
  end
2641
2989
  end
2642
2990
  end
@@ -3022,7 +3370,10 @@ module SyntaxTree
3022
3370
  q.group do
3023
3371
  q.text("def ")
3024
3372
  q.format(name)
3025
- 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
3026
3377
  end
3027
3378
 
3028
3379
  unless bodystmt.empty?
@@ -3242,7 +3593,10 @@ module SyntaxTree
3242
3593
  q.format(target)
3243
3594
  q.format(CallOperatorFormatter.new(operator), stackable: false)
3244
3595
  q.format(name)
3245
- 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
3246
3600
  end
3247
3601
 
3248
3602
  unless bodystmt.empty?
@@ -3777,7 +4131,7 @@ module SyntaxTree
3777
4131
  end
3778
4132
 
3779
4133
  alias deconstruct child_nodes
3780
-
4134
+
3781
4135
  def deconstruct_keys(keys)
3782
4136
  { value: value, location: location }
3783
4137
  end
@@ -3807,7 +4161,7 @@ module SyntaxTree
3807
4161
  end
3808
4162
 
3809
4163
  alias deconstruct child_nodes
3810
-
4164
+
3811
4165
  def deconstruct_keys(keys)
3812
4166
  { value: value, location: location }
3813
4167
  end
@@ -3839,7 +4193,7 @@ module SyntaxTree
3839
4193
  end
3840
4194
 
3841
4195
  alias deconstruct child_nodes
3842
-
4196
+
3843
4197
  def deconstruct_keys(keys)
3844
4198
  { value: value, location: location }
3845
4199
  end
@@ -3987,7 +4341,15 @@ module SyntaxTree
3987
4341
 
3988
4342
  def format(q)
3989
4343
  q.format(value)
3990
- 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
3991
4353
  end
3992
4354
  end
3993
4355
 
@@ -4260,6 +4622,29 @@ module SyntaxTree
4260
4622
  # { key => value }
4261
4623
  #
4262
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
+
4263
4648
  # [LBrace] the left brace that opens this hash
4264
4649
  attr_reader :lbrace
4265
4650
 
@@ -4304,7 +4689,18 @@ module SyntaxTree
4304
4689
 
4305
4690
  private
4306
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
+
4307
4698
  def format_contents(q)
4699
+ if empty_with_comments?
4700
+ EmptyWithCommentsFormatter.new(lbrace).format(q)
4701
+ return
4702
+ end
4703
+
4308
4704
  q.format(lbrace)
4309
4705
 
4310
4706
  if assocs.empty?
@@ -4334,6 +4730,9 @@ module SyntaxTree
4334
4730
  # [String] the ending of the heredoc
4335
4731
  attr_reader :ending
4336
4732
 
4733
+ # [Integer] how far to dedent the heredoc
4734
+ attr_reader :dedent
4735
+
4337
4736
  # [Array[ StringEmbExpr | StringDVar | TStringContent ]] the parts of the
4338
4737
  # heredoc string literal
4339
4738
  attr_reader :parts
@@ -4341,9 +4740,10 @@ module SyntaxTree
4341
4740
  # [Array[ Comment | EmbDoc ]] the comments attached to this node
4342
4741
  attr_reader :comments
4343
4742
 
4344
- def initialize(beginning:, ending: nil, parts: [], location:, comments: [])
4743
+ def initialize(beginning:, ending: nil, dedent: 0, parts: [], location:, comments: [])
4345
4744
  @beginning = beginning
4346
4745
  @ending = ending
4746
+ @dedent = dedent
4347
4747
  @parts = parts
4348
4748
  @location = location
4349
4749
  @comments = comments
@@ -4536,8 +4936,14 @@ module SyntaxTree
4536
4936
  def format(q)
4537
4937
  parts = keywords.map { |(key, value)| KeywordFormatter.new(key, value) }
4538
4938
  parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest
4939
+
4539
4940
  contents = -> do
4540
- q.seplist(parts) { |part| q.format(part, stackable: false) }
4941
+ q.group { q.seplist(parts) { |part| q.format(part, stackable: false) } }
4942
+
4943
+ # If there isn't a constant, and there's a blank keyword_rest, then we
4944
+ # have an plain ** that needs to have a `then` after it in order to
4945
+ # parse correctly on the next parse.
4946
+ q.text(" then") if !constant && keyword_rest && keyword_rest.value.nil?
4541
4947
  end
4542
4948
 
4543
4949
  if constant
@@ -4546,8 +4952,9 @@ module SyntaxTree
4546
4952
  return
4547
4953
  end
4548
4954
 
4549
- parent = q.parent
4550
- if PATTERNS.include?(parent.class)
4955
+ if parts.empty?
4956
+ q.text("{}")
4957
+ elsif PATTERNS.include?(q.parent.class)
4551
4958
  q.text("{ ")
4552
4959
  contents.call
4553
4960
  q.text(" }")
@@ -4615,6 +5022,62 @@ module SyntaxTree
4615
5022
  end
4616
5023
  end
4617
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
+
4618
5081
  # Formats an If or Unless node.
4619
5082
  class ConditionalFormatter
4620
5083
  # [String] the keyword associated with this conditional
@@ -4629,6 +5092,13 @@ module SyntaxTree
4629
5092
  end
4630
5093
 
4631
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
+
4632
5102
  # If the predicate of the conditional contains an assignment (in which
4633
5103
  # case we can't know for certain that that assignment doesn't impact the
4634
5104
  # statements inside the conditional) then we can't use the modifier form
@@ -4638,7 +5108,7 @@ module SyntaxTree
4638
5108
  return
4639
5109
  end
4640
5110
 
4641
- if node.consequent || node.statements.empty?
5111
+ if node.consequent || node.statements.empty? || contains_conditional?
4642
5112
  q.group { format_break(q, force: true) }
4643
5113
  else
4644
5114
  q.group do
@@ -4674,6 +5144,59 @@ module SyntaxTree
4674
5144
  q.breakable(force: force)
4675
5145
  q.text("end")
4676
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
4677
5200
  end
4678
5201
 
4679
5202
  # If represents the first clause in an +if+ chain.
@@ -4786,7 +5309,7 @@ module SyntaxTree
4786
5309
  Yield0, ZSuper
4787
5310
  ]
4788
5311
 
4789
- 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)
4790
5313
  q.group { format_flat(q) }
4791
5314
  return
4792
5315
  end
@@ -5259,7 +5782,7 @@ module SyntaxTree
5259
5782
  end
5260
5783
 
5261
5784
  alias deconstruct child_nodes
5262
-
5785
+
5263
5786
  def deconstruct_keys(keys)
5264
5787
  { value: value, location: location }
5265
5788
  end
@@ -5533,6 +6056,17 @@ module SyntaxTree
5533
6056
  end
5534
6057
 
5535
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)
5536
6070
  q.format(call)
5537
6071
  q.format(block)
5538
6072
  end
@@ -5592,11 +6126,17 @@ module SyntaxTree
5592
6126
  # [MLHS | MLHSParen] the contents inside of the parentheses
5593
6127
  attr_reader :contents
5594
6128
 
6129
+ # [boolean] whether or not there is a trailing comma at the end of this
6130
+ # list, which impacts destructuring. It's an attr_accessor so that while
6131
+ # the syntax tree is being built it can be set by its parent node
6132
+ attr_accessor :comma
6133
+
5595
6134
  # [Array[ Comment | EmbDoc ]] the comments attached to this node
5596
6135
  attr_reader :comments
5597
6136
 
5598
- def initialize(contents:, location:, comments: [])
6137
+ def initialize(contents:, comma: false, location:, comments: [])
5599
6138
  @contents = contents
6139
+ @comma = comma
5600
6140
  @location = location
5601
6141
  @comments = comments
5602
6142
  end
@@ -5620,6 +6160,7 @@ module SyntaxTree
5620
6160
 
5621
6161
  if parent.is_a?(MAssign) || parent.is_a?(MLHSParen)
5622
6162
  q.format(contents)
6163
+ q.text(",") if comma
5623
6164
  else
5624
6165
  q.group(0, "(", ")") do
5625
6166
  q.indent do
@@ -5627,6 +6168,7 @@ module SyntaxTree
5627
6168
  q.format(contents)
5628
6169
  end
5629
6170
 
6171
+ q.text(",") if comma
5630
6172
  q.breakable("")
5631
6173
  end
5632
6174
  end
@@ -6146,7 +6688,9 @@ module SyntaxTree
6146
6688
  q.format(rest) if rest && rest.is_a?(ExcessedComma)
6147
6689
  end
6148
6690
 
6149
- 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
6150
6694
  q.group(0, "(", ")") do
6151
6695
  q.indent do
6152
6696
  q.breakable("")
@@ -6154,8 +6698,6 @@ module SyntaxTree
6154
6698
  end
6155
6699
  q.breakable("")
6156
6700
  end
6157
- else
6158
- q.nest(0, &contents)
6159
6701
  end
6160
6702
  end
6161
6703
  end
@@ -6376,7 +6918,7 @@ module SyntaxTree
6376
6918
  end
6377
6919
 
6378
6920
  alias deconstruct child_nodes
6379
-
6921
+
6380
6922
  def deconstruct_keys(keys)
6381
6923
  { value: value, location: location }
6382
6924
  end
@@ -6476,7 +7018,7 @@ module SyntaxTree
6476
7018
  end
6477
7019
 
6478
7020
  alias deconstruct child_nodes
6479
-
7021
+
6480
7022
  def deconstruct_keys(keys)
6481
7023
  { value: value, location: location }
6482
7024
  end
@@ -6537,7 +7079,7 @@ module SyntaxTree
6537
7079
  end
6538
7080
 
6539
7081
  alias deconstruct child_nodes
6540
-
7082
+
6541
7083
  def deconstruct_keys(keys)
6542
7084
  { value: value, location: location }
6543
7085
  end
@@ -6562,7 +7104,7 @@ module SyntaxTree
6562
7104
  end
6563
7105
 
6564
7106
  alias deconstruct child_nodes
6565
-
7107
+
6566
7108
  def deconstruct_keys(keys)
6567
7109
  { value: value, location: location }
6568
7110
  end
@@ -6633,7 +7175,7 @@ module SyntaxTree
6633
7175
  end
6634
7176
 
6635
7177
  alias deconstruct child_nodes
6636
-
7178
+
6637
7179
  def deconstruct_keys(keys)
6638
7180
  { beginning: beginning, parts: parts, location: location }
6639
7181
  end
@@ -6666,7 +7208,7 @@ module SyntaxTree
6666
7208
  end
6667
7209
 
6668
7210
  alias deconstruct child_nodes
6669
-
7211
+
6670
7212
  def deconstruct_keys(keys)
6671
7213
  { value: value, location: location }
6672
7214
  end
@@ -6700,7 +7242,7 @@ module SyntaxTree
6700
7242
  end
6701
7243
 
6702
7244
  alias deconstruct child_nodes
6703
-
7245
+
6704
7246
  def deconstruct_keys(keys)
6705
7247
  { value: value, location: location }
6706
7248
  end
@@ -7186,7 +7728,7 @@ module SyntaxTree
7186
7728
  @value = value
7187
7729
  @location = location
7188
7730
  end
7189
-
7731
+
7190
7732
  def accept(visitor)
7191
7733
  visitor.visit_rparen(self)
7192
7734
  end
@@ -7196,7 +7738,7 @@ module SyntaxTree
7196
7738
  end
7197
7739
 
7198
7740
  alias deconstruct child_nodes
7199
-
7741
+
7200
7742
  def deconstruct_keys(keys)
7201
7743
  { value: value, location: location }
7202
7744
  end
@@ -7408,19 +7950,20 @@ module SyntaxTree
7408
7950
  comment = parser_comments[comment_index]
7409
7951
  location = comment.location
7410
7952
 
7411
- if !comment.inline? && (start_char <= location.start_char) &&
7412
- (end_char >= location.end_char) && !comment.ignore?
7413
- parser_comments.delete_at(comment_index)
7414
-
7415
- while (node = body[body_index]) &&
7416
- (
7417
- node.is_a?(VoidStmt) ||
7418
- node.location.start_char < location.start_char
7419
- )
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)
7420
7955
  body_index += 1
7421
7956
  end
7422
7957
 
7423
- 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
7424
7967
  else
7425
7968
  comment_index += 1
7426
7969
  end
@@ -7451,7 +7994,7 @@ module SyntaxTree
7451
7994
  end
7452
7995
 
7453
7996
  alias deconstruct child_nodes
7454
-
7997
+
7455
7998
  def deconstruct_keys(keys)
7456
7999
  { parts: parts, location: location }
7457
8000
  end
@@ -7748,7 +8291,7 @@ module SyntaxTree
7748
8291
  end
7749
8292
 
7750
8293
  alias deconstruct child_nodes
7751
-
8294
+
7752
8295
  def deconstruct_keys(keys)
7753
8296
  { value: value, location: location }
7754
8297
  end
@@ -7778,7 +8321,7 @@ module SyntaxTree
7778
8321
  end
7779
8322
 
7780
8323
  alias deconstruct child_nodes
7781
-
8324
+
7782
8325
  def deconstruct_keys(keys)
7783
8326
  { value: value, location: location }
7784
8327
  end
@@ -7909,7 +8452,7 @@ module SyntaxTree
7909
8452
  end
7910
8453
 
7911
8454
  alias deconstruct child_nodes
7912
-
8455
+
7913
8456
  def deconstruct_keys(keys)
7914
8457
  { value: value, location: location }
7915
8458
  end
@@ -7938,7 +8481,7 @@ module SyntaxTree
7938
8481
  end
7939
8482
 
7940
8483
  alias deconstruct child_nodes
7941
-
8484
+
7942
8485
  def deconstruct_keys(keys)
7943
8486
  { value: value, location: location }
7944
8487
  end
@@ -7968,7 +8511,7 @@ module SyntaxTree
7968
8511
  end
7969
8512
 
7970
8513
  alias deconstruct child_nodes
7971
-
8514
+
7972
8515
  def deconstruct_keys(keys)
7973
8516
  { value: value, location: location }
7974
8517
  end
@@ -8079,7 +8622,7 @@ module SyntaxTree
8079
8622
  end
8080
8623
 
8081
8624
  alias deconstruct child_nodes
8082
-
8625
+
8083
8626
  def deconstruct_keys(keys)
8084
8627
  { value: value, location: location }
8085
8628
  end
@@ -8157,7 +8700,7 @@ module SyntaxTree
8157
8700
  end
8158
8701
 
8159
8702
  alias deconstruct child_nodes
8160
-
8703
+
8161
8704
  def deconstruct_keys(keys)
8162
8705
  { value: value, location: location }
8163
8706
  end
@@ -8168,7 +8711,7 @@ module SyntaxTree
8168
8711
  # not value
8169
8712
  #
8170
8713
  class Not < Node
8171
- # [untyped] the statement on which to operate
8714
+ # [nil | untyped] the statement on which to operate
8172
8715
  attr_reader :statement
8173
8716
 
8174
8717
  # [boolean] whether or not parentheses were used
@@ -8204,9 +8747,26 @@ module SyntaxTree
8204
8747
  end
8205
8748
 
8206
8749
  def format(q)
8207
- q.text(parentheses ? "not(" : "not ")
8208
- q.format(statement)
8209
- q.text(")") if parentheses
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
+
8763
+ q.format(statement) if statement
8764
+
8765
+ if parentheses
8766
+ q.text(")")
8767
+ elsif ternary
8768
+ q.if_flat { q.text(")") }
8769
+ end
8210
8770
  end
8211
8771
  end
8212
8772
 
@@ -8675,7 +9235,11 @@ module SyntaxTree
8675
9235
  end
8676
9236
 
8677
9237
  def format(q)
8678
- q.format(value) if value
9238
+ if value == :nil
9239
+ q.text("nil")
9240
+ elsif value
9241
+ q.format(value)
9242
+ end
8679
9243
  end
8680
9244
  end
8681
9245
 
@@ -9178,7 +9742,7 @@ module SyntaxTree
9178
9742
  end
9179
9743
 
9180
9744
  alias deconstruct child_nodes
9181
-
9745
+
9182
9746
  def deconstruct_keys(keys)
9183
9747
  { value: value, location: location }
9184
9748
  end
@@ -9207,7 +9771,7 @@ module SyntaxTree
9207
9771
  end
9208
9772
 
9209
9773
  alias deconstruct child_nodes
9210
-
9774
+
9211
9775
  def deconstruct_keys(keys)
9212
9776
  { parts: parts, location: location }
9213
9777
  end