syntax_tree 6.2.0 → 6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec83d4ecf5722316de95bb541d45d6213db7917ff6bb6e13fef397a65075bffa
4
- data.tar.gz: 6171e1b60ab99eea54a761d5feec302554133b97fea8f95ed473a799e4005542
3
+ metadata.gz: 4d2e2bb8b41f091f6895ef5207081790c08ddaab126e8d5ddac324d9c13ea012
4
+ data.tar.gz: 9383e5f306415f9ad9e52c1cc58766fc629ae612644006a794229641a68cc7e7
5
5
  SHA512:
6
- metadata.gz: 5266b33544de3aae02d4d200f7239ed388a1e1bb045e93ed46c1d14d1fb618ee8592238ce21040cc05708c7947ed564dd3477d757849da080758c9399d883309
7
- data.tar.gz: 398685af92d3b506382feba646f5d24e4151b6dc3d25e19a018b8fdfe5e0d301a53c54a0684a584c083951267e9205be46ab36420d075536bbe1a08d12de7b3d
6
+ metadata.gz: 1da6c505691e7a07716c26c57e536f38cae5b4c13a370a9e95e75aeda01e460bb6c9d7e21bc2cc8c226cdba412d064d4a386a8b6418e729af2243913b86c1087
7
+ data.tar.gz: b72d5dc5173edbeba46427278297f6b5c90d755f5806fa154084faef72978895e1a84ebb2cb3a52401b231b1c73e07c3f90f7268c14d115dc4036cee0632c0c2
@@ -12,7 +12,7 @@ jobs:
12
12
  steps:
13
13
  - name: Dependabot metadata
14
14
  id: metadata
15
- uses: dependabot/fetch-metadata@v1.6.0
15
+ uses: dependabot/fetch-metadata@v2.4.0
16
16
  with:
17
17
  github-token: "${{ secrets.GITHUB_TOKEN }}"
18
18
  - name: Enable auto-merge for Dependabot PRs
@@ -5,8 +5,8 @@ on:
5
5
  branches:
6
6
  - main
7
7
 
8
- # Allows you to run this workflow manually from the Actions tab
9
- workflow_dispatch:
8
+ # Allows you to run this workflow manually from the Actions tab
9
+ workflow_dispatch:
10
10
 
11
11
  # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
12
12
  permissions:
@@ -27,7 +27,7 @@ jobs:
27
27
  - name: Checkout
28
28
  uses: actions/checkout@v4
29
29
  - name: Setup Pages
30
- uses: actions/configure-pages@v3
30
+ uses: actions/configure-pages@v5
31
31
  - name: Set up Ruby
32
32
  uses: ruby/setup-ruby@v1
33
33
  with:
@@ -39,7 +39,7 @@ jobs:
39
39
  rdoc --main README.md --op _site --exclude={Gemfile,Rakefile,"coverage/*","vendor/*","bin/*","test/*","tmp/*"}
40
40
  cp -r doc _site/doc
41
41
  - name: Upload artifact
42
- uses: actions/upload-pages-artifact@v2
42
+ uses: actions/upload-pages-artifact@v3
43
43
 
44
44
  # Deployment job
45
45
  deploy:
@@ -51,4 +51,4 @@ jobs:
51
51
  steps:
52
52
  - name: Deploy to GitHub Pages
53
53
  id: deployment
54
- uses: actions/deploy-pages@v2
54
+ uses: actions/deploy-pages@v4
@@ -14,7 +14,8 @@ jobs:
14
14
  - '3.0'
15
15
  - '3.1'
16
16
  - '3.2'
17
- - head
17
+ - '3.3'
18
+ - '3.4'
18
19
  - truffleruby-head
19
20
  name: CI
20
21
  runs-on: ubuntu-latest
data/.rubocop.yml CHANGED
@@ -136,6 +136,9 @@ Style/KeywordParametersOrder:
136
136
  Style/MissingRespondToMissing:
137
137
  Enabled: false
138
138
 
139
+ Style/MultipleComparison:
140
+ Enabled: false
141
+
139
142
  Style/MutableConstant:
140
143
  Enabled: false
141
144
 
@@ -157,6 +160,9 @@ Style/PerlBackrefs:
157
160
  Style/RedundantArrayConstructor:
158
161
  Enabled: false
159
162
 
163
+ Style/RedundantParentheses:
164
+ Enabled: false
165
+
160
166
  Style/SafeNavigation:
161
167
  Enabled: false
162
168
 
data/CHANGELOG.md CHANGED
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [6.3.0] - 2025-07-16
10
+
11
+ ### Added
12
+
13
+ - The `--extension` command line option has been added to the CLI to specify what type of content is coming from stdin.
14
+ - The `--config` command line option has been added to the CLI to specify the path to the configuration file.
15
+
16
+ ### Changed
17
+
18
+ - Fix formatting of character literals when single quotes is enabled.
19
+ - Pass ignore files option to the language server.
20
+ - Hash keys should remain unchanged when there are any omitted values in the hash.
21
+ - We now properly handle compilation errors in the parser.
22
+
9
23
  ## [6.2.0] - 2023-09-20
10
24
 
11
25
  ### Added
data/Gemfile CHANGED
@@ -3,3 +3,5 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
+
7
+ gem "fiddle"
data/Gemfile.lock CHANGED
@@ -1,50 +1,53 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree (6.2.0)
4
+ syntax_tree (6.3.0)
5
5
  prettier_print (>= 1.2.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- ast (2.4.2)
11
- base64 (0.1.1)
12
- docile (1.4.0)
13
- json (2.6.3)
14
- language_server-protocol (3.17.0.3)
15
- minitest (5.20.0)
16
- parallel (1.23.0)
17
- parser (3.2.2.3)
10
+ ast (2.4.3)
11
+ docile (1.4.1)
12
+ fiddle (1.1.8)
13
+ json (2.12.2)
14
+ language_server-protocol (3.17.0.5)
15
+ lint_roller (1.1.0)
16
+ minitest (5.25.5)
17
+ parallel (1.27.0)
18
+ parser (3.3.8.0)
18
19
  ast (~> 2.4.1)
19
20
  racc
20
21
  prettier_print (1.2.1)
21
- racc (1.7.1)
22
+ prism (1.4.0)
23
+ racc (1.8.1)
22
24
  rainbow (3.1.1)
23
- rake (13.0.6)
24
- regexp_parser (2.8.1)
25
- rexml (3.2.6)
26
- rubocop (1.56.3)
27
- base64 (~> 0.1.1)
25
+ rake (13.3.0)
26
+ regexp_parser (2.10.0)
27
+ rubocop (1.78.0)
28
28
  json (~> 2.3)
29
- language_server-protocol (>= 3.17.0)
29
+ language_server-protocol (~> 3.17.0.2)
30
+ lint_roller (~> 1.1.0)
30
31
  parallel (~> 1.10)
31
- parser (>= 3.2.2.3)
32
+ parser (>= 3.3.0.2)
32
33
  rainbow (>= 2.2.2, < 4.0)
33
- regexp_parser (>= 1.8, < 3.0)
34
- rexml (>= 3.2.5, < 4.0)
35
- rubocop-ast (>= 1.28.1, < 2.0)
34
+ regexp_parser (>= 2.9.3, < 3.0)
35
+ rubocop-ast (>= 1.45.1, < 2.0)
36
36
  ruby-progressbar (~> 1.7)
37
- unicode-display_width (>= 2.4.0, < 3.0)
38
- rubocop-ast (1.29.0)
39
- parser (>= 3.2.1.0)
37
+ unicode-display_width (>= 2.4.0, < 4.0)
38
+ rubocop-ast (1.45.1)
39
+ parser (>= 3.3.7.2)
40
+ prism (~> 1.4)
40
41
  ruby-progressbar (1.13.0)
41
42
  simplecov (0.22.0)
42
43
  docile (~> 1.1)
43
44
  simplecov-html (~> 0.11)
44
45
  simplecov_json_formatter (~> 0.1)
45
- simplecov-html (0.12.3)
46
+ simplecov-html (0.13.1)
46
47
  simplecov_json_formatter (0.1.4)
47
- unicode-display_width (2.4.2)
48
+ unicode-display_width (3.1.4)
49
+ unicode-emoji (~> 4.0, >= 4.0.4)
50
+ unicode-emoji (4.0.4)
48
51
 
49
52
  PLATFORMS
50
53
  arm64-darwin-21
@@ -55,6 +58,7 @@ PLATFORMS
55
58
 
56
59
  DEPENDENCIES
57
60
  bundler
61
+ fiddle
58
62
  minitest
59
63
  rake
60
64
  rubocop
data/README.md CHANGED
@@ -187,7 +187,7 @@ SyntaxTree::Binary[
187
187
 
188
188
  ### format
189
189
 
190
- This command will output the formatted version of each of the listed files. Importantly, it will not write that content back to the source files. It is meant to display the formatted version only.
190
+ This command will output the formatted version of each of the listed files to stdout. Importantly, it will not write that content back to the source files for that, you want [`write`](#write).
191
191
 
192
192
  ```sh
193
193
  stree format path/to/file.rb
@@ -63,12 +63,13 @@ module SyntaxTree
63
63
  class ScriptItem
64
64
  attr_reader :source
65
65
 
66
- def initialize(source)
66
+ def initialize(source, extension)
67
67
  @source = source
68
+ @extension = extension
68
69
  end
69
70
 
70
71
  def handler
71
- HANDLERS[".rb"]
72
+ HANDLERS[@extension]
72
73
  end
73
74
 
74
75
  def filepath
@@ -82,8 +83,12 @@ module SyntaxTree
82
83
 
83
84
  # An item of work that correspond to the content passed in via stdin.
84
85
  class STDINItem
86
+ def initialize(extension)
87
+ @extension = extension
88
+ end
89
+
85
90
  def handler
86
- HANDLERS[".rb"]
91
+ HANDLERS[@extension]
87
92
  end
88
93
 
89
94
  def filepath
@@ -159,7 +164,7 @@ module SyntaxTree
159
164
  attr_reader :entries
160
165
 
161
166
  def initialize(options)
162
- super(options)
167
+ super
163
168
  @entries = []
164
169
  end
165
170
 
@@ -450,14 +455,26 @@ module SyntaxTree
450
455
  #{Color.bold("stree write [--plugins=...] [--print-width=NUMBER] [-e SCRIPT] FILE")}
451
456
  Read, format, and write back the source of the given files
452
457
 
458
+ --ignore-files=...
459
+ A glob pattern to ignore files when processing. This can be specified
460
+ multiple times to ignore multiple patterns.
461
+
453
462
  --plugins=...
454
463
  A comma-separated list of plugins to load.
455
464
 
456
- --print-width=NUMBER
465
+ --print-width=...
457
466
  The maximum line width to use when formatting.
458
467
 
459
- -e SCRIPT
460
- Parse an inline Ruby string.
468
+ -e ...
469
+ Parse an inline string.
470
+
471
+ --extension=...
472
+ A file extension matching the content passed in via STDIN or -e.
473
+ Defaults to '.rb'.
474
+
475
+ --config=...
476
+ Path to a configuration file. Defaults to .streerc in the current
477
+ working directory.
461
478
  HELP
462
479
 
463
480
  # This represents all of the options that can be passed to the CLI. It is
@@ -468,6 +485,7 @@ module SyntaxTree
468
485
  :plugins,
469
486
  :print_width,
470
487
  :scripts,
488
+ :extension,
471
489
  :target_ruby_version
472
490
 
473
491
  def initialize
@@ -475,6 +493,7 @@ module SyntaxTree
475
493
  @plugins = []
476
494
  @print_width = DEFAULT_PRINT_WIDTH
477
495
  @scripts = []
496
+ @extension = ".rb"
478
497
  @target_ruby_version = DEFAULT_RUBY_VERSION
479
498
  end
480
499
 
@@ -523,6 +542,13 @@ module SyntaxTree
523
542
  # it and add it to the list of scripts to run.
524
543
  opts.on("-e SCRIPT") { |script| @scripts << script }
525
544
 
545
+ # If there is a extension specified, then parse it and use it for
546
+ # STDIN and scripts.
547
+ opts.on("--extension=EXTENSION") do |extension|
548
+ # Both ".rb" and "rb" are going to work
549
+ @extension = ".#{extension.delete_prefix(".")}"
550
+ end
551
+
526
552
  # If there is a target ruby version specified on the command line,
527
553
  # parse that out and use it when formatting.
528
554
  opts.on("--target-ruby-version=VERSION") do |version|
@@ -546,8 +572,16 @@ module SyntaxTree
546
572
 
547
573
  attr_reader :filepath
548
574
 
549
- def initialize
550
- @filepath = File.join(Dir.pwd, FILENAME)
575
+ def initialize(filepath = nil)
576
+ if filepath
577
+ if File.readable?(filepath)
578
+ @filepath = filepath
579
+ else
580
+ raise ArgumentError, "Invalid configuration file: #{filepath}"
581
+ end
582
+ else
583
+ @filepath = File.join(Dir.pwd, FILENAME)
584
+ end
551
585
  end
552
586
 
553
587
  def exists?
@@ -565,8 +599,24 @@ module SyntaxTree
565
599
  def run(argv)
566
600
  name, *arguments = argv
567
601
 
568
- config_file = ConfigFile.new
569
- arguments.unshift(*config_file.arguments)
602
+ # First, we need to check if there's a --config option specified
603
+ # so we can use the custom config file path.
604
+ config_filepath = nil
605
+ arguments.each_with_index do |arg, index|
606
+ if arg.start_with?("--config=")
607
+ config_filepath = arg.split("=", 2)[1]
608
+ arguments.delete_at(index)
609
+ break
610
+ elsif arg == "--config" && arguments[index + 1]
611
+ config_filepath = arguments[index + 1]
612
+ arguments.delete_at(index + 1)
613
+ arguments.delete_at(index)
614
+ break
615
+ end
616
+ end
617
+
618
+ config_file = ConfigFile.new(config_filepath)
619
+ arguments = config_file.arguments.concat(arguments)
570
620
 
571
621
  options = Options.new
572
622
  options.parse(arguments)
@@ -593,7 +643,10 @@ module SyntaxTree
593
643
  when "j", "json"
594
644
  Json.new(options)
595
645
  when "lsp"
596
- LanguageServer.new(print_width: options.print_width).run
646
+ LanguageServer.new(
647
+ print_width: options.print_width,
648
+ ignore_files: options.ignore_files
649
+ ).run
597
650
  return 0
598
651
  when "m", "match"
599
652
  Match.new(options)
@@ -630,9 +683,11 @@ module SyntaxTree
630
683
  end
631
684
  end
632
685
 
633
- options.scripts.each { |script| queue << ScriptItem.new(script) }
686
+ options.scripts.each do |script|
687
+ queue << ScriptItem.new(script, options.extension)
688
+ end
634
689
  else
635
- queue << STDINItem.new
690
+ queue << STDINItem.new(options.extension)
636
691
  end
637
692
 
638
693
  # At the end, we're going to return whether or not this worker ever
@@ -217,11 +217,13 @@ module SyntaxTree
217
217
  def initialize(
218
218
  input: $stdin,
219
219
  output: $stdout,
220
- print_width: DEFAULT_PRINT_WIDTH
220
+ print_width: DEFAULT_PRINT_WIDTH,
221
+ ignore_files: []
221
222
  )
222
223
  @input = input.binmode
223
224
  @output = output.binmode
224
225
  @print_width = print_width
226
+ @ignore_files = ignore_files
225
227
  end
226
228
 
227
229
  # rubocop:disable Layout/LineLength
@@ -255,8 +257,12 @@ module SyntaxTree
255
257
  store.delete(request.dig(:params, :textDocument, :uri))
256
258
  when Request[method: "textDocument/formatting", id: :any, params: { textDocument: { uri: :any } }]
257
259
  uri = request.dig(:params, :textDocument, :uri)
260
+ filepath = uri.split("///").last
261
+ ignore = @ignore_files.any? do |glob|
262
+ File.fnmatch(glob, filepath)
263
+ end
258
264
  contents = store[uri]
259
- write(id: request[:id], result: contents ? format(contents, uri.split(".").last) : nil)
265
+ write(id: request[:id], result: contents && !ignore ? format(contents, uri.split(".").last) : nil)
260
266
  when Request[method: "textDocument/inlayHint", id: :any, params: { textDocument: { uri: :any } }]
261
267
  uri = request.dig(:params, :textDocument, :uri)
262
268
  contents = store[uri]
@@ -288,7 +288,7 @@ module SyntaxTree
288
288
  q.text(value)
289
289
  else
290
290
  q.text(q.quote)
291
- q.text(value[1] == "\"" ? "\\\"" : value[1])
291
+ q.text(value[1] == q.quote ? "\\#{q.quote}" : value[1])
292
292
  q.text(q.quote)
293
293
  end
294
294
  end
@@ -1783,45 +1783,60 @@ module SyntaxTree
1783
1783
  end
1784
1784
  end
1785
1785
 
1786
- def self.for(container)
1787
- container.assocs.each do |assoc|
1788
- if assoc.is_a?(AssocSplat)
1789
- # Splat nodes do not impact the formatting choice.
1790
- elsif assoc.value.nil?
1791
- # If the value is nil, then it has been omitted. In this case we have
1792
- # to match the existing formatting because standardizing would
1793
- # potentially break the code. For example:
1794
- #
1795
- # { first:, "second" => "value" }
1796
- #
1797
- return Identity.new
1798
- else
1799
- # Otherwise, we need to check the type of the key. If it's a label or
1800
- # dynamic symbol, we can use labels. If it's a symbol literal then it
1801
- # needs to match a certain pattern to be used as a label. If it's
1802
- # anything else, then we need to use hash rockets.
1803
- case assoc.key
1804
- when Label, DynaSymbol
1805
- # Here labels can be used.
1806
- when SymbolLiteral
1807
- # When attempting to convert a hash rocket into a hash label,
1808
- # you need to take care because only certain patterns are
1809
- # allowed. Ruby source says that they have to match keyword
1810
- # arguments to methods, but don't specify what that is. After
1811
- # some experimentation, it looks like it's:
1812
- value = assoc.key.value.value
1813
-
1814
- if !value.match?(/^[_A-Za-z]/) || value.end_with?("=")
1815
- return Rockets.new
1816
- end
1786
+ class << self
1787
+ def for(container)
1788
+ (assocs = container.assocs).each_with_index do |assoc, index|
1789
+ if assoc.is_a?(AssocSplat)
1790
+ # Splat nodes do not impact the formatting choice.
1791
+ elsif assoc.value.nil?
1792
+ # If the value is nil, then it has been omitted. In this case we
1793
+ # have to match the existing formatting because standardizing would
1794
+ # potentially break the code. For example:
1795
+ #
1796
+ # { first:, "second" => "value" }
1797
+ #
1798
+ return Identity.new
1817
1799
  else
1818
- # If the value is anything else, we have to use hash rockets.
1819
- return Rockets.new
1800
+ # Otherwise, we need to check the type of the key. If it's a label
1801
+ # or dynamic symbol, we can use labels. If it's a symbol literal
1802
+ # then it needs to match a certain pattern to be used as a label. If
1803
+ # it's anything else, then we need to use hash rockets.
1804
+ case assoc.key
1805
+ when Label, DynaSymbol
1806
+ # Here labels can be used.
1807
+ when SymbolLiteral
1808
+ # When attempting to convert a hash rocket into a hash label,
1809
+ # you need to take care because only certain patterns are
1810
+ # allowed. Ruby source says that they have to match keyword
1811
+ # arguments to methods, but don't specify what that is. After
1812
+ # some experimentation, it looks like it's:
1813
+ value = assoc.key.value.value
1814
+
1815
+ if !value.match?(/^[_A-Za-z]/) || value.end_with?("=")
1816
+ if omitted_value?(assocs[(index + 1)..])
1817
+ return Identity.new
1818
+ else
1819
+ return Rockets.new
1820
+ end
1821
+ end
1822
+ else
1823
+ if omitted_value?(assocs[(index + 1)..])
1824
+ return Identity.new
1825
+ else
1826
+ return Rockets.new
1827
+ end
1828
+ end
1820
1829
  end
1821
1830
  end
1831
+
1832
+ Labels.new
1822
1833
  end
1823
1834
 
1824
- Labels.new
1835
+ private
1836
+
1837
+ def omitted_value?(assocs)
1838
+ assocs.any? { |assoc| !assoc.is_a?(AssocSplat) && assoc.value.nil? }
1839
+ end
1825
1840
  end
1826
1841
  end
1827
1842
 
@@ -11644,6 +11659,10 @@ module SyntaxTree
11644
11659
  elsif value.is_a?(Array) && (index = value.index(self))
11645
11660
  parent.public_send(key)[index] = replace
11646
11661
  break
11662
+ elsif value.is_a?(Array) &&
11663
+ (index = value.index { |(_k, v)| v == self })
11664
+ parent.public_send(key)[index][1] = replace
11665
+ break
11647
11666
  end
11648
11667
  end
11649
11668
  end
@@ -670,7 +670,11 @@ module SyntaxTree
670
670
 
671
671
  visit_methods do
672
672
  def visit_var_ref(node)
673
- node.pin(stack[-2], pins.shift)
673
+ if node.start_char > pins.first.start_char
674
+ node.pin(stack[-2], pins.shift)
675
+ else
676
+ super
677
+ end
674
678
  end
675
679
  end
676
680
 
@@ -1732,13 +1736,13 @@ module SyntaxTree
1732
1736
  # :call-seq:
1733
1737
  # on_field: (
1734
1738
  # untyped parent,
1735
- # (:"::" | Op | Period) operator
1739
+ # (:"::" | Op | Period | 73) operator
1736
1740
  # (Const | Ident) name
1737
1741
  # ) -> Field
1738
1742
  def on_field(parent, operator, name)
1739
1743
  Field.new(
1740
1744
  parent: parent,
1741
- operator: operator,
1745
+ operator: operator == 73 ? :"::" : operator,
1742
1746
  name: name,
1743
1747
  location: parent.location.to(name.location)
1744
1748
  )
@@ -2867,6 +2871,7 @@ module SyntaxTree
2867
2871
  alias on_assign_error on_parse_error
2868
2872
  alias on_class_name_error on_parse_error
2869
2873
  alias on_param_error on_parse_error
2874
+ alias compile_error on_parse_error
2870
2875
 
2871
2876
  # :call-seq:
2872
2877
  # on_period: (String value) -> Period
@@ -70,6 +70,7 @@ module SyntaxTree
70
70
  raise CompilationError, query
71
71
  end
72
72
 
73
+ raise CompilationError, query if program.nil?
73
74
  compile_node(program.statements.body.first.consequent.pattern)
74
75
  end
75
76
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SyntaxTree
4
- VERSION = "6.2.0"
4
+ VERSION = "6.3.0"
5
5
  end
@@ -408,7 +408,7 @@ module SyntaxTree
408
408
  def find_local(iseq, operands)
409
409
  name_string, level_string = operands.split(/,\s*/)
410
410
  name = name_string.to_sym
411
- level = level_string&.to_i || 0
411
+ level = level_string.to_i
412
412
 
413
413
  iseq.local_table.plain(name)
414
414
  iseq.local_table.find(name, level)
@@ -455,7 +455,7 @@ module SyntaxTree
455
455
  CallData::CALL_ARGS_SIMPLE
456
456
  end
457
457
 
458
- YARV.calldata(message.to_sym, argc_value&.to_i || 0, flags)
458
+ YARV.calldata(message.to_sym, argc_value.to_i, flags)
459
459
  end
460
460
  end
461
461
  end
@@ -41,7 +41,7 @@ module SyntaxTree
41
41
  end
42
42
 
43
43
  def flag?(mask)
44
- (flags & mask) > 0
44
+ flags.anybits?(mask)
45
45
  end
46
46
 
47
47
  def to_h
@@ -45,7 +45,7 @@ module SyntaxTree
45
45
  when Integer
46
46
  Int(value.to_s)
47
47
  when Symbol
48
- SymbolLiteral(Ident(value.to_s))
48
+ SymbolLiteral(Ident(value.name))
49
49
  end
50
50
  end
51
51
 
@@ -88,10 +88,10 @@ module SyntaxTree
88
88
 
89
89
  clause << HashLiteral(LBrace("{"), assocs)
90
90
  when GetGlobal
91
- clause << VarRef(GVar(insn.name.to_s))
91
+ clause << VarRef(GVar(insn.name.name))
92
92
  when GetLocalWC0
93
93
  local = iseq.local_table.locals[insn.index]
94
- clause << VarRef(Ident(local.name.to_s))
94
+ clause << VarRef(Ident(local.name.name))
95
95
  when Jump
96
96
  clause << Assign(block_label.field, node_for(insn.label.name))
97
97
  clause << Next(Args([]))
@@ -123,7 +123,7 @@ module SyntaxTree
123
123
  left, right = clause.pop(2)
124
124
  clause << Binary(left, :"!=", right)
125
125
  when OptSendWithoutBlock
126
- method = insn.calldata.method.to_s
126
+ method = insn.calldata.method.name
127
127
  argc = insn.calldata.argc
128
128
 
129
129
  if insn.calldata.flag?(CallData::CALL_FCALL)
@@ -182,7 +182,7 @@ module SyntaxTree
182
182
  when PutSelf
183
183
  clause << VarRef(Kw("self"))
184
184
  when SetGlobal
185
- target = GVar(insn.name.to_s)
185
+ target = GVar(insn.name.name)
186
186
  value = clause.pop
187
187
 
188
188
  clause << if value.is_a?(Binary) && VarRef(target) === value.left
@@ -256,7 +256,7 @@ module SyntaxTree
256
256
  def local_name(index, level)
257
257
  current = iseq
258
258
  level.times { current = current.parent_iseq }
259
- current.local_table.locals[index].name.to_s
259
+ current.local_table.locals[index].name.name
260
260
  end
261
261
  end
262
262
  end
@@ -252,19 +252,23 @@ module SyntaxTree
252
252
  dumped_options = argument_options.dup
253
253
  dumped_options[:opt].map!(&:name) if dumped_options[:opt]
254
254
 
255
+ metadata = {
256
+ arg_size: argument_size,
257
+ local_size: local_table.size,
258
+ stack_max: stack.maximum_size,
259
+ node_id: -1,
260
+ node_ids: [-1] * insns.length
261
+ }
262
+
263
+ metadata[:parser] = :prism if RUBY_VERSION >= "3.3"
264
+
255
265
  # Next, return the instruction sequence as an array.
256
266
  [
257
267
  MAGIC,
258
268
  versions[0],
259
269
  versions[1],
260
270
  1,
261
- {
262
- arg_size: argument_size,
263
- local_size: local_table.size,
264
- stack_max: stack.maximum_size,
265
- node_id: -1,
266
- node_ids: [-1] * insns.length
267
- },
271
+ metadata,
268
272
  name,
269
273
  file,
270
274
  "<compiled>",
@@ -689,6 +693,10 @@ module SyntaxTree
689
693
  push(ConcatStrings.new(number))
690
694
  end
691
695
 
696
+ def concattoarray(object)
697
+ push(ConcatToArray.new(object))
698
+ end
699
+
692
700
  def defineclass(name, class_iseq, flags)
693
701
  push(DefineClass.new(name, class_iseq, flags))
694
702
  end
@@ -897,6 +905,14 @@ module SyntaxTree
897
905
  push(Pop.new)
898
906
  end
899
907
 
908
+ def pushtoarraykwsplat
909
+ push(PushToArrayKwSplat.new)
910
+ end
911
+
912
+ def putchilledstring(object)
913
+ push(PutChilledString.new(object))
914
+ end
915
+
900
916
  def putnil
901
917
  push(PutNil.new)
902
918
  end
@@ -1079,6 +1095,8 @@ module SyntaxTree
1079
1095
  iseq.concatarray
1080
1096
  when :concatstrings
1081
1097
  iseq.concatstrings(opnds[0])
1098
+ when :concattoarray
1099
+ iseq.concattoarray(opnds[0])
1082
1100
  when :defineclass
1083
1101
  iseq.defineclass(opnds[0], from(opnds[1], options, iseq), opnds[2])
1084
1102
  when :defined
@@ -1191,8 +1209,13 @@ module SyntaxTree
1191
1209
  iseq.newarray(opnds[0])
1192
1210
  iseq.send(YARV.calldata(:min))
1193
1211
  when :opt_newarray_send
1212
+ mid = opnds[1]
1213
+ if RUBY_VERSION >= "3.4"
1214
+ mid = %i[max min hash pack pack_buffer include?][mid - 1]
1215
+ end
1216
+
1194
1217
  iseq.newarray(opnds[0])
1195
- iseq.send(CallData.new(opnds[1]))
1218
+ iseq.send(CallData.new(mid))
1196
1219
  when :opt_neq
1197
1220
  iseq.push(
1198
1221
  OptNEq.new(CallData.from(opnds[0]), CallData.from(opnds[1]))
@@ -1207,6 +1230,10 @@ module SyntaxTree
1207
1230
  iseq.send(YARV.calldata(:-@))
1208
1231
  when :pop
1209
1232
  iseq.pop
1233
+ when :pushtoarraykwsplat
1234
+ iseq.pushtoarraykwsplat
1235
+ when :putchilledstring
1236
+ iseq.putchilledstring(opnds[0])
1210
1237
  when :putnil
1211
1238
  iseq.putnil
1212
1239
  when :putobject
@@ -757,6 +757,59 @@ module SyntaxTree
757
757
  end
758
758
  end
759
759
 
760
+ # ### Summary
761
+ #
762
+ # `concattoarray` pops a single value off the stack and attempts to concat
763
+ # it to the Array on top of the stack. If the value is not an Array, it
764
+ # will be coerced into one.
765
+ #
766
+ # ### Usage
767
+ #
768
+ # ~~~ruby
769
+ # [1, *2]
770
+ # ~~~
771
+ #
772
+ class ConcatToArray < Instruction
773
+ attr_reader :object
774
+
775
+ def initialize(object)
776
+ @object = object
777
+ end
778
+
779
+ def disasm(fmt)
780
+ fmt.instruction("concattoarray", [fmt.object(object)])
781
+ end
782
+
783
+ def to_a(_iseq)
784
+ [:concattoarray, object]
785
+ end
786
+
787
+ def deconstruct_keys(_keys)
788
+ { object: object }
789
+ end
790
+
791
+ def ==(other)
792
+ other.is_a?(ConcatToArray) && other.object == object
793
+ end
794
+
795
+ def length
796
+ 2
797
+ end
798
+
799
+ def pops
800
+ 1
801
+ end
802
+
803
+ def pushes
804
+ 1
805
+ end
806
+
807
+ def call(vm)
808
+ array, value = vm.pop(2)
809
+ vm.push(array.concat(Array(value)))
810
+ end
811
+ end
812
+
760
813
  # ### Summary
761
814
  #
762
815
  # `defineclass` defines a class. First it pops the superclass off the
@@ -4472,6 +4525,52 @@ module SyntaxTree
4472
4525
  end
4473
4526
  end
4474
4527
 
4528
+ # ### Summary
4529
+ #
4530
+ # `pushtoarraykwsplat` is used to append a hash literal that is being
4531
+ # splatted onto an array.
4532
+ #
4533
+ # ### Usage
4534
+ #
4535
+ # ~~~ruby
4536
+ # ["string", **{ foo: "bar" }]
4537
+ # ~~~
4538
+ #
4539
+ class PushToArrayKwSplat < Instruction
4540
+ def disasm(fmt)
4541
+ fmt.instruction("pushtoarraykwsplat")
4542
+ end
4543
+
4544
+ def to_a(_iseq)
4545
+ [:pushtoarraykwsplat]
4546
+ end
4547
+
4548
+ def deconstruct_keys(_keys)
4549
+ {}
4550
+ end
4551
+
4552
+ def ==(other)
4553
+ other.is_a?(PushToArrayKwSplat)
4554
+ end
4555
+
4556
+ def length
4557
+ 2
4558
+ end
4559
+
4560
+ def pops
4561
+ 2
4562
+ end
4563
+
4564
+ def pushes
4565
+ 1
4566
+ end
4567
+
4568
+ def call(vm)
4569
+ array, hash = vm.pop(2)
4570
+ vm.push(array << hash)
4571
+ end
4572
+ end
4573
+
4475
4574
  # ### Summary
4476
4575
  #
4477
4576
  # `putnil` pushes a global nil object onto the stack.
@@ -4759,6 +4858,54 @@ module SyntaxTree
4759
4858
  end
4760
4859
  end
4761
4860
 
4861
+ # ### Summary
4862
+ #
4863
+ # `putchilledstring` pushes an unfrozen string literal onto the stack that
4864
+ # acts like a frozen string. This is a migration path to frozen string
4865
+ # literals as the default in the future.
4866
+ #
4867
+ # ### Usage
4868
+ #
4869
+ # ~~~ruby
4870
+ # "foo"
4871
+ # ~~~
4872
+ #
4873
+ class PutChilledString < Instruction
4874
+ attr_reader :object
4875
+
4876
+ def initialize(object)
4877
+ @object = object
4878
+ end
4879
+
4880
+ def disasm(fmt)
4881
+ fmt.instruction("putchilledstring", [fmt.object(object)])
4882
+ end
4883
+
4884
+ def to_a(_iseq)
4885
+ [:putchilledstring, object]
4886
+ end
4887
+
4888
+ def deconstruct_keys(_keys)
4889
+ { object: object }
4890
+ end
4891
+
4892
+ def ==(other)
4893
+ other.is_a?(PutChilledString) && other.object == object
4894
+ end
4895
+
4896
+ def length
4897
+ 2
4898
+ end
4899
+
4900
+ def pushes
4901
+ 1
4902
+ end
4903
+
4904
+ def call(vm)
4905
+ vm.push(object.dup)
4906
+ end
4907
+ end
4908
+
4762
4909
  # ### Summary
4763
4910
  #
4764
4911
  # `putstring` pushes an unfrozen string literal onto the stack.
@@ -43,8 +43,13 @@ module ParseHelper
43
43
  # that we do not support.
44
44
  return if (versions & %w[3.1 3.2]).empty?
45
45
 
46
- entry = caller.find { _1.include?("test_parser.rb") }
47
- _, lineno, name = *entry.match(/(\d+):in `(.+)'/)
46
+ entry =
47
+ caller.find do |call|
48
+ call.include?("test_parser.rb") && call.match?(%r{(?<!/)test_})
49
+ end
50
+
51
+ _, lineno, name =
52
+ *entry.match(/(\d+):in [`'](?:block in )?(?:TestParser#)?(.+)'/)
48
53
 
49
54
  COLLECTED["#{name}:#{lineno}"] << code
50
55
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syntax_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.2.0
4
+ version: 6.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2023-09-20 00:00:00.000000000 Z
10
+ date: 2025-07-17 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: prettier_print
@@ -94,7 +93,6 @@ dependencies:
94
93
  - - ">="
95
94
  - !ruby/object:Gem::Version
96
95
  version: '0'
97
- description:
98
96
  email:
99
97
  - kddnewton@gmail.com
100
98
  executables:
@@ -110,7 +108,6 @@ files:
110
108
  - ".github/workflows/main.yml"
111
109
  - ".gitignore"
112
110
  - ".rubocop.yml"
113
- - ".ruby-version"
114
111
  - CHANGELOG.md
115
112
  - CODE_OF_CONDUCT.md
116
113
  - Gemfile
@@ -184,7 +181,6 @@ licenses:
184
181
  - MIT
185
182
  metadata:
186
183
  rubygems_mfa_required: 'true'
187
- post_install_message:
188
184
  rdoc_options: []
189
185
  require_paths:
190
186
  - lib
@@ -199,8 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
195
  - !ruby/object:Gem::Version
200
196
  version: '0'
201
197
  requirements: []
202
- rubygems_version: 3.5.0.dev
203
- signing_key:
198
+ rubygems_version: 3.6.2
204
199
  specification_version: 4
205
200
  summary: A parser based on ripper
206
201
  test_files: []
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 3.2.0