Repository: ruby/prism
Pull request #2375 (2024-02-07 13:52:08)

RipperCompat: support method calls with blocks
There are a few cases we don't correctly support yet ("foo bar 1") and the argument handling is messy here. I'm working on it. But we're supporting a few fixture files correctly now.

This is part of fixing issue #2354.


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index d46ef895..6c794cf0 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -8,35 +8,3 @@ updates:
     directory: '/java-wasm'
     schedule:
       interval: 'weekly'
-  - package-ecosystem: 'bundler'
-    directory: '/gemfiles/2.7'
-    schedule:
-      interval: 'weekly'
-  - package-ecosystem: 'bundler'
-    directory: '/gemfiles/3.0'
-    schedule:
-      interval: 'weekly'
-  - package-ecosystem: 'bundler'
-    directory: '/gemfiles/3.1'
-    schedule:
-      interval: 'weekly'
-  - package-ecosystem: 'bundler'
-    directory: '/gemfiles/3.2'
-    schedule:
-      interval: 'weekly'
-  - package-ecosystem: 'bundler'
-    directory: '/gemfiles/3.3'
-    schedule:
-      interval: 'weekly'
-  - package-ecosystem: 'bundler'
-    directory: '/gemfiles/3.4'
-    schedule:
-      interval: 'weekly'
-  - package-ecosystem: 'bundler'
-    directory: '/gemfiles/jruby'
-    schedule:
-      interval: 'weekly'
-  - package-ecosystem: 'bundler'
-    directory: '/gemfiles/truffleruby'
-    schedule:
-      interval: 'weekly'
diff --git a/.github/workflows/java-wasm-bindings.yml b/.github/workflows/java-wasm-bindings.yml
index 30e5f151..03994a3d 100644
--- a/.github/workflows/java-wasm-bindings.yml
+++ b/.github/workflows/java-wasm-bindings.yml
@@ -1,4 +1,4 @@
-name: Java WASM bindings
+name: Bindings to Chicory
 
 on:
   push:
diff --git a/.github/workflows/javascript-bindings.yml b/.github/workflows/javascript-bindings.yml
index 6bae0c9b..7897049a 100644
--- a/.github/workflows/javascript-bindings.yml
+++ b/.github/workflows/javascript-bindings.yml
@@ -1,4 +1,4 @@
-name: JavaScript bindings
+name: JavaScript Bindings
 
 on:
   push:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f3b5047d..722cf6de 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -18,7 +18,7 @@ jobs:
     - name: Set up Ruby
       uses: ruby/setup-ruby@v1
       with:
-        ruby-version: "3.3"
+        ruby-version: "3.2"
         bundler-cache: true
     - name: Lint
       run: bundle exec rake lint
@@ -45,7 +45,7 @@ jobs:
     - name: Set up Ruby
       uses: ruby/setup-ruby@v1
       with:
-        ruby-version: "3.3"
+        ruby-version: "3.2"
         bundler-cache: true
     - name: Check annotations
       run: |
@@ -73,20 +73,19 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        target:
-          - { ruby: "head", gemfile: "3.4" }
-          - { ruby: "jruby", gemfile: "jruby" }
-          - { ruby: "truffleruby", gemfile: "truffleruby" }
+        ruby: [ruby, jruby, truffleruby]
     runs-on: ubuntu-latest
     env:
       PRISM_FFI_BACKEND: "true"
-      BUNDLE_GEMFILE: gemfiles/${{ matrix.target.gemfile }}/Gemfile
     steps:
       - uses: actions/checkout@v4
+      # The Gemfile.lock is different with PRISM_FFI_BACKEND=true for CRuby
+      - run: rm Gemfile.lock
+        if: matrix.ruby == 'ruby'
       - name: Set up Ruby
         uses: ruby/setup-ruby@v1
         with:
-          ruby-version: ${{ matrix.target.ruby }}
+          ruby-version: ${{ matrix.ruby }}
           bundler-cache: true
       - name: Run Ruby tests
         run: bundle exec rake
@@ -236,40 +235,16 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        target:
-          - { ruby: "2.7", os: "ubuntu-latest", gemfile: "2.7" }
-          - { ruby: "3.0", os: "ubuntu-latest", gemfile: "3.0" }
-          - { ruby: "3.1", os: "ubuntu-latest", gemfile: "3.1" }
-          - { ruby: "3.2", os: "ubuntu-latest", gemfile: "3.2" }
-          - { ruby: "3.3", os: "ubuntu-latest", gemfile: "3.3" }
-          - { ruby: "head", os: "ubuntu-latest", gemfile: "3.4" }
-          - { ruby: "jruby", os: "ubuntu-latest", gemfile: "jruby" }
-          - { ruby: "truffleruby", os: "ubuntu-latest", gemfile: "truffleruby" }
-
-          - { ruby: "2.7", os: "macos-latest", gemfile: "2.7" }
-          - { ruby: "3.0", os: "macos-latest", gemfile: "3.0" }
-          - { ruby: "3.1", os: "macos-latest", gemfile: "3.1" }
-          - { ruby: "3.2", os: "macos-latest", gemfile: "3.2" }
-          - { ruby: "3.3", os: "macos-latest", gemfile: "3.3" }
-          - { ruby: "head", os: "macos-latest", gemfile: "3.4" }
-          - { ruby: "jruby", os: "macos-latest", gemfile: "jruby" }
-          - { ruby: "truffleruby", os: "macos-latest", gemfile: "truffleruby" }
-
-          - { ruby: "2.7", os: "windows-latest", gemfile: "2.7" }
-          - { ruby: "3.0", os: "windows-latest", gemfile: "3.0" }
-          - { ruby: "3.1", os: "windows-latest", gemfile: "3.1" }
-          - { ruby: "3.2", os: "windows-latest", gemfile: "3.2" }
-          - { ruby: "3.3", os: "windows-latest", gemfile: "3.3" }
-          - { ruby: "head", os: "windows-latest", gemfile: "3.4" }
-          - { ruby: "jruby", os: "windows-latest", gemfile: "jruby" }
-    env:
-      BUNDLE_GEMFILE: gemfiles/${{ matrix.target.gemfile }}/Gemfile
-    runs-on: ${{ matrix.target.os }}
+        ruby: ["3.0", "3.1", "3.2", "head", "jruby", "truffleruby"]
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        exclude:
+        - { ruby: truffleruby, os: windows-latest }
+    runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v4
     - uses: ruby/setup-ruby@v1
       with:
-        ruby-version: ${{ matrix.target.ruby }}
+        ruby-version: ${{ matrix.ruby }}
     - uses: actions/download-artifact@v4
       with:
         name: gem-package
diff --git a/.github/workflows/cruby-bindings.yml b/.github/workflows/ruby-core.yml
similarity index 98%
rename from .github/workflows/cruby-bindings.yml
rename to .github/workflows/ruby-core.yml
index 97093304..fdd2695b 100644
--- a/.github/workflows/cruby-bindings.yml
+++ b/.github/workflows/ruby-core.yml
@@ -1,4 +1,4 @@
-name: CRuby bindings
+name: Ruby Core
 
 concurrency:
   group: "${{github.workflow}}-${{github.ref}}"
diff --git a/.github/workflows/rust-bindings.yml b/.github/workflows/rust-bindings.yml
index 982767a0..446d9839 100644
--- a/.github/workflows/rust-bindings.yml
+++ b/.github/workflows/rust-bindings.yml
@@ -1,4 +1,5 @@
-name: Rust bindings
+---
+name: Rust Bindings
 
 on:
   push:
diff --git a/.github/workflows/truffleruby-bindings.yml b/.github/workflows/truffleruby.yml
similarity index 98%
rename from .github/workflows/truffleruby-bindings.yml
rename to .github/workflows/truffleruby.yml
index ce376ef2..45f421d2 100644
--- a/.github/workflows/truffleruby-bindings.yml
+++ b/.github/workflows/truffleruby.yml
@@ -1,4 +1,4 @@
-name: TruffleRuby bindings
+name: Test Loader.java with TruffleRuby
 
 concurrency:
   group: "${{github.workflow}}-${{github.ref}}"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff834b01..317cd1ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,21 +6,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
 
 ## [Unreleased]
 
-## [0.22.0] - 2024-02-07
-
-### Added
-
-- More support for `Prism::RipperCompat` is added.
-- Support for Ruby 2.7 has been added, and the minimum Ruby requirement has been lowered to 2.7.
-
-### Changed
-
-- The error for an invalid source encoding has a new `:argument` level to indicate it raises an argument error.
-- `BeginNode` nodes that are used when a class, singleton class, module, method definition, or block have an inline `rescue`/`ensure`/`else` now have their opening locations set to the beginning of the respective keyword.
-- Improved error messages for invalid characters.
-- `Prism.parse_file` and similar APIs will raise more appropriate errors when the file does not exist or cannot be mapped.
-- Correctly handle the `recover` parameter for `Prism::Translation::Parser`.
-
 ## [0.21.0] - 2024-02-05
 
 ### Added
@@ -356,8 +341,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
 
 - 🎉 Initial release! 🎉
 
-[unreleased]: https://github.com/ruby/prism/compare/v0.22.0...HEAD
-[0.22.0]: https://github.com/ruby/prism/compare/v0.21.0...v0.22.0
+[unreleased]: https://github.com/ruby/prism/compare/v0.21.0...HEAD
 [0.21.0]: https://github.com/ruby/prism/compare/v0.20.0...v0.21.0
 [0.20.0]: https://github.com/ruby/prism/compare/v0.19.0...v0.20.0
 [0.19.0]: https://github.com/ruby/prism/compare/v0.18.0...v0.19.0
diff --git a/Gemfile.lock b/Gemfile.lock
index bd4533c7..6f1c90ff 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,7 @@
 PATH
   remote: .
   specs:
-    prism (0.22.0)
+    prism (0.21.0)
 
 GEM
   remote: https://rubygems.org/
@@ -13,6 +13,8 @@ GEM
     nokogiri (1.16.2)
       mini_portile2 (~> 2.8.2)
       racc (~> 1.4)
+    nokogiri (1.16.2-java)
+      racc (~> 1.4)
     parser (3.3.0.5)
       ast (~> 2.4.1)
       racc
diff --git a/docs/releasing.md b/docs/releasing.md
index 89d7f7b3..ae6548b3 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -18,24 +18,6 @@ To release a new version of Prism, perform the following steps:
   * `templates/javascript/src/deserialize.js.erb` in the version constants
   * `templates/lib/prism/serialize.rb.erb` in the version constants
 * Run `bundle install` to update the `Gemfile.lock` file.
-
-```sh
-bundle install
-```
-
-* Run `bundle install` with the various other gemfiles:
-
-```sh
-chruby ruby-2.7.8 && BUNDLE_GEMFILE=gemfiles/2.7/Gemfile bundle install
-chruby ruby-3.0.6 && BUNDLE_GEMFILE=gemfiles/3.0/Gemfile bundle install
-chruby ruby-3.1.4 && BUNDLE_GEMFILE=gemfiles/3.1/Gemfile bundle install
-chruby ruby-3.2.3 && BUNDLE_GEMFILE=gemfiles/3.2/Gemfile bundle install
-chruby ruby-3.3.0 && BUNDLE_GEMFILE=gemfiles/3.3/Gemfile bundle install
-chruby ruby-3.4.0-dev && BUNDLE_GEMFILE=gemfiles/3.4/Gemfile bundle install
-chruby jruby-9.4.5.0 && BUNDLE_GEMFILE=gemfiles/jruby/Gemfile bundle install
-chruby truffleruby-23.1.2 && BUNDLE_GEMFILE=gemfiles/truffleruby/Gemfile bundle install
-```
-
 * Update `rust/ruby-prism-sys/Cargo.toml` to match the new version and run `cargo build`
 * Update `rust/ruby-prism/Cargo.toml` to match the new version and run `cargo build`
 * Commit all of the updated files.
diff --git a/ext/prism/extension.h b/ext/prism/extension.h
index ba97583e..8b64e6ef 100644
--- a/ext/prism/extension.h
+++ b/ext/prism/extension.h
@@ -1,7 +1,7 @@
 #ifndef PRISM_EXT_NODE_H
 #define PRISM_EXT_NODE_H
 
-#define EXPECTED_PRISM_VERSION "0.22.0"
+#define EXPECTED_PRISM_VERSION "0.21.0"
 
 #include <ruby.h>
 #include <ruby/encoding.h>
diff --git a/gemfiles/2.7/Gemfile b/gemfiles/2.7/Gemfile
deleted file mode 100644
index 813a0a48..00000000
--- a/gemfiles/2.7/Gemfile
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-ruby "~> 2.7.0"
-
-gemspec path: "../.."
-
-gem "ffi"
-gem "parser"
-gem "rake"
-gem "rake-compiler"
-gem "rbs"
-gem "test-unit"
diff --git a/gemfiles/2.7/Gemfile.lock b/gemfiles/2.7/Gemfile.lock
deleted file mode 100644
index 6ef0a001..00000000
--- a/gemfiles/2.7/Gemfile.lock
+++ /dev/null
@@ -1,39 +0,0 @@
-PATH
-  remote: ../..
-  specs:
-    prism (0.22.0)
-
-GEM
-  remote: https://rubygems.org/
-  specs:
-    ast (2.4.2)
-    ffi (1.16.3)
-    parser (3.3.0.5)
-      ast (~> 2.4.1)
-      racc
-    power_assert (2.0.3)
-    racc (1.7.3)
-    rake (13.1.0)
-    rake-compiler (1.2.7)
-      rake
-    rbs (3.1.3)
-    test-unit (3.6.1)
-      power_assert
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  ffi
-  parser
-  prism!
-  rake
-  rake-compiler
-  rbs
-  test-unit
-
-RUBY VERSION
-   ruby 2.7.8p225
-
-BUNDLED WITH
-   2.1.4
diff --git a/gemfiles/3.0/Gemfile b/gemfiles/3.0/Gemfile
deleted file mode 100644
index 6c5262c3..00000000
--- a/gemfiles/3.0/Gemfile
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-ruby "~> 3.0.0"
-
-gemspec path: "../.."
-
-gem "ffi"
-gem "parser"
-gem "rake"
-gem "rake-compiler"
-gem "rbs"
-gem "ruby_memcheck"
-gem "test-unit"
diff --git a/gemfiles/3.0/Gemfile.lock b/gemfiles/3.0/Gemfile.lock
deleted file mode 100644
index 4cfc76a0..00000000
--- a/gemfiles/3.0/Gemfile.lock
+++ /dev/null
@@ -1,48 +0,0 @@
-PATH
-  remote: ../..
-  specs:
-    prism (0.22.0)
-
-GEM
-  remote: https://rubygems.org/
-  specs:
-    abbrev (0.1.2)
-    ast (2.4.2)
-    ffi (1.16.3)
-    mini_portile2 (2.8.5)
-    nokogiri (1.16.2)
-      mini_portile2 (~> 2.8.2)
-      racc (~> 1.4)
-    parser (3.3.0.5)
-      ast (~> 2.4.1)
-      racc
-    power_assert (2.0.3)
-    racc (1.7.3)
-    rake (13.1.0)
-    rake-compiler (1.2.7)
-      rake
-    rbs (3.4.3)
-      abbrev
-    ruby_memcheck (2.3.0)
-      nokogiri
-    test-unit (3.6.1)
-      power_assert
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  ffi
-  parser
-  prism!
-  rake
-  rake-compiler
-  rbs
-  ruby_memcheck
-  test-unit
-
-RUBY VERSION
-   ruby 3.0.6p216
-
-BUNDLED WITH
-   2.2.33
diff --git a/gemfiles/3.1/Gemfile b/gemfiles/3.1/Gemfile
deleted file mode 100644
index e1807967..00000000
--- a/gemfiles/3.1/Gemfile
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-ruby "~> 3.1.0"
-
-gemspec path: "../.."
-
-gem "ffi"
-gem "parser"
-gem "rake"
-gem "rake-compiler"
-gem "rbs"
-gem "ruby_memcheck"
-gem "test-unit"
diff --git a/gemfiles/3.1/Gemfile.lock b/gemfiles/3.1/Gemfile.lock
deleted file mode 100644
index ab5b326a..00000000
--- a/gemfiles/3.1/Gemfile.lock
+++ /dev/null
@@ -1,48 +0,0 @@
-PATH
-  remote: ../..
-  specs:
-    prism (0.22.0)
-
-GEM
-  remote: https://rubygems.org/
-  specs:
-    abbrev (0.1.2)
-    ast (2.4.2)
-    ffi (1.16.3)
-    mini_portile2 (2.8.5)
-    nokogiri (1.16.2)
-      mini_portile2 (~> 2.8.2)
-      racc (~> 1.4)
-    parser (3.3.0.5)
-      ast (~> 2.4.1)
-      racc
-    power_assert (2.0.3)
-    racc (1.7.3)
-    rake (13.1.0)
-    rake-compiler (1.2.7)
-      rake
-    rbs (3.4.3)
-      abbrev
-    ruby_memcheck (2.3.0)
-      nokogiri
-    test-unit (3.6.1)
-      power_assert
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  ffi
-  parser
-  prism!
-  rake
-  rake-compiler
-  rbs
-  ruby_memcheck
-  test-unit
-
-RUBY VERSION
-   ruby 3.1.4p223
-
-BUNDLED WITH
-   2.3.26
diff --git a/gemfiles/3.2/Gemfile b/gemfiles/3.2/Gemfile
deleted file mode 100644
index c260051a..00000000
--- a/gemfiles/3.2/Gemfile
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-ruby "~> 3.2.0"
-
-gemspec path: "../.."
-
-gem "ffi"
-gem "parser"
-gem "rake"
-gem "rake-compiler"
-gem "rbs"
-gem "ruby_memcheck"
-gem "test-unit"
diff --git a/gemfiles/3.2/Gemfile.lock b/gemfiles/3.2/Gemfile.lock
deleted file mode 100644
index f5db1d45..00000000
--- a/gemfiles/3.2/Gemfile.lock
+++ /dev/null
@@ -1,48 +0,0 @@
-PATH
-  remote: ../..
-  specs:
-    prism (0.22.0)
-
-GEM
-  remote: https://rubygems.org/
-  specs:
-    abbrev (0.1.2)
-    ast (2.4.2)
-    ffi (1.16.3)
-    mini_portile2 (2.8.5)
-    nokogiri (1.16.2)
-      mini_portile2 (~> 2.8.2)
-      racc (~> 1.4)
-    parser (3.3.0.5)
-      ast (~> 2.4.1)
-      racc
-    power_assert (2.0.3)
-    racc (1.7.3)
-    rake (13.1.0)
-    rake-compiler (1.2.7)
-      rake
-    rbs (3.4.3)
-      abbrev
-    ruby_memcheck (2.3.0)
-      nokogiri
-    test-unit (3.6.1)
-      power_assert
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  ffi
-  parser
-  prism!
-  rake
-  rake-compiler
-  rbs
-  ruby_memcheck
-  test-unit
-
-RUBY VERSION
-   ruby 3.2.3p157
-
-BUNDLED WITH
-   2.4.19
diff --git a/gemfiles/3.3/Gemfile b/gemfiles/3.3/Gemfile
deleted file mode 100644
index 2c809a67..00000000
--- a/gemfiles/3.3/Gemfile
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-ruby "~> 3.3.0"
-
-gemspec path: "../.."
-
-gem "ffi"
-gem "parser"
-gem "rake"
-gem "rake-compiler"
-gem "rbs"
-gem "ruby_memcheck"
-gem "test-unit"
diff --git a/gemfiles/3.3/Gemfile.lock b/gemfiles/3.3/Gemfile.lock
deleted file mode 100644
index 967b01ab..00000000
--- a/gemfiles/3.3/Gemfile.lock
+++ /dev/null
@@ -1,48 +0,0 @@
-PATH
-  remote: ../..
-  specs:
-    prism (0.22.0)
-
-GEM
-  remote: https://rubygems.org/
-  specs:
-    abbrev (0.1.2)
-    ast (2.4.2)
-    ffi (1.16.3)
-    mini_portile2 (2.8.5)
-    nokogiri (1.16.2)
-      mini_portile2 (~> 2.8.2)
-      racc (~> 1.4)
-    parser (3.3.0.5)
-      ast (~> 2.4.1)
-      racc
-    power_assert (2.0.3)
-    racc (1.7.3)
-    rake (13.1.0)
-    rake-compiler (1.2.7)
-      rake
-    rbs (3.4.3)
-      abbrev
-    ruby_memcheck (2.3.0)
-      nokogiri
-    test-unit (3.6.1)
-      power_assert
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  ffi
-  parser
-  prism!
-  rake
-  rake-compiler
-  rbs
-  ruby_memcheck
-  test-unit
-
-RUBY VERSION
-   ruby 3.3.0p0
-
-BUNDLED WITH
-   2.5.3
diff --git a/gemfiles/3.4/Gemfile b/gemfiles/3.4/Gemfile
deleted file mode 100644
index e506db23..00000000
--- a/gemfiles/3.4/Gemfile
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-ruby "~> 3.4.0.dev"
-
-gemspec path: "../.."
-
-gem "ffi"
-gem "parser"
-gem "rake"
-gem "rake-compiler"
-gem "rbs"
-gem "ruby_memcheck"
-gem "test-unit"
diff --git a/gemfiles/3.4/Gemfile.lock b/gemfiles/3.4/Gemfile.lock
deleted file mode 100644
index ba3ef406..00000000
--- a/gemfiles/3.4/Gemfile.lock
+++ /dev/null
@@ -1,48 +0,0 @@
-PATH
-  remote: ../..
-  specs:
-    prism (0.22.0)
-
-GEM
-  remote: https://rubygems.org/
-  specs:
-    abbrev (0.1.2)
-    ast (2.4.2)
-    ffi (1.16.3)
-    mini_portile2 (2.8.5)
-    nokogiri (1.16.2)
-      mini_portile2 (~> 2.8.2)
-      racc (~> 1.4)
-    parser (3.3.0.5)
-      ast (~> 2.4.1)
-      racc
-    power_assert (2.0.3)
-    racc (1.7.3)
-    rake (13.1.0)
-    rake-compiler (1.2.7)
-      rake
-    rbs (3.4.3)
-      abbrev
-    ruby_memcheck (2.3.0)
-      nokogiri
-    test-unit (3.6.1)
-      power_assert
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  ffi
-  parser
-  prism!
-  rake
-  rake-compiler
-  rbs
-  ruby_memcheck
-  test-unit
-
-RUBY VERSION
-   ruby 3.4.0.dev
-
-BUNDLED WITH
-   2.6.0.dev
diff --git a/gemfiles/jruby/Gemfile b/gemfiles/jruby/Gemfile
deleted file mode 100644
index 6d2ca211..00000000
--- a/gemfiles/jruby/Gemfile
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-ruby "~> 3.1.4", engine: "jruby", engine_version: "~> 9.4.5"
-
-gemspec path: "../.."
-
-gem "parser"
-gem "rake"
-gem "rake-compiler"
-gem "test-unit"
diff --git a/gemfiles/jruby/Gemfile.lock b/gemfiles/jruby/Gemfile.lock
deleted file mode 100644
index db617819..00000000
--- a/gemfiles/jruby/Gemfile.lock
+++ /dev/null
@@ -1,36 +0,0 @@
-PATH
-  remote: ../..
-  specs:
-    prism (0.22.0)
-
-GEM
-  remote: https://rubygems.org/
-  specs:
-    ast (2.4.2)
-    parser (3.3.0.5)
-      ast (~> 2.4.1)
-      racc
-    power_assert (2.0.3)
-    racc (1.7.3-java)
-    rake (13.1.0)
-    rake-compiler (1.2.7)
-      rake
-    test-unit (3.6.1)
-      power_assert
-
-PLATFORMS
-  universal-java-11
-  universal-java-20
-
-DEPENDENCIES
-  parser
-  prism!
-  rake
-  rake-compiler
-  test-unit
-
-RUBY VERSION
-   ruby 3.1.4p0 (jruby 9.4.5.0)
-
-BUNDLED WITH
-   2.3.26
diff --git a/gemfiles/truffleruby/Gemfile b/gemfiles/truffleruby/Gemfile
deleted file mode 100644
index befd2d10..00000000
--- a/gemfiles/truffleruby/Gemfile
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-ruby "~> 3.2.2", engine: "truffleruby", engine_version: "~> 23.1.2"
-
-gemspec path: "../.."
-
-gem "parser"
-gem "rake"
-gem "rake-compiler"
-gem "test-unit"
diff --git a/gemfiles/truffleruby/Gemfile.lock b/gemfiles/truffleruby/Gemfile.lock
deleted file mode 100644
index 9ef13cc1..00000000
--- a/gemfiles/truffleruby/Gemfile.lock
+++ /dev/null
@@ -1,35 +0,0 @@
-PATH
-  remote: ../..
-  specs:
-    prism (0.22.0)
-
-GEM
-  remote: https://rubygems.org/
-  specs:
-    ast (2.4.2)
-    parser (3.3.0.5)
-      ast (~> 2.4.1)
-      racc
-    power_assert (2.0.3)
-    racc (1.7.3)
-    rake (13.1.0)
-    rake-compiler (1.2.7)
-      rake
-    test-unit (3.6.1)
-      power_assert
-
-PLATFORMS
-  ruby
-
-DEPENDENCIES
-  parser
-  prism!
-  rake
-  rake-compiler
-  test-unit
-
-RUBY VERSION
-   ruby 3.2.2p0 (truffleruby 23.1.2)
-
-BUNDLED WITH
-   2.2.32
diff --git a/include/prism/version.h b/include/prism/version.h
index f53ad4bb..9adf782d 100644
--- a/include/prism/version.h
+++ b/include/prism/version.h
@@ -14,7 +14,7 @@
 /**
  * The minor version of the Prism library as an int.
  */
-#define PRISM_VERSION_MINOR 22
+#define PRISM_VERSION_MINOR 21
 
 /**
  * The patch version of the Prism library as an int.
@@ -24,6 +24,6 @@
 /**
  * The version of the Prism library as a constant string.
  */
-#define PRISM_VERSION "0.22.0"
+#define PRISM_VERSION "0.21.0"
 
 #endif
diff --git a/javascript/package.json b/javascript/package.json
index 4c23ccc6..bad6f68c 100644
--- a/javascript/package.json
+++ b/javascript/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@ruby/prism",
-  "version": "0.22.0",
+  "version": "0.21.0",
   "description": "Prism Ruby parser",
   "type": "module",
   "main": "src/index.js",
diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb
index 0cc63cd9..2c239337 100644
--- a/lib/prism/lex_compat.rb
+++ b/lib/prism/lex_compat.rb
@@ -860,7 +860,7 @@ module Prism
       previous = []
       results = []
 
-      lex(source).each do |token|
+      Ripper.lex(source, raise_errors: true).each do |token|
         case token[1]
         when :on_sp
           # skip
@@ -886,21 +886,6 @@ module Prism
 
       results
     end
-
-    private
-
-    if Ripper.method(:lex).parameters.assoc(:keyrest)
-      def lex(source)
-        Ripper.lex(source, raise_errors: true)
-      end
-    else
-      def lex(source)
-        ripper = Ripper::Lexer.new(source)
-        ripper.lex.tap do |result|
-          raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any?
-        end
-      end
-    end
   end
 
   private_constant :LexRipper
diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb
index a27f30d4..b67fe061 100644
--- a/lib/prism/parse_result.rb
+++ b/lib/prism/parse_result.rb
@@ -91,8 +91,7 @@ module Prism
   class Location
     # A Source object that is used to determine more information from the given
     # offset and length.
-    attr_reader :source
-    protected :source
+    protected attr_reader :source
 
     # The byte offset from the beginning of the source where this location
     # starts.
diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb
index 912114cf..44983f85 100644
--- a/lib/prism/ripper_compat.rb
+++ b/lib/prism/ripper_compat.rb
@@ -116,32 +116,57 @@ module Prism
     end
 
     # Visit a CallNode node.
+    # Ripper distinguishes between many different method-call
+    # nodes -- unary and binary operators, "command" calls with
+    # no parentheses, and call/fcall/vcall.
     def visit_call_node(node)
       if node.variable_call?
-        if node.message.match?(/^[[:alpha:]_]/)
-          bounds(node.message_loc)
-          return on_vcall(on_ident(node.message))
-        end
+        raise NotImplementedError unless node.receiver.nil?
 
-        raise NotImplementedError, "Non-alpha variable call"
+        bounds(node.message_loc)
+        return on_vcall(on_ident(node.message))
       end
 
       if node.opening_loc.nil?
-        left = visit(node.receiver)
-        if node.arguments&.arguments&.length == 1
-          right = visit(node.arguments.arguments.first)
+        return visit_no_paren_call(node)
+      end
 
-          on_binary(left, node.name, right)
-        elsif !node.arguments || node.arguments.empty?
-          on_unary(node.name, left)
-        else
-          raise NotImplementedError, "More than two arguments for operator"
-        end
+      # A non-operator method call with parentheses
+      args = on_arg_paren(node.arguments.nil? ? nil : args_node_to_arguments(node.arguments))
+
+      bounds(node.message_loc)
+      ident_val = on_ident(node.message)
+
+      bounds(node.location)
+      args_call_val = on_method_add_arg(on_fcall(ident_val), args)
+      if node.block
+        block_val = visit(node.block)
+
+        return on_method_add_block(args_call_val, on_brace_block(nil, block_val))
       else
-        raise NotImplementedError, "Non-nil opening_loc"
+        return args_call_val
       end
     end
 
+    # Visit a BlockNode
+    def visit_block_node(node)
+      if node.body.nil?
+        on_stmts_add(on_stmts_new, on_void_stmt)
+      else
+        visit(node.body)
+      end
+    end
+
+    # Visit an AndNode
+    def visit_and_node(node)
+      visit_binary_operator(node)
+    end
+
+    # Visit an OrNode
+    def visit_or_node(node)
+      visit_binary_operator(node)
+    end
+
     # Visit a FloatNode node.
     def visit_float_node(node)
       visit_number(node) { |text| on_float(text) }
@@ -219,6 +244,72 @@ module Prism
 
     private
 
+    # Generate Ripper events for a CallNode with no opening_loc
+    def visit_no_paren_call(node)
+      # No opening_loc can mean an operator. It can also mean a
+      # method call with no parentheses.
+      if node.message.match?(/^[[:punct:]]/)
+        left = visit(node.receiver)
+        if node.arguments&.arguments&.length == 1
+          right = visit(node.arguments.arguments.first)
+
+          return on_binary(left, node.name, right)
+        elsif !node.arguments || node.arguments.empty?
+          return on_unary(node.name, left)
+        else
+          raise NotImplementedError, "More than two arguments for operator"
+        end
+      elsif node.call_operator_loc.nil?
+        # In Ripper a method call like "puts myvar" with no parenthesis is a "command".
+        bounds(node.message_loc)
+        ident_val = on_ident(node.message)
+
+        # Unless it has a block, and then it's an fcall (e.g. "foo { bar }")
+        if node.block
+          block_val = visit(node.block)
+          # In these calls, even if node.arguments is nil, we still get an :args_new call.
+          method_args_val = on_method_add_arg(on_fcall(ident_val), args_node_to_arguments(node.arguments))
+          return on_method_add_block(method_args_val, on_brace_block(nil, block_val))
+        else
+          args = node.arguments.nil? ? nil : args_node_to_arguments(node.arguments)
+          return on_command(ident_val, args)
+        end
+      else
+        operator = node.call_operator_loc.slice
+        if operator == "." || operator == "&."
+          left_val = visit(node.receiver)
+
+          bounds(node.call_operator_loc)
+          operator_val = operator == "." ? on_period(node.call_operator) : on_op(node.call_operator)
+
+          bounds(node.message_loc)
+          right_val = on_ident(node.message)
+
+          call_val = on_call(left_val, operator_val, right_val)
+
+          if node.block
+            block_val = visit(node.block)
+            return on_method_add_block(call_val, on_brace_block(nil, block_val))
+          else
+            return call_val
+          end
+        else
+          raise NotImplementedError, "operator other than . or &. for call: #{operator.inspect}"
+        end
+      end
+    end
+
+    # Ripper generates an interesting format of argument list.
+    # It seems to be very location-specific. We should get rid of
+    # this method and make it clearer how it's done in each place.
+    def args_node_to_arguments(args_node)
+      return on_args_new if args_node.nil?
+
+      args = visit_elements(args_node.arguments)
+
+      on_args_add_block(args, false)
+    end
+
     # Visit a list of elements, like the elements of an array or arguments.
     def visit_elements(elements)
       bounds(elements.first.location)
@@ -238,13 +329,32 @@ module Prism
         value = yield slice[1..-1]
 
         bounds(node.location)
-        on_unary(RUBY_ENGINE == "jruby" ? :- : :-@, value)
+        on_unary(visit_unary_operator(:-@), value)
       else
         bounds(location)
         yield slice
       end
     end
 
+    if RUBY_ENGINE == "jruby" && Gem::Version.new(JRUBY_VERSION) < Gem::Version.new("9.4.6.0")
+      # JRuby before 9.4.6.0 uses :- for unary minus instead of :-@
+      def visit_unary_operator(value)
+        value == :-@ ? :- : value
+      end
+    else
+      # For most Rubies and JRuby after 9.4.6.0 this is a no-op.
+      def visit_unary_operator(value)
+        value
+      end
+    end
+
+    # Visit a binary operator node like an AndNode or OrNode
+    def visit_binary_operator(node)
+      left_val = visit(node.left)
+      right_val = visit(node.right)
+      on_binary(left_val, node.operator.to_sym, right_val)
+    end
+
     # This method is responsible for updating lineno and column information
     # to reflect the current node.
     #
diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb
index 6723216d..60b62186 100644
--- a/lib/prism/translation/parser.rb
+++ b/lib/prism/translation/parser.rb
@@ -68,23 +68,17 @@ module Prism
 
       # Parses a source buffer and returns the AST, the source code comments,
       # and the tokens emitted by the lexer.
-      def tokenize(source_buffer, recover = false)
+      def tokenize(source_buffer, _recover = false)
         @source_buffer = source_buffer
         source = source_buffer.source
 
         offset_cache = build_offset_cache(source)
-        result =
-          begin
-            unwrap(Prism.parse_lex(source, filepath: source_buffer.name), offset_cache)
-          rescue ::Parser::SyntaxError
-            raise if !recover
-          end
+        result = unwrap(Prism.parse_lex(source, filepath: source_buffer.name), offset_cache)
 
         program, tokens = result.value
-        ast = build_ast(program, offset_cache) if result.success?
 
         [
-          ast,
+          build_ast(program, offset_cache),
           build_comments(result.comments, offset_cache),
           build_tokens(tokens, offset_cache)
         ]
diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb
index 264d85c2..45a51911 100644
--- a/lib/prism/translation/parser/compiler.rb
+++ b/lib/prism/translation/parser/compiler.rb
@@ -1062,22 +1062,12 @@ module Prism
 
         # foo in bar
         # ^^^^^^^^^^
-        if RUBY_VERSION >= "3.0"
-          def visit_match_predicate_node(node)
-            builder.match_pattern_p(
-              visit(node.value),
-              token(node.operator_loc),
-              within_pattern { |compiler| node.pattern.accept(compiler) }
-            )
-          end
-        else
-          def visit_match_predicate_node(node)
-            builder.match_pattern(
-              visit(node.value),
-              token(node.operator_loc),
-              within_pattern { |compiler| node.pattern.accept(compiler) }
-            )
-          end
+        def visit_match_predicate_node(node)
+          builder.match_pattern_p(
+            visit(node.value),
+            token(node.operator_loc),
+            within_pattern { |compiler| node.pattern.accept(compiler) }
+          )
         end
 
         # foo => bar
diff --git a/prism.gemspec b/prism.gemspec
index 0d0d548d..8754f4c9 100644
--- a/prism.gemspec
+++ b/prism.gemspec
@@ -2,7 +2,7 @@
 
 Gem::Specification.new do |spec|
   spec.name = "prism"
-  spec.version = "0.22.0"
+  spec.version = "0.21.0"
   spec.authors = ["Shopify"]
   spec.email = ["ruby@shopify.com"]
 
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
   spec.homepage = "https://github.com/ruby/prism"
   spec.license = "MIT"
 
-  spec.required_ruby_version = ">= 2.7.0"
+  spec.required_ruby_version = ">= 3.0.0"
 
   spec.require_paths = ["lib"]
   spec.files = [
diff --git a/rakelib/check_manifest.rake b/rakelib/check_manifest.rake
index 7607187b..57e6e419 100644
--- a/rakelib/check_manifest.rake
+++ b/rakelib/check_manifest.rake
@@ -15,7 +15,6 @@ task check_manifest: :templates do
     build
     doc
     fuzz
-    gemfiles
     javascript
     java
     java-wasm
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index d7a1ac8e..4614d7df 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -267,7 +267,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "ruby-prism"
-version = "0.22.0"
+version = "0.21.0"
 dependencies = [
  "ruby-prism-sys",
  "serde",
@@ -276,7 +276,7 @@ dependencies = [
 
 [[package]]
 name = "ruby-prism-sys"
-version = "0.22.0"
+version = "0.21.0"
 dependencies = [
  "bindgen",
  "cc",
diff --git a/rust/ruby-prism-sys/Cargo.toml b/rust/ruby-prism-sys/Cargo.toml
index 9cab043b..35c70bb1 100644
--- a/rust/ruby-prism-sys/Cargo.toml
+++ b/rust/ruby-prism-sys/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "ruby-prism-sys"
-version = "0.22.0"
+version = "0.21.0"
 edition = "2021"
 license-file = "../../LICENSE.md"
 repository = "https://github.com/ruby/prism"
diff --git a/rust/ruby-prism/Cargo.toml b/rust/ruby-prism/Cargo.toml
index 20d993d0..8c386373 100644
--- a/rust/ruby-prism/Cargo.toml
+++ b/rust/ruby-prism/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "ruby-prism"
-version = "0.22.0"
+version = "0.21.0"
 edition = "2021"
 license-file = "../../LICENSE.md"
 repository = "https://github.com/ruby/prism"
@@ -26,7 +26,7 @@ serde = { version = "1.0", features = ["derive"] }
 serde_yaml = "0.9"
 
 [dependencies]
-ruby-prism-sys = { version = "0.22.0", path = "../ruby-prism-sys" }
+ruby-prism-sys = { version = "0.21.0", path = "../ruby-prism-sys" }
 
 [features]
 default = ["vendored"]
diff --git a/templates/java/org/prism/Loader.java.erb b/templates/java/org/prism/Loader.java.erb
index f306b1a6..10580172 100644
--- a/templates/java/org/prism/Loader.java.erb
+++ b/templates/java/org/prism/Loader.java.erb
@@ -98,7 +98,7 @@ public class Loader {
         expect((byte) 'M', "incorrect prism header");
 
         expect((byte) 0, "prism version does not match");
-        expect((byte) 22, "prism version does not match");
+        expect((byte) 21, "prism version does not match");
         expect((byte) 0, "prism version does not match");
 
         expect((byte) 1, "Loader.java requires no location fields in the serialized output");
diff --git a/templates/javascript/src/deserialize.js.erb b/templates/javascript/src/deserialize.js.erb
index 2c979eff..cb0f41cd 100644
--- a/templates/javascript/src/deserialize.js.erb
+++ b/templates/javascript/src/deserialize.js.erb
@@ -1,7 +1,7 @@
 import * as nodes from "./nodes.js";
 
 const MAJOR_VERSION = 0;
-const MINOR_VERSION = 22;
+const MINOR_VERSION = 21;
 const PATCH_VERSION = 0;
 
 class SerializationBuffer {
diff --git a/templates/lib/prism/node.rb.erb b/templates/lib/prism/node.rb.erb
index b74d39c0..d7259c12 100644
--- a/templates/lib/prism/node.rb.erb
+++ b/templates/lib/prism/node.rb.erb
@@ -89,12 +89,11 @@ module Prism
     #<%= line %>
     <%- end -%>
     <%- end -%>
-    attr_reader :<%= field.name -%><%= "\n    private :#{field.name}" if field.is_a?(Prism::FlagsField) %>
+    <%= "private " if field.is_a?(Prism::FlagsField) %>attr_reader :<%= field.name %>
 
     <%- end -%>
     # def initialize: (<%= (node.fields.map { |field| "#{field.rbs_class} #{field.name}" } + ["Location location"]).join(", ") %>) -> void
     def initialize(<%= (node.fields.map(&:name) + ["location"]).join(", ") %>)
-      @newline = false
       <%- node.fields.each do |field| -%>
       @<%= field.name %> = <%= field.name %>
       <%- end -%>
diff --git a/templates/lib/prism/serialize.rb.erb b/templates/lib/prism/serialize.rb.erb
index 58ec6e85..205cbc45 100644
--- a/templates/lib/prism/serialize.rb.erb
+++ b/templates/lib/prism/serialize.rb.erb
@@ -20,7 +20,7 @@ module Prism
 
     # The minor version of prism that we are expecting to find in the serialized
     # strings.
-    MINOR_VERSION = 22
+    MINOR_VERSION = 21
 
     # The patch version of prism that we are expecting to find in the serialized
     # strings.


Test patch:
diff --git a/rakelib/test.rake b/rakelib/test.rake
index 9801d5c2..361f3961 100644
--- a/rakelib/test.rake
+++ b/rakelib/test.rake
@@ -17,6 +17,7 @@ return if RUBY_ENGINE == "jruby" || RUBY_ENGINE == "truffleruby"
 # Don't bother trying to configure memcheck on old versions of Ruby.
 return if RUBY_VERSION < "3.0"
 
+
 begin
   require "ruby_memcheck"
   have_memcheck = true
diff --git a/rust/ruby-prism-sys/tests/utils_tests.rs b/rust/ruby-prism-sys/tests/utils_tests.rs
index 2b15303d..929d4e5d 100644
--- a/rust/ruby-prism-sys/tests/utils_tests.rs
+++ b/rust/ruby-prism-sys/tests/utils_tests.rs
@@ -12,7 +12,7 @@ fn version_test() {
         CStr::from_ptr(version)
     };
 
-    assert_eq!(&cstring.to_string_lossy(), "0.22.0");
+    assert_eq!(&cstring.to_string_lossy(), "0.21.0");
 }
 
 #[test]
diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb
index f7231b6a..ea5ecb7c 100644
--- a/test/prism/errors_test.rb
+++ b/test/prism/errors_test.rb
@@ -588,45 +588,43 @@ module Prism
       ]
     end
 
-    if RUBY_VERSION >= "3.0"
-      def test_cannot_assign_to_a_reserved_numbered_parameter
-        expected = BeginNode(
-          Location(),
-          StatementsNode([
-            LocalVariableWriteNode(:_1, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
-            LocalVariableWriteNode(:_2, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
-            LocalVariableWriteNode(:_3, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
-            LocalVariableWriteNode(:_4, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
-            LocalVariableWriteNode(:_5, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
-            LocalVariableWriteNode(:_6, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
-            LocalVariableWriteNode(:_7, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
-            LocalVariableWriteNode(:_8, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
-            LocalVariableWriteNode(:_9, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
-            LocalVariableWriteNode(:_10, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location())
-          ]),
-          nil,
-          nil,
-          nil,
-          Location()
-        )
-        source = <<~RUBY
-        begin
-          _1=:a;_2=:a;_3=:a;_4=:a;_5=:a
-          _6=:a;_7=:a;_8=:a;_9=:a;_10=:a
-        end
-        RUBY
-        assert_errors expected, source, [
-          ["_1 is reserved for numbered parameters", 8..10],
-          ["_2 is reserved for numbered parameters", 14..16],
-          ["_3 is reserved for numbered parameters", 20..22],
-          ["_4 is reserved for numbered parameters", 26..28],
-          ["_5 is reserved for numbered parameters", 32..34],
-          ["_6 is reserved for numbered parameters", 40..42],
-          ["_7 is reserved for numbered parameters", 46..48],
-          ["_8 is reserved for numbered parameters", 52..54],
-          ["_9 is reserved for numbered parameters", 58..60],
-        ]
+    def test_cannot_assign_to_a_reserved_numbered_parameter
+      expected = BeginNode(
+        Location(),
+        StatementsNode([
+          LocalVariableWriteNode(:_1, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
+          LocalVariableWriteNode(:_2, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
+          LocalVariableWriteNode(:_3, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
+          LocalVariableWriteNode(:_4, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
+          LocalVariableWriteNode(:_5, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
+          LocalVariableWriteNode(:_6, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
+          LocalVariableWriteNode(:_7, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
+          LocalVariableWriteNode(:_8, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
+          LocalVariableWriteNode(:_9, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location()),
+          LocalVariableWriteNode(:_10, 0, Location(), SymbolNode(SymbolFlags::FORCED_US_ASCII_ENCODING, Location(), Location(), nil, "a"), Location())
+        ]),
+        nil,
+        nil,
+        nil,
+        Location()
+      )
+      source = <<~RUBY
+      begin
+        _1=:a;_2=:a;_3=:a;_4=:a;_5=:a
+        _6=:a;_7=:a;_8=:a;_9=:a;_10=:a
       end
+      RUBY
+      assert_errors expected, source, [
+        ["_1 is reserved for numbered parameters", 8..10],
+        ["_2 is reserved for numbered parameters", 14..16],
+        ["_3 is reserved for numbered parameters", 20..22],
+        ["_4 is reserved for numbered parameters", 26..28],
+        ["_5 is reserved for numbered parameters", 32..34],
+        ["_6 is reserved for numbered parameters", 40..42],
+        ["_7 is reserved for numbered parameters", 46..48],
+        ["_8 is reserved for numbered parameters", 52..54],
+        ["_9 is reserved for numbered parameters", 58..60],
+      ]
     end
 
     def test_do_not_allow_trailing_commas_in_method_parameters
@@ -1346,25 +1344,23 @@ module Prism
       ]
     end
 
-    if RUBY_VERSION >= "3.0"
-      def test_writing_numbered_parameter
-        assert_errors expression("-> { _1 = 0 }"), "-> { _1 = 0 }", [
-          ["_1 is reserved for numbered parameters", 5..7]
-        ]
-      end
+    def test_writing_numbered_parameter
+      assert_errors expression("-> { _1 = 0 }"), "-> { _1 = 0 }", [
+        ["_1 is reserved for numbered parameters", 5..7]
+      ]
+    end
 
-      def test_targeting_numbered_parameter
-        assert_errors expression("-> { _1, = 0 }"), "-> { _1, = 0 }", [
-          ["_1 is reserved for numbered parameters", 5..7]
-        ]
-      end
+    def test_targeting_numbered_parameter
+      assert_errors expression("-> { _1, = 0 }"), "-> { _1, = 0 }", [
+        ["_1 is reserved for numbered parameters", 5..7]
+      ]
+    end
 
-      def test_defining_numbered_parameter
-        error_messages = ["_1 is reserved for numbered parameters"]
+    def test_defining_numbered_parameter
+      error_messages = ["_1 is reserved for numbered parameters"]
 
-        assert_error_messages "def _1; end", error_messages
-        assert_error_messages "def self._1; end", error_messages
-      end
+      assert_error_messages "def _1; end", error_messages
+      assert_error_messages "def self._1; end", error_messages
     end
 
     def test_double_scope_numbered_parameters
@@ -1413,13 +1409,11 @@ module Prism
       ]
     end
 
-    if RUBY_VERSION >= "3.0"
-      def test_numbered_parameters_in_block_arguments
-        source = "foo { |_1| }"
-        assert_errors expression(source), source, [
-          ["_1 is reserved for numbered parameters", 7..9],
-        ]
-      end
+    def test_numbered_parameters_in_block_arguments
+      source = "foo { |_1| }"
+      assert_errors expression(source), source, [
+        ["_1 is reserved for numbered parameters", 7..9],
+      ]
     end
 
     def test_conditional_predicate_closed
diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_compat_test.rb
index 8ca8545a..1aaade04 100644
--- a/test/prism/ripper_compat_test.rb
+++ b/test/prism/ripper_compat_test.rb
@@ -24,8 +24,44 @@ module Prism
       assert_equivalent("(3 + 7) * 4")
     end
 
-    def test_ident
+    def test_method_calls_with_variable_names
       assert_equivalent("foo")
+      assert_equivalent("foo()")
+      assert_equivalent("foo(-7)")
+      assert_equivalent("foo(1, 2, 3)")
+      assert_equivalent("foo 1")
+      assert_equivalent("foo bar")
+      assert_equivalent("foo 1, 2")
+      assert_equivalent("foo.bar")
+
+      # TruffleRuby prints emoji symbols differently in a way that breaks here.
+      if RUBY_ENGINE != "truffleruby"
+        assert_equivalent("🗻")
+        assert_equivalent("🗻.location")
+        assert_equivalent("foo.🗻")
+        assert_equivalent("🗻.😮!")
+        assert_equivalent("🗻 🗻,🗻,🗻")
+      end
+
+      assert_equivalent("foo&.bar")
+      assert_equivalent("foo { bar }")
+      assert_equivalent("foo.bar { 7 }")
+      assert_equivalent("foo(1) { bar }")
+      assert_equivalent("foo(bar)")
+      assert_equivalent("foo(bar(1))")
+      assert_equivalent("foo bar(1)")
+      # assert_equivalent("foo(bar 1)") # This succeeds for me locally but fails on CI
+      # assert_equivalent("foo bar 1")
+    end
+
+    def test_method_calls_on_immediate_values
+      assert_equivalent("7.even?")
+      assert_equivalent("!1")
+      assert_equivalent("7 && 7")
+      assert_equivalent("7 and 7")
+      assert_equivalent("7 || 7")
+      assert_equivalent("7 or 7")
+      #assert_equivalent("'racecar'.reverse")
     end
 
     def test_range
@@ -58,4 +94,32 @@ module Prism
       assert_equal expected, RipperCompat.sexp_raw(source)
     end
   end
+
+  class RipperCompatFixturesTest < TestCase
+    #base = File.join(__dir__, "fixtures")
+    #relatives = ENV["FOCUS"] ? [ENV["FOCUS"]] : Dir["**/*.txt", base: base]
+    relatives = [
+      "arithmetic.txt",
+      "comments.txt",
+      "integer_operations.txt",
+    ]
+
+    relatives.each do |relative|
+      define_method "test_ripper_filepath_#{relative}" do
+        path = File.join(__dir__, "fixtures", relative)
+
+        # First, read the source from the path. Use binmode to avoid converting CRLF on Windows,
+        # and explicitly set the external encoding to UTF-8 to override the binmode default.
+        source = File.read(path, binmode: true, external_encoding: Encoding::UTF_8)
+
+        expected = Ripper.sexp_raw(source)
+        if expected.nil?
+          puts "Could not parse #{path.inspect}!"
+        end
+        refute_nil expected
+        assert_equal expected, RipperCompat.sexp_raw(source)
+      end
+    end
+
+  end
 end
diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb
index 28b0725d..a16de14c 100644
--- a/test/prism/test_helper.rb
+++ b/test/prism/test_helper.rb
@@ -43,18 +43,15 @@ module Prism
           )
         end
       when SourceFileNode
-        expected_deconstruct = expected.deconstruct_keys(nil)
-        actual_deconstruct = actual.deconstruct_keys(nil)
-        assert_equal expected_deconstruct.keys, actual_deconstruct.keys
+        deconstructed_expected = expected.deconstruct_keys(nil)
+        deconstructed_actual = actual.deconstruct_keys(nil)
+        assert_equal deconstructed_expected.keys, deconstructed_actual.keys
 
         # Filepaths can be different if test suites were run on different
         # machines. We accommodate for this by comparing the basenames, and not
         # the absolute filepaths.
-        expected_filepath = expected_deconstruct.delete(:filepath)
-        actual_filepath = actual_deconstruct.delete(:filepath)
-
-        assert_equal expected_deconstruct, actual_deconstruct
-        assert_equal File.basename(expected_filepath), File.basename(actual_filepath)
+        assert_equal deconstructed_expected.except(:filepath), deconstructed_actual.except(:filepath)
+        assert_equal File.basename(deconstructed_expected[:filepath]), File.basename(deconstructed_actual[:filepath])
       when Node
         deconstructed_expected = expected.deconstruct_keys(nil)
         deconstructed_actual = actual.deconstruct_keys(nil)