syntax_tree 2.1.1 → 2.3.1

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,25 @@ 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
+ q.if_flat { q.text(" ") }
2903
+ yield
2562
2904
  in Args[parts: [Command => command]]
2563
- align?(command)
2905
+ align(q, command, &block)
2564
2906
  else
2565
- true
2907
+ q.text(" ")
2908
+ q.nest(message.value.length + 1) { yield }
2566
2909
  end
2567
2910
  end
2568
2911
  end
@@ -2634,9 +2977,15 @@ module SyntaxTree
2634
2977
  q.format(message)
2635
2978
  end
2636
2979
 
2637
- if arguments
2980
+ case arguments
2981
+ in Args[parts: [IfOp]]
2982
+ q.if_flat { q.text(" ") }
2983
+ q.format(arguments)
2984
+ in Args
2638
2985
  q.text(" ")
2639
2986
  q.nest(argument_alignment(q, doc)) { q.format(arguments) }
2987
+ else
2988
+ # If there are no arguments, print nothing.
2640
2989
  end
2641
2990
  end
2642
2991
  end
@@ -3022,7 +3371,10 @@ module SyntaxTree
3022
3371
  q.group do
3023
3372
  q.text("def ")
3024
3373
  q.format(name)
3025
- q.format(params) if !params.is_a?(Params) || !params.empty?
3374
+
3375
+ if !params.is_a?(Params) || !params.empty? || params.comments.any?
3376
+ q.format(params)
3377
+ end
3026
3378
  end
3027
3379
 
3028
3380
  unless bodystmt.empty?
@@ -3242,7 +3594,10 @@ module SyntaxTree
3242
3594
  q.format(target)
3243
3595
  q.format(CallOperatorFormatter.new(operator), stackable: false)
3244
3596
  q.format(name)
3245
- q.format(params) if !params.is_a?(Params) || !params.empty?
3597
+
3598
+ if !params.is_a?(Params) || !params.empty? || params.comments.any?
3599
+ q.format(params)
3600
+ end
3246
3601
  end
3247
3602
 
3248
3603
  unless bodystmt.empty?
@@ -3777,7 +4132,7 @@ module SyntaxTree
3777
4132
  end
3778
4133
 
3779
4134
  alias deconstruct child_nodes
3780
-
4135
+
3781
4136
  def deconstruct_keys(keys)
3782
4137
  { value: value, location: location }
3783
4138
  end
@@ -3807,7 +4162,7 @@ module SyntaxTree
3807
4162
  end
3808
4163
 
3809
4164
  alias deconstruct child_nodes
3810
-
4165
+
3811
4166
  def deconstruct_keys(keys)
3812
4167
  { value: value, location: location }
3813
4168
  end
@@ -3839,7 +4194,7 @@ module SyntaxTree
3839
4194
  end
3840
4195
 
3841
4196
  alias deconstruct child_nodes
3842
-
4197
+
3843
4198
  def deconstruct_keys(keys)
3844
4199
  { value: value, location: location }
3845
4200
  end
@@ -3987,7 +4342,15 @@ module SyntaxTree
3987
4342
 
3988
4343
  def format(q)
3989
4344
  q.format(value)
3990
- q.format(arguments)
4345
+
4346
+ if arguments.is_a?(ArgParen) && arguments.arguments.nil? && !value.is_a?(Const)
4347
+ # If you're using an explicit set of parentheses on something that looks
4348
+ # like a constant, then we need to match that in order to maintain valid
4349
+ # Ruby. For example, you could do something like Foo(), on which we
4350
+ # would need to keep the parentheses to make it look like a method call.
4351
+ else
4352
+ q.format(arguments)
4353
+ end
3991
4354
  end
3992
4355
  end
3993
4356
 
@@ -4260,6 +4623,29 @@ module SyntaxTree
4260
4623
  # { key => value }
4261
4624
  #
4262
4625
  class HashLiteral < Node
4626
+ class EmptyWithCommentsFormatter
4627
+ # [LBrace] the opening brace
4628
+ attr_reader :lbrace
4629
+
4630
+ def initialize(lbrace)
4631
+ @lbrace = lbrace
4632
+ end
4633
+
4634
+ def format(q)
4635
+ q.group do
4636
+ q.text("{")
4637
+ q.indent do
4638
+ lbrace.comments.each do |comment|
4639
+ q.breakable(force: true)
4640
+ comment.format(q)
4641
+ end
4642
+ end
4643
+ q.breakable(force: true)
4644
+ q.text("}")
4645
+ end
4646
+ end
4647
+ end
4648
+
4263
4649
  # [LBrace] the left brace that opens this hash
4264
4650
  attr_reader :lbrace
4265
4651
 
@@ -4304,7 +4690,18 @@ module SyntaxTree
4304
4690
 
4305
4691
  private
4306
4692
 
4693
+ # If we have an empty hash that contains only comments, then we're going
4694
+ # to do some special printing to ensure they get indented correctly.
4695
+ def empty_with_comments?
4696
+ assocs.empty? && lbrace.comments.any? && lbrace.comments.none?(&:inline?)
4697
+ end
4698
+
4307
4699
  def format_contents(q)
4700
+ if empty_with_comments?
4701
+ EmptyWithCommentsFormatter.new(lbrace).format(q)
4702
+ return
4703
+ end
4704
+
4308
4705
  q.format(lbrace)
4309
4706
 
4310
4707
  if assocs.empty?
@@ -4334,6 +4731,9 @@ module SyntaxTree
4334
4731
  # [String] the ending of the heredoc
4335
4732
  attr_reader :ending
4336
4733
 
4734
+ # [Integer] how far to dedent the heredoc
4735
+ attr_reader :dedent
4736
+
4337
4737
  # [Array[ StringEmbExpr | StringDVar | TStringContent ]] the parts of the
4338
4738
  # heredoc string literal
4339
4739
  attr_reader :parts
@@ -4341,9 +4741,10 @@ module SyntaxTree
4341
4741
  # [Array[ Comment | EmbDoc ]] the comments attached to this node
4342
4742
  attr_reader :comments
4343
4743
 
4344
- def initialize(beginning:, ending: nil, parts: [], location:, comments: [])
4744
+ def initialize(beginning:, ending: nil, dedent: 0, parts: [], location:, comments: [])
4345
4745
  @beginning = beginning
4346
4746
  @ending = ending
4747
+ @dedent = dedent
4347
4748
  @parts = parts
4348
4749
  @location = location
4349
4750
  @comments = comments
@@ -4538,7 +4939,12 @@ module SyntaxTree
4538
4939
  parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest
4539
4940
 
4540
4941
  contents = -> do
4541
- q.seplist(parts) { |part| q.format(part, stackable: false) }
4942
+ q.group { q.seplist(parts) { |part| q.format(part, stackable: false) } }
4943
+
4944
+ # If there isn't a constant, and there's a blank keyword_rest, then we
4945
+ # have an plain ** that needs to have a `then` after it in order to
4946
+ # parse correctly on the next parse.
4947
+ q.text(" then") if !constant && keyword_rest && keyword_rest.value.nil?
4542
4948
  end
4543
4949
 
4544
4950
  if constant
@@ -4617,6 +5023,62 @@ module SyntaxTree
4617
5023
  end
4618
5024
  end
4619
5025
 
5026
+ # In order for an `if` or `unless` expression to be shortened to a ternary,
5027
+ # there has to be one and only one consequent clause which is an Else. Both
5028
+ # the body of the main node and the body of the Else node must have only one
5029
+ # statement, and that statement must not be on the denied list of potential
5030
+ # statements.
5031
+ module Ternaryable
5032
+ class << self
5033
+ def call(q, node)
5034
+ case q.parents.take(2)[1]
5035
+ in Paren[contents: Statements[body: [node]]]
5036
+ # If this is a conditional inside of a parentheses as the only
5037
+ # content, then we don't want to transform it into a ternary.
5038
+ # Presumably the user wanted it to be an explicit conditional because
5039
+ # there are parentheses around it. So we'll just leave it in place.
5040
+ false
5041
+ else
5042
+ # Otherwise, we're going to check the conditional for certain cases.
5043
+ case node
5044
+ in { predicate: Assign | Command | CommandCall | MAssign | OpAssign }
5045
+ false
5046
+ in { statements: { body: [truthy] }, consequent: Else[statements: { body: [falsy] }] }
5047
+ ternaryable?(truthy) && ternaryable?(falsy)
5048
+ else
5049
+ false
5050
+ end
5051
+ end
5052
+ end
5053
+
5054
+ private
5055
+
5056
+ # Certain expressions cannot be reduced to a ternary without adding
5057
+ # parentheses around them. In this case we say they cannot be ternaried and
5058
+ # default instead to breaking them into multiple lines.
5059
+ def ternaryable?(statement)
5060
+ # This is a list of nodes that should not be allowed to be a part of a
5061
+ # ternary clause.
5062
+ no_ternary = [
5063
+ Alias, Assign, Break, Command, CommandCall, Heredoc, If, IfMod, IfOp,
5064
+ Lambda, MAssign, Next, OpAssign, RescueMod, Return, Return0, Super,
5065
+ Undef, Unless, UnlessMod, Until, UntilMod, VarAlias, VoidStmt, While,
5066
+ WhileMod, Yield, Yield0, ZSuper
5067
+ ]
5068
+
5069
+ # Here we're going to check that the only statement inside the
5070
+ # statements node is no a part of our denied list of nodes that can be
5071
+ # ternaries.
5072
+ #
5073
+ # If the user is using one of the lower precedence "and" or "or"
5074
+ # operators, then we can't use a ternary expression as it would break
5075
+ # the flow control.
5076
+ !no_ternary.include?(statement.class) &&
5077
+ !(statement.is_a?(Binary) && %i[and or].include?(statement.operator))
5078
+ end
5079
+ end
5080
+ end
5081
+
4620
5082
  # Formats an If or Unless node.
4621
5083
  class ConditionalFormatter
4622
5084
  # [String] the keyword associated with this conditional
@@ -4631,6 +5093,13 @@ module SyntaxTree
4631
5093
  end
4632
5094
 
4633
5095
  def format(q)
5096
+ # If we can transform this node into a ternary, then we're going to print
5097
+ # a special version that uses the ternary operator if it fits on one line.
5098
+ if Ternaryable.call(q, node)
5099
+ format_ternary(q)
5100
+ return
5101
+ end
5102
+
4634
5103
  # If the predicate of the conditional contains an assignment (in which
4635
5104
  # case we can't know for certain that that assignment doesn't impact the
4636
5105
  # statements inside the conditional) then we can't use the modifier form
@@ -4640,7 +5109,7 @@ module SyntaxTree
4640
5109
  return
4641
5110
  end
4642
5111
 
4643
- if node.consequent || node.statements.empty?
5112
+ if node.consequent || node.statements.empty? || contains_conditional?
4644
5113
  q.group { format_break(q, force: true) }
4645
5114
  else
4646
5115
  q.group do
@@ -4676,6 +5145,59 @@ module SyntaxTree
4676
5145
  q.breakable(force: force)
4677
5146
  q.text("end")
4678
5147
  end
5148
+
5149
+ def format_ternary(q)
5150
+ q.group do
5151
+ q.if_break do
5152
+ q.text("#{keyword} ")
5153
+ q.nest(keyword.length + 1) { q.format(node.predicate) }
5154
+
5155
+ q.indent do
5156
+ q.breakable
5157
+ q.format(node.statements)
5158
+ end
5159
+
5160
+ q.breakable
5161
+ q.group do
5162
+ q.format(node.consequent.keyword)
5163
+ q.indent do
5164
+ # This is a very special case of breakable where we want to force
5165
+ # it into the output but we _don't_ want to explicitly break the
5166
+ # parent. If a break-parent shows up in the tree, then it's going
5167
+ # to force it all the way up to the tree, which is going to negate
5168
+ # the ternary. Maybe this should be an option in prettyprint? As
5169
+ # in force: :no_break_parent or something.
5170
+ q.target << PrettyPrint::Breakable.new(" ", 1, force: true)
5171
+ q.format(node.consequent.statements)
5172
+ end
5173
+ end
5174
+
5175
+ q.breakable
5176
+ q.text("end")
5177
+ end.if_flat do
5178
+ Parentheses.flat(q) do
5179
+ q.format(node.predicate)
5180
+ q.text(" ? ")
5181
+
5182
+ statements = [node.statements, node.consequent.statements]
5183
+ statements.reverse! if keyword == "unless"
5184
+
5185
+ q.format(statements[0])
5186
+ q.text(" : ")
5187
+ q.format(statements[1])
5188
+ end
5189
+ end
5190
+ end
5191
+ end
5192
+
5193
+ def contains_conditional?
5194
+ case node
5195
+ in { statements: { body: [If | IfMod | IfOp | Unless | UnlessMod] } }
5196
+ true
5197
+ else
5198
+ false
5199
+ end
5200
+ end
4679
5201
  end
4680
5202
 
4681
5203
  # If represents the first clause in an +if+ chain.
@@ -4788,7 +5310,7 @@ module SyntaxTree
4788
5310
  Yield0, ZSuper
4789
5311
  ]
4790
5312
 
4791
- if force_flat.include?(truthy.class) || force_flat.include?(falsy.class)
5313
+ if q.parent.is_a?(Paren) || force_flat.include?(truthy.class) || force_flat.include?(falsy.class)
4792
5314
  q.group { format_flat(q) }
4793
5315
  return
4794
5316
  end
@@ -5261,7 +5783,7 @@ module SyntaxTree
5261
5783
  end
5262
5784
 
5263
5785
  alias deconstruct child_nodes
5264
-
5786
+
5265
5787
  def deconstruct_keys(keys)
5266
5788
  { value: value, location: location }
5267
5789
  end
@@ -5535,6 +6057,17 @@ module SyntaxTree
5535
6057
  end
5536
6058
 
5537
6059
  def format(q)
6060
+ # If we're at the top of a call chain, then we're going to do some
6061
+ # specialized printing in case we can print it nicely. We _only_ do this
6062
+ # at the top of the chain to avoid weird recursion issues.
6063
+ if !CallChainFormatter.chained?(q.parent) && CallChainFormatter.chained?(call)
6064
+ q.group { q.if_break { CallChainFormatter.new(self).format(q) }.if_flat { format_contents(q) } }
6065
+ else
6066
+ format_contents(q)
6067
+ end
6068
+ end
6069
+
6070
+ def format_contents(q)
5538
6071
  q.format(call)
5539
6072
  q.format(block)
5540
6073
  end
@@ -5594,11 +6127,17 @@ module SyntaxTree
5594
6127
  # [MLHS | MLHSParen] the contents inside of the parentheses
5595
6128
  attr_reader :contents
5596
6129
 
6130
+ # [boolean] whether or not there is a trailing comma at the end of this
6131
+ # list, which impacts destructuring. It's an attr_accessor so that while
6132
+ # the syntax tree is being built it can be set by its parent node
6133
+ attr_accessor :comma
6134
+
5597
6135
  # [Array[ Comment | EmbDoc ]] the comments attached to this node
5598
6136
  attr_reader :comments
5599
6137
 
5600
- def initialize(contents:, location:, comments: [])
6138
+ def initialize(contents:, comma: false, location:, comments: [])
5601
6139
  @contents = contents
6140
+ @comma = comma
5602
6141
  @location = location
5603
6142
  @comments = comments
5604
6143
  end
@@ -5622,6 +6161,7 @@ module SyntaxTree
5622
6161
 
5623
6162
  if parent.is_a?(MAssign) || parent.is_a?(MLHSParen)
5624
6163
  q.format(contents)
6164
+ q.text(",") if comma
5625
6165
  else
5626
6166
  q.group(0, "(", ")") do
5627
6167
  q.indent do
@@ -5629,6 +6169,7 @@ module SyntaxTree
5629
6169
  q.format(contents)
5630
6170
  end
5631
6171
 
6172
+ q.text(",") if comma
5632
6173
  q.breakable("")
5633
6174
  end
5634
6175
  end
@@ -6148,7 +6689,9 @@ module SyntaxTree
6148
6689
  q.format(rest) if rest && rest.is_a?(ExcessedComma)
6149
6690
  end
6150
6691
 
6151
- if [Def, Defs, DefEndless].include?(q.parent.class)
6692
+ if ![Def, Defs, DefEndless].include?(q.parent.class) || parts.empty?
6693
+ q.nest(0, &contents)
6694
+ else
6152
6695
  q.group(0, "(", ")") do
6153
6696
  q.indent do
6154
6697
  q.breakable("")
@@ -6156,8 +6699,6 @@ module SyntaxTree
6156
6699
  end
6157
6700
  q.breakable("")
6158
6701
  end
6159
- else
6160
- q.nest(0, &contents)
6161
6702
  end
6162
6703
  end
6163
6704
  end
@@ -6378,7 +6919,7 @@ module SyntaxTree
6378
6919
  end
6379
6920
 
6380
6921
  alias deconstruct child_nodes
6381
-
6922
+
6382
6923
  def deconstruct_keys(keys)
6383
6924
  { value: value, location: location }
6384
6925
  end
@@ -6478,7 +7019,7 @@ module SyntaxTree
6478
7019
  end
6479
7020
 
6480
7021
  alias deconstruct child_nodes
6481
-
7022
+
6482
7023
  def deconstruct_keys(keys)
6483
7024
  { value: value, location: location }
6484
7025
  end
@@ -6539,7 +7080,7 @@ module SyntaxTree
6539
7080
  end
6540
7081
 
6541
7082
  alias deconstruct child_nodes
6542
-
7083
+
6543
7084
  def deconstruct_keys(keys)
6544
7085
  { value: value, location: location }
6545
7086
  end
@@ -6564,7 +7105,7 @@ module SyntaxTree
6564
7105
  end
6565
7106
 
6566
7107
  alias deconstruct child_nodes
6567
-
7108
+
6568
7109
  def deconstruct_keys(keys)
6569
7110
  { value: value, location: location }
6570
7111
  end
@@ -6635,7 +7176,7 @@ module SyntaxTree
6635
7176
  end
6636
7177
 
6637
7178
  alias deconstruct child_nodes
6638
-
7179
+
6639
7180
  def deconstruct_keys(keys)
6640
7181
  { beginning: beginning, parts: parts, location: location }
6641
7182
  end
@@ -6668,7 +7209,7 @@ module SyntaxTree
6668
7209
  end
6669
7210
 
6670
7211
  alias deconstruct child_nodes
6671
-
7212
+
6672
7213
  def deconstruct_keys(keys)
6673
7214
  { value: value, location: location }
6674
7215
  end
@@ -6702,7 +7243,7 @@ module SyntaxTree
6702
7243
  end
6703
7244
 
6704
7245
  alias deconstruct child_nodes
6705
-
7246
+
6706
7247
  def deconstruct_keys(keys)
6707
7248
  { value: value, location: location }
6708
7249
  end
@@ -7188,7 +7729,7 @@ module SyntaxTree
7188
7729
  @value = value
7189
7730
  @location = location
7190
7731
  end
7191
-
7732
+
7192
7733
  def accept(visitor)
7193
7734
  visitor.visit_rparen(self)
7194
7735
  end
@@ -7198,7 +7739,7 @@ module SyntaxTree
7198
7739
  end
7199
7740
 
7200
7741
  alias deconstruct child_nodes
7201
-
7742
+
7202
7743
  def deconstruct_keys(keys)
7203
7744
  { value: value, location: location }
7204
7745
  end
@@ -7410,19 +7951,20 @@ module SyntaxTree
7410
7951
  comment = parser_comments[comment_index]
7411
7952
  location = comment.location
7412
7953
 
7413
- if !comment.inline? && (start_char <= location.start_char) &&
7414
- (end_char >= location.end_char) && !comment.ignore?
7415
- parser_comments.delete_at(comment_index)
7416
-
7417
- while (node = body[body_index]) &&
7418
- (
7419
- node.is_a?(VoidStmt) ||
7420
- node.location.start_char < location.start_char
7421
- )
7954
+ if !comment.inline? && (start_char <= location.start_char) && (end_char >= location.end_char) && !comment.ignore?
7955
+ while (node = body[body_index]) && (node.is_a?(VoidStmt) || node.location.start_char < location.start_char)
7422
7956
  body_index += 1
7423
7957
  end
7424
7958
 
7425
- body.insert(body_index, comment)
7959
+ if body_index != 0 && body[body_index - 1].location.start_char < location.start_char && body[body_index - 1].location.end_char > location.start_char
7960
+ # The previous node entirely encapsules the comment, so we don't
7961
+ # want to attach it here since it will get attached normally. This
7962
+ # is mostly in the case of hash and array literals.
7963
+ comment_index += 1
7964
+ else
7965
+ parser_comments.delete_at(comment_index)
7966
+ body.insert(body_index, comment)
7967
+ end
7426
7968
  else
7427
7969
  comment_index += 1
7428
7970
  end
@@ -7453,7 +7995,7 @@ module SyntaxTree
7453
7995
  end
7454
7996
 
7455
7997
  alias deconstruct child_nodes
7456
-
7998
+
7457
7999
  def deconstruct_keys(keys)
7458
8000
  { parts: parts, location: location }
7459
8001
  end
@@ -7750,7 +8292,7 @@ module SyntaxTree
7750
8292
  end
7751
8293
 
7752
8294
  alias deconstruct child_nodes
7753
-
8295
+
7754
8296
  def deconstruct_keys(keys)
7755
8297
  { value: value, location: location }
7756
8298
  end
@@ -7780,7 +8322,7 @@ module SyntaxTree
7780
8322
  end
7781
8323
 
7782
8324
  alias deconstruct child_nodes
7783
-
8325
+
7784
8326
  def deconstruct_keys(keys)
7785
8327
  { value: value, location: location }
7786
8328
  end
@@ -7911,7 +8453,7 @@ module SyntaxTree
7911
8453
  end
7912
8454
 
7913
8455
  alias deconstruct child_nodes
7914
-
8456
+
7915
8457
  def deconstruct_keys(keys)
7916
8458
  { value: value, location: location }
7917
8459
  end
@@ -7940,7 +8482,7 @@ module SyntaxTree
7940
8482
  end
7941
8483
 
7942
8484
  alias deconstruct child_nodes
7943
-
8485
+
7944
8486
  def deconstruct_keys(keys)
7945
8487
  { value: value, location: location }
7946
8488
  end
@@ -7970,7 +8512,7 @@ module SyntaxTree
7970
8512
  end
7971
8513
 
7972
8514
  alias deconstruct child_nodes
7973
-
8515
+
7974
8516
  def deconstruct_keys(keys)
7975
8517
  { value: value, location: location }
7976
8518
  end
@@ -8081,7 +8623,7 @@ module SyntaxTree
8081
8623
  end
8082
8624
 
8083
8625
  alias deconstruct child_nodes
8084
-
8626
+
8085
8627
  def deconstruct_keys(keys)
8086
8628
  { value: value, location: location }
8087
8629
  end
@@ -8159,7 +8701,7 @@ module SyntaxTree
8159
8701
  end
8160
8702
 
8161
8703
  alias deconstruct child_nodes
8162
-
8704
+
8163
8705
  def deconstruct_keys(keys)
8164
8706
  { value: value, location: location }
8165
8707
  end
@@ -8206,9 +8748,26 @@ module SyntaxTree
8206
8748
  end
8207
8749
 
8208
8750
  def format(q)
8209
- q.text(parentheses ? "not(" : "not ")
8751
+ parent = q.parents.take(2)[1]
8752
+ ternary = (parent.is_a?(If) || parent.is_a?(Unless)) && Ternaryable.call(q, parent)
8753
+
8754
+ q.text("not")
8755
+
8756
+ if parentheses
8757
+ q.text("(")
8758
+ elsif ternary
8759
+ q.if_break { q.text(" ") }.if_flat { q.text("(") }
8760
+ else
8761
+ q.text(" ")
8762
+ end
8763
+
8210
8764
  q.format(statement) if statement
8211
- q.text(")") if parentheses
8765
+
8766
+ if parentheses
8767
+ q.text(")")
8768
+ elsif ternary
8769
+ q.if_flat { q.text(")") }
8770
+ end
8212
8771
  end
8213
8772
  end
8214
8773
 
@@ -9184,7 +9743,7 @@ module SyntaxTree
9184
9743
  end
9185
9744
 
9186
9745
  alias deconstruct child_nodes
9187
-
9746
+
9188
9747
  def deconstruct_keys(keys)
9189
9748
  { value: value, location: location }
9190
9749
  end
@@ -9213,7 +9772,7 @@ module SyntaxTree
9213
9772
  end
9214
9773
 
9215
9774
  alias deconstruct child_nodes
9216
-
9775
+
9217
9776
  def deconstruct_keys(keys)
9218
9777
  { parts: parts, location: location }
9219
9778
  end