syntax_tree 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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