syntax_tree 1.1.1 → 1.2.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: 123ef3ac2fa068f2c1f5d653c1eaf0d57c68ab0c17fc632401f0aead46ed8577
4
- data.tar.gz: 166fedda529629024fae922db271b4f96294851ea16ea065ffd4f89c2d4671e9
3
+ metadata.gz: 4797ffd100da9b050f154cd0ac4a235b342c395e49747ee51574eb0e0f9fcf1a
4
+ data.tar.gz: 52babf707ae1bf42abaeb51b350644fc50f61fa1b74482abb776e863d2ec48dd
5
5
  SHA512:
6
- metadata.gz: 3b3bf4c737c16159b2bac2329e9999b841c88a43e21a783da7df7e691b27e27f5fb85dbfceb3ec97bcd2309993fec7f5b994abc2e71ec4c3c6f3dfc76a2da05e
7
- data.tar.gz: 62d250a1322985c29f53eb6259e084e3ebb2dd54e4e507250ba2338fdf149d15fe6e4683b8026602d0a8e219935fc91c8d2459a15a31f0a43054970886d7b262
6
+ metadata.gz: 62e28b7e6b25e81bdfb70817c02152bd640395149c1ac75fa553154ebb636c9c913dbf2ebda7a65f9687dd1bb69d51e0e3d8f5ffba172d0b1eadb5849e64369a
7
+ data.tar.gz: 6233b01960317929ce137305a08f45d3d756d741b5f6b6d037f2499377a53922f222543f433912cc853ee31726e2306527fbe29c75ad757e6125207b7476956c
@@ -13,7 +13,7 @@ jobs:
13
13
  - uses: ruby/setup-ruby@v1
14
14
  with:
15
15
  bundler-cache: true
16
- ruby-version: 3.0
16
+ ruby-version: '3.1'
17
17
  - name: Test
18
18
  run: bundle exec rake test
19
19
  automerge:
data/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.2.0] - 2022-01-09
10
+
11
+ ### Added
12
+
13
+ - Support for Ruby 3.1 syntax, including: blocks without names, hash keys without values, endless methods without parentheses, and new argument forwarding.
14
+ - Support for pinned expressions and variables within pattern matching.
15
+ - Support endless ranges as the final argument to a `when` clause.
16
+
9
17
  ## [1.1.1] - 2021-12-09
10
18
 
11
19
  ### Added
@@ -97,7 +105,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
97
105
 
98
106
  - 🎉 Initial release! 🎉
99
107
 
100
- [unreleased]: https://github.com/kddnewton/syntax_tree/compare/v1.1.1...HEAD
108
+ [unreleased]: https://github.com/kddnewton/syntax_tree/compare/v1.2.0...HEAD
109
+ [1.2.0]: https://github.com/kddnewton/syntax_tree/compare/v1.1.1...v1.2.0
101
110
  [1.1.1]: https://github.com/kddnewton/syntax_tree/compare/v1.1.0...v1.1.1
102
111
  [1.1.0]: https://github.com/kddnewton/syntax_tree/compare/v1.0.0...v1.1.0
103
112
  [1.0.0]: https://github.com/kddnewton/syntax_tree/compare/v0.1.0...v1.0.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree (1.1.1)
4
+ syntax_tree (1.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -9,8 +9,8 @@ GEM
9
9
  ast (2.4.2)
10
10
  benchmark-ips (2.9.2)
11
11
  docile (1.4.0)
12
- minitest (5.14.4)
13
- parser (3.0.3.2)
12
+ minitest (5.15.0)
13
+ parser (3.1.0.0)
14
14
  ast (~> 2.4.1)
15
15
  rake (13.0.6)
16
16
  ruby_parser (3.18.1)
@@ -26,6 +26,7 @@ GEM
26
26
 
27
27
  PLATFORMS
28
28
  x86_64-darwin-19
29
+ x86_64-darwin-21
29
30
  x86_64-linux
30
31
 
31
32
  DEPENDENCIES
@@ -40,4 +41,4 @@ DEPENDENCIES
40
41
  syntax_tree!
41
42
 
42
43
  BUNDLED WITH
43
- 2.2.15
44
+ 2.2.31
@@ -3,5 +3,5 @@
3
3
  require "ripper"
4
4
 
5
5
  class SyntaxTree < Ripper
6
- VERSION = "1.1.1"
6
+ VERSION = "1.2.0"
7
7
  end
data/lib/syntax_tree.rb CHANGED
@@ -1173,7 +1173,7 @@ class SyntaxTree < Ripper
1173
1173
  # method(&expression)
1174
1174
  #
1175
1175
  class ArgBlock
1176
- # [untyped] the expression being turned into a block
1176
+ # [nil | untyped] the expression being turned into a block
1177
1177
  attr_reader :value
1178
1178
 
1179
1179
  # [Location] the location of this node
@@ -1194,15 +1194,17 @@ class SyntaxTree < Ripper
1194
1194
 
1195
1195
  def format(q)
1196
1196
  q.text("&")
1197
- q.format(value)
1197
+ q.format(value) if value
1198
1198
  end
1199
1199
 
1200
1200
  def pretty_print(q)
1201
1201
  q.group(2, "(", ")") do
1202
1202
  q.text("arg_block")
1203
1203
 
1204
- q.breakable
1205
- q.pp(value)
1204
+ if value
1205
+ q.breakable
1206
+ q.pp(value)
1207
+ end
1206
1208
 
1207
1209
  q.pp(Comment::List.new(comments))
1208
1210
  end
@@ -1221,17 +1223,34 @@ class SyntaxTree < Ripper
1221
1223
  # (false | untyped) block
1222
1224
  # ) -> Args
1223
1225
  def on_args_add_block(arguments, block)
1224
- return arguments unless block
1226
+ operator = find_token(Op, "&", consume: false)
1225
1227
 
1226
- arg_block =
1227
- ArgBlock.new(
1228
- value: block,
1229
- location: find_token(Op, "&").location.to(block.location)
1230
- )
1228
+ # If we can't find the & operator, then there's no block to add to the list,
1229
+ # so we're just going to return the arguments as-is.
1230
+ return arguments unless operator
1231
+
1232
+ # Now we know we have an & operator, so we're going to delete it from the
1233
+ # list of tokens to make sure it doesn't get confused with anything else.
1234
+ tokens.delete(operator)
1235
+
1236
+ # Construct the location that represents the block argument.
1237
+ location = operator.location
1238
+ location = operator.location.to(block.location) if block
1239
+
1240
+ # If there are any arguments and the operator we found from the list is not
1241
+ # after them, then we're going to return the arguments as-is because we're
1242
+ # looking at an & that occurs before the arguments are done.
1243
+ if arguments.parts.any? && location.start_char < arguments.location.end_char
1244
+ return arguments
1245
+ end
1246
+
1247
+ # Otherwise, we're looking at an actual block argument (with or without a
1248
+ # block, which could be missing because it could be a bare & since 3.1.0).
1249
+ arg_block = ArgBlock.new(value: block, location: location)
1231
1250
 
1232
1251
  Args.new(
1233
1252
  parts: arguments.parts << arg_block,
1234
- location: arguments.location.to(arg_block.location)
1253
+ location: arguments.location.to(location)
1235
1254
  )
1236
1255
  end
1237
1256
 
@@ -1676,10 +1695,13 @@ class SyntaxTree < Ripper
1676
1695
  parts += posts
1677
1696
 
1678
1697
  if constant
1679
- q.format(constant)
1680
- q.text("[")
1681
- q.seplist(parts) { |part| q.format(part) }
1682
- q.text("]")
1698
+ q.group do
1699
+ q.format(constant)
1700
+ q.text("[")
1701
+ q.seplist(parts) { |part| q.format(part) }
1702
+ q.text("]")
1703
+ end
1704
+
1683
1705
  return
1684
1706
  end
1685
1707
 
@@ -1689,7 +1711,7 @@ class SyntaxTree < Ripper
1689
1711
  q.seplist(parts) { |part| q.format(part) }
1690
1712
  q.text("]")
1691
1713
  else
1692
- q.seplist(parts) { |part| q.format(part) }
1714
+ q.group { q.seplist(parts) { |part| q.format(part) } }
1693
1715
  end
1694
1716
  end
1695
1717
 
@@ -1896,7 +1918,7 @@ class SyntaxTree < Ripper
1896
1918
  end
1897
1919
 
1898
1920
  def format(q)
1899
- if value.is_a?(HashLiteral)
1921
+ if value&.is_a?(HashLiteral)
1900
1922
  format_contents(q)
1901
1923
  else
1902
1924
  q.group { format_contents(q) }
@@ -1910,8 +1932,10 @@ class SyntaxTree < Ripper
1910
1932
  q.breakable
1911
1933
  q.pp(key)
1912
1934
 
1913
- q.breakable
1914
- q.pp(value)
1935
+ if value
1936
+ q.breakable
1937
+ q.pp(value)
1938
+ end
1915
1939
 
1916
1940
  q.pp(Comment::List.new(comments))
1917
1941
  end
@@ -1931,6 +1955,7 @@ class SyntaxTree < Ripper
1931
1955
 
1932
1956
  def format_contents(q)
1933
1957
  q.parent.format_key(q, key)
1958
+ return unless value
1934
1959
 
1935
1960
  if key.comments.empty? && AssignFormatting.skip_indent?(value)
1936
1961
  q.text(" ")
@@ -1947,7 +1972,10 @@ class SyntaxTree < Ripper
1947
1972
  # :call-seq:
1948
1973
  # on_assoc_new: (untyped key, untyped value) -> Assoc
1949
1974
  def on_assoc_new(key, value)
1950
- Assoc.new(key: key, value: value, location: key.location.to(value.location))
1975
+ location = key.location
1976
+ location = location.to(value.location) if value
1977
+
1978
+ Assoc.new(key: key, value: value, location: location)
1951
1979
  end
1952
1980
 
1953
1981
  # AssocSplat represents double-splatting a value into a hash (either a hash
@@ -2315,24 +2343,95 @@ class SyntaxTree < Ripper
2315
2343
  end
2316
2344
  end
2317
2345
 
2346
+ # PinnedBegin represents a pinning a nested statement within pattern matching.
2347
+ #
2348
+ # case value
2349
+ # in ^(statement)
2350
+ # end
2351
+ #
2352
+ class PinnedBegin
2353
+ # [untyped] the expression being pinned
2354
+ attr_reader :statement
2355
+
2356
+ # [Location] the location of this node
2357
+ attr_reader :location
2358
+
2359
+ # [Array[ Comment | EmbDoc ]] the comments attached to this node
2360
+ attr_reader :comments
2361
+
2362
+ def initialize(statement:, location:, comments: [])
2363
+ @statement = statement
2364
+ @location = location
2365
+ @comments = comments
2366
+ end
2367
+
2368
+ def child_nodes
2369
+ [statement]
2370
+ end
2371
+
2372
+ def format(q)
2373
+ q.group do
2374
+ q.text("^(")
2375
+ q.nest(1) do
2376
+ q.indent do
2377
+ q.breakable("")
2378
+ q.format(statement)
2379
+ end
2380
+ q.breakable("")
2381
+ q.text(")")
2382
+ end
2383
+ end
2384
+ end
2385
+
2386
+ def pretty_print(q)
2387
+ q.group(2, "(", ")") do
2388
+ q.text("pinned_begin")
2389
+
2390
+ q.breakable
2391
+ q.pp(statement)
2392
+
2393
+ q.pp(Comment::List.new(comments))
2394
+ end
2395
+ end
2396
+
2397
+ def to_json(*opts)
2398
+ {
2399
+ type: :pinned_begin,
2400
+ stmt: statement,
2401
+ loc: location,
2402
+ cmts: comments
2403
+ }.to_json(*opts)
2404
+ end
2405
+ end
2406
+
2318
2407
  # :call-seq:
2319
- # on_begin: (BodyStmt bodystmt) -> Begin
2408
+ # on_begin: (untyped bodystmt) -> Begin | PinnedBegin
2320
2409
  def on_begin(bodystmt)
2321
- keyword = find_token(Kw, "begin")
2322
- end_char =
2323
- if bodystmt.rescue_clause || bodystmt.ensure_clause ||
2324
- bodystmt.else_clause
2325
- bodystmt.location.end_char
2326
- else
2327
- find_token(Kw, "end").location.end_char
2328
- end
2410
+ pin = find_token(Op, "^", consume: false)
2329
2411
 
2330
- bodystmt.bind(keyword.location.end_char, end_char)
2412
+ if pin && pin.location.start_char < bodystmt.location.start_char
2413
+ tokens.delete(pin)
2414
+ find_token(LParen)
2331
2415
 
2332
- Begin.new(
2333
- bodystmt: bodystmt,
2334
- location: keyword.location.to(bodystmt.location)
2335
- )
2416
+ rparen = find_token(RParen)
2417
+ location = pin.location.to(rparen.location)
2418
+
2419
+ PinnedBegin.new(statement: bodystmt, location: location)
2420
+ else
2421
+ keyword = find_token(Kw, "begin")
2422
+ end_char =
2423
+ if bodystmt.rescue_clause || bodystmt.ensure_clause ||
2424
+ bodystmt.else_clause
2425
+ bodystmt.location.end_char
2426
+ else
2427
+ find_token(Kw, "end").location.end_char
2428
+ end
2429
+
2430
+ bodystmt.bind(keyword.location.end_char, end_char)
2431
+ location = keyword.location.to(bodystmt.location)
2432
+
2433
+ Begin.new(bodystmt: bodystmt, location: location)
2434
+ end
2336
2435
  end
2337
2436
 
2338
2437
  # Binary represents any expression that involves two sub-expressions with an
@@ -2423,12 +2522,29 @@ class SyntaxTree < Ripper
2423
2522
  # :call-seq:
2424
2523
  # on_binary: (untyped left, (Op | Symbol) operator, untyped right) -> Binary
2425
2524
  def on_binary(left, operator, right)
2426
- # On most Ruby implementations, operator is a Symbol that represents that
2427
- # operation being performed. For instance in the example `1 < 2`, the
2428
- # `operator` object would be `:<`. However, on JRuby, it's an `@op` node,
2429
- # so here we're going to explicitly convert it into the same normalized
2430
- # form.
2431
- operator = tokens.delete(operator).value unless operator.is_a?(Symbol)
2525
+ if operator.is_a?(Symbol)
2526
+ # Here, we're going to search backward for the token that's between the
2527
+ # two operands that matches the operator so we can delete it from the
2528
+ # list.
2529
+ index =
2530
+ tokens.rindex do |token|
2531
+ location = token.location
2532
+
2533
+ token.is_a?(Op) &&
2534
+ token.value == operator.to_s &&
2535
+ location.start_char > left.location.end_char &&
2536
+ location.end_char < right.location.start_char
2537
+ end
2538
+
2539
+ tokens.delete_at(index) if index
2540
+ else
2541
+ # On most Ruby implementations, operator is a Symbol that represents that
2542
+ # operation being performed. For instance in the example `1 < 2`, the
2543
+ # `operator` object would be `:<`. However, on JRuby, it's an `@op` node,
2544
+ # so here we're going to explicitly convert it into the same normalized
2545
+ # form.
2546
+ operator = tokens.delete(operator).value
2547
+ end
2432
2548
 
2433
2549
  Binary.new(
2434
2550
  left: left,
@@ -2578,7 +2694,7 @@ class SyntaxTree < Ripper
2578
2694
  # def method(&block); end
2579
2695
  #
2580
2696
  class BlockArg
2581
- # [Ident] the name of the block argument
2697
+ # [nil | Ident] the name of the block argument
2582
2698
  attr_reader :name
2583
2699
 
2584
2700
  # [Location] the location of this node
@@ -2599,15 +2715,17 @@ class SyntaxTree < Ripper
2599
2715
 
2600
2716
  def format(q)
2601
2717
  q.text("&")
2602
- q.format(name)
2718
+ q.format(name) if name
2603
2719
  end
2604
2720
 
2605
2721
  def pretty_print(q)
2606
2722
  q.group(2, "(", ")") do
2607
2723
  q.text("blockarg")
2608
2724
 
2609
- q.breakable
2610
- q.pp(name)
2725
+ if name
2726
+ q.breakable
2727
+ q.pp(name)
2728
+ end
2611
2729
 
2612
2730
  q.pp(Comment::List.new(comments))
2613
2731
  end
@@ -2625,7 +2743,10 @@ class SyntaxTree < Ripper
2625
2743
  def on_blockarg(name)
2626
2744
  operator = find_token(Op, "&")
2627
2745
 
2628
- BlockArg.new(name: name, location: operator.location.to(name.location))
2746
+ location = operator.location
2747
+ location = location.to(name.location) if name
2748
+
2749
+ BlockArg.new(name: name, location: location)
2629
2750
  end
2630
2751
 
2631
2752
  # bodystmt can't actually determine its bounds appropriately because it
@@ -3968,6 +4089,10 @@ class SyntaxTree < Ripper
3968
4089
  []
3969
4090
  end
3970
4091
 
4092
+ def child_nodes
4093
+ []
4094
+ end
4095
+
3971
4096
  def format(q)
3972
4097
  q.text(value)
3973
4098
  end
@@ -4419,7 +4544,7 @@ class SyntaxTree < Ripper
4419
4544
  # [Backtick | Const | Ident | Kw | Op] the name of the method
4420
4545
  attr_reader :name
4421
4546
 
4422
- # [nil | Paren] the parameter declaration for the method
4547
+ # [nil | Params | Paren] the parameter declaration for the method
4423
4548
  attr_reader :paren
4424
4549
 
4425
4550
  # [untyped] the expression to be executed by the method
@@ -4463,7 +4588,12 @@ class SyntaxTree < Ripper
4463
4588
  end
4464
4589
 
4465
4590
  q.format(name)
4466
- q.format(paren) if paren && !paren.contents.empty?
4591
+
4592
+ if paren
4593
+ params = paren
4594
+ params = params.contents if params.is_a?(Paren)
4595
+ q.format(paren) unless params.empty?
4596
+ end
4467
4597
 
4468
4598
  q.text(" =")
4469
4599
  q.group do
@@ -4529,21 +4659,6 @@ class SyntaxTree < Ripper
4529
4659
  # and normal method definitions.
4530
4660
  beginning = find_token(Kw, "def")
4531
4661
 
4532
- # If we don't have a bodystmt node, then we have a single-line method
4533
- unless bodystmt.is_a?(BodyStmt)
4534
- node =
4535
- DefEndless.new(
4536
- target: nil,
4537
- operator: nil,
4538
- name: name,
4539
- paren: params,
4540
- statement: bodystmt,
4541
- location: beginning.location.to(bodystmt.location)
4542
- )
4543
-
4544
- return node
4545
- end
4546
-
4547
4662
  # If there aren't any params then we need to correct the params node
4548
4663
  # location information
4549
4664
  if params.is_a?(Params) && params.empty?
@@ -4559,18 +4674,35 @@ class SyntaxTree < Ripper
4559
4674
  params = Params.new(location: location)
4560
4675
  end
4561
4676
 
4562
- ending = find_token(Kw, "end")
4563
- bodystmt.bind(
4564
- find_next_statement_start(params.location.end_char),
4565
- ending.location.start_char
4566
- )
4677
+ ending = find_token(Kw, "end", consume: false)
4567
4678
 
4568
- Def.new(
4569
- name: name,
4570
- params: params,
4571
- bodystmt: bodystmt,
4572
- location: beginning.location.to(ending.location)
4573
- )
4679
+ if ending
4680
+ tokens.delete(ending)
4681
+ bodystmt.bind(
4682
+ find_next_statement_start(params.location.end_char),
4683
+ ending.location.start_char
4684
+ )
4685
+
4686
+ Def.new(
4687
+ name: name,
4688
+ params: params,
4689
+ bodystmt: bodystmt,
4690
+ location: beginning.location.to(ending.location)
4691
+ )
4692
+ else
4693
+ # In Ruby >= 3.1.0, this is a BodyStmt that wraps a single statement in
4694
+ # the statements list. Before, it was just the individual statement.
4695
+ statement = bodystmt.is_a?(BodyStmt) ? bodystmt.statements : bodystmt
4696
+
4697
+ DefEndless.new(
4698
+ target: nil,
4699
+ operator: nil,
4700
+ name: name,
4701
+ paren: params,
4702
+ statement: statement,
4703
+ location: beginning.location.to(bodystmt.location)
4704
+ )
4705
+ end
4574
4706
  end
4575
4707
 
4576
4708
  # Defined represents the use of the +defined?+ operator. It can be used with
@@ -4778,37 +4910,37 @@ class SyntaxTree < Ripper
4778
4910
  end
4779
4911
 
4780
4912
  beginning = find_token(Kw, "def")
4913
+ ending = find_token(Kw, "end", consume: false)
4781
4914
 
4782
- # If we don't have a bodystmt node, then we have a single-line method
4783
- unless bodystmt.is_a?(BodyStmt)
4784
- node =
4785
- DefEndless.new(
4786
- target: target,
4787
- operator: operator,
4788
- name: name,
4789
- paren: params,
4790
- statement: bodystmt,
4791
- location: beginning.location.to(bodystmt.location)
4792
- )
4793
-
4794
- return node
4795
- end
4796
-
4797
- ending = find_token(Kw, "end")
4915
+ if ending
4916
+ tokens.delete(ending)
4917
+ bodystmt.bind(
4918
+ find_next_statement_start(params.location.end_char),
4919
+ ending.location.start_char
4920
+ )
4798
4921
 
4799
- bodystmt.bind(
4800
- find_next_statement_start(params.location.end_char),
4801
- ending.location.start_char
4802
- )
4922
+ Defs.new(
4923
+ target: target,
4924
+ operator: operator,
4925
+ name: name,
4926
+ params: params,
4927
+ bodystmt: bodystmt,
4928
+ location: beginning.location.to(ending.location)
4929
+ )
4930
+ else
4931
+ # In Ruby >= 3.1.0, this is a BodyStmt that wraps a single statement in
4932
+ # the statements list. Before, it was just the individual statement.
4933
+ statement = bodystmt.is_a?(BodyStmt) ? bodystmt.statements : bodystmt
4803
4934
 
4804
- Defs.new(
4805
- target: target,
4806
- operator: operator,
4807
- name: name,
4808
- params: params,
4809
- bodystmt: bodystmt,
4810
- location: beginning.location.to(ending.location)
4811
- )
4935
+ DefEndless.new(
4936
+ target: target,
4937
+ operator: operator,
4938
+ name: name,
4939
+ paren: params,
4940
+ statement: statement,
4941
+ location: beginning.location.to(bodystmt.location)
4942
+ )
4943
+ end
4812
4944
  end
4813
4945
 
4814
4946
  # DoBlock represents passing a block to a method call using the +do+ and +end+
@@ -8927,7 +9059,7 @@ class SyntaxTree < Ripper
8927
9059
  end
8928
9060
 
8929
9061
  class KeywordRestFormatter
8930
- # [:nil | KwRestParam] the value of the parameter
9062
+ # [:nil | ArgsForward | KwRestParam] the value of the parameter
8931
9063
  attr_reader :value
8932
9064
 
8933
9065
  def initialize(value)
@@ -9042,7 +9174,7 @@ class SyntaxTree < Ripper
9042
9174
  q.format(rest) if rest && rest.is_a?(ExcessedComma)
9043
9175
  end
9044
9176
 
9045
- if [Def, Defs].include?(q.parent.class)
9177
+ if [Def, Defs, DefEndless].include?(q.parent.class)
9046
9178
  q.group(0, "(", ")") do
9047
9179
  q.indent do
9048
9180
  q.breakable("")
@@ -9142,8 +9274,8 @@ class SyntaxTree < Ripper
9142
9274
  # (nil | ArgsForward | ExcessedComma | RestParam) rest,
9143
9275
  # (nil | Array[Ident]) posts,
9144
9276
  # (nil | Array[[Ident, nil | untyped]]) keywords,
9145
- # (nil | :nil | KwRestParam) keyword_rest,
9146
- # (nil | BlockArg) block
9277
+ # (nil | :nil | ArgsForward | KwRestParam) keyword_rest,
9278
+ # (nil | :& | BlockArg) block
9147
9279
  # ) -> Params
9148
9280
  def on_params(
9149
9281
  requireds,
@@ -9161,7 +9293,7 @@ class SyntaxTree < Ripper
9161
9293
  *posts,
9162
9294
  *keywords&.flat_map { |(key, value)| [key, value || nil] },
9163
9295
  (keyword_rest if keyword_rest != :nil),
9164
- block
9296
+ (block if block != :&)
9165
9297
  ].compact
9166
9298
 
9167
9299
  location =
@@ -9178,7 +9310,7 @@ class SyntaxTree < Ripper
9178
9310
  posts: posts || [],
9179
9311
  keywords: keywords || [],
9180
9312
  keyword_rest: keyword_rest,
9181
- block: block,
9313
+ block: (block if block != :&),
9182
9314
  location: location
9183
9315
  )
9184
9316
  end
@@ -12901,10 +13033,70 @@ class SyntaxTree < Ripper
12901
13033
  end
12902
13034
  end
12903
13035
 
13036
+ # PinnedVarRef represents a pinned variable reference within a pattern
13037
+ # matching pattern.
13038
+ #
13039
+ # case value
13040
+ # in ^variable
13041
+ # end
13042
+ #
13043
+ # This can be a plain local variable like the example above. It can also be a
13044
+ # a class variable, a global variable, or an instance variable.
13045
+ class PinnedVarRef
13046
+ # [VarRef] the value of this node
13047
+ attr_reader :value
13048
+
13049
+ # [Location] the location of this node
13050
+ attr_reader :location
13051
+
13052
+ # [Array[ Comment | EmbDoc ]] the comments attached to this node
13053
+ attr_reader :comments
13054
+
13055
+ def initialize(value:, location:, comments: [])
13056
+ @value = value
13057
+ @location = location
13058
+ @comments = comments
13059
+ end
13060
+
13061
+ def child_nodes
13062
+ [value]
13063
+ end
13064
+
13065
+ def format(q)
13066
+ q.group do
13067
+ q.text("^")
13068
+ q.format(value)
13069
+ end
13070
+ end
13071
+
13072
+ def pretty_print(q)
13073
+ q.group(2, "(", ")") do
13074
+ q.text("pinned_var_ref")
13075
+
13076
+ q.breakable
13077
+ q.pp(value)
13078
+
13079
+ q.pp(Comment::List.new(comments))
13080
+ end
13081
+ end
13082
+
13083
+ def to_json(*opts)
13084
+ { type: :pinned_var_ref, value: value, loc: location, cmts: comments }
13085
+ .to_json(*opts)
13086
+ end
13087
+ end
13088
+
12904
13089
  # :call-seq:
12905
13090
  # on_var_ref: ((Const | CVar | GVar | Ident | IVar | Kw) value) -> VarRef
12906
13091
  def on_var_ref(value)
12907
- VarRef.new(value: value, location: value.location)
13092
+ pin = find_token(Op, "^", consume: false)
13093
+
13094
+ if pin && pin.location.start_char == value.location.start_char - 1
13095
+ tokens.delete(pin)
13096
+ PinnedVarRef.new(value: value, location: pin.location.to(value.location))
13097
+ else
13098
+ VarRef.new(value: value, location: value.location)
13099
+ end
12908
13100
  end
12909
13101
 
12910
13102
  # VCall represent any plain named object with Ruby that could be either a
@@ -12976,6 +13168,10 @@ class SyntaxTree < Ripper
12976
13168
  @comments = comments
12977
13169
  end
12978
13170
 
13171
+ def child_nodes
13172
+ []
13173
+ end
13174
+
12979
13175
  def format(q)
12980
13176
  end
12981
13177
 
@@ -13050,6 +13246,14 @@ class SyntaxTree < Ripper
13050
13246
  separator = -> { q.group { q.comma_breakable } }
13051
13247
  q.seplist(arguments.parts, separator) { |part| q.format(part) }
13052
13248
  end
13249
+
13250
+ # Very special case here. If you're inside of a when clause and the
13251
+ # last argument to the predicate is and endless range, then you are
13252
+ # forced to use the "then" keyword to make it parse properly.
13253
+ last = arguments.parts.last
13254
+ if (last.is_a?(Dot2) || last.is_a?(Dot3)) && !last.right
13255
+ q.text(" then")
13256
+ end
13053
13257
  end
13054
13258
  end
13055
13259
 
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: 1.1.1
4
+ version: 1.2.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: 2021-12-09 00:00:00.000000000 Z
11
+ date: 2022-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -114,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
114
  - !ruby/object:Gem::Version
115
115
  version: '0'
116
116
  requirements: []
117
- rubygems_version: 3.2.3
117
+ rubygems_version: 3.3.0.dev
118
118
  signing_key:
119
119
  specification_version: 4
120
120
  summary: A parser based on ripper