From fb75e99efea67ad3c2f802817345f749ffaf202c Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 18 Jan 2026 22:48:42 +0900 Subject: [PATCH 1/7] [ruby/rubygems] Add missing `.binmode` for `Gem::AtomicFileWriter` In https://github.com/ruby/rubygems/pull/9202, I commented that `temp_file.binmode` is redundant. But I was wrong. We need `temp_file.binmode` even when we specify `File::BINARY`. Sorry. https://github.com/ruby/rubygems/commit/d9f1f5c6a6 --- lib/rubygems/util/atomic_file_writer.rb | 1 + test/rubygems/test_gem_util_atomic_file_writer.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 test/rubygems/test_gem_util_atomic_file_writer.rb diff --git a/lib/rubygems/util/atomic_file_writer.rb b/lib/rubygems/util/atomic_file_writer.rb index ea592407eb015c..32767c6a79081d 100644 --- a/lib/rubygems/util/atomic_file_writer.rb +++ b/lib/rubygems/util/atomic_file_writer.rb @@ -30,6 +30,7 @@ def self.open(file_name) flags |= File::SHARE_DELETE if defined?(File::SHARE_DELETE) File.open(tmp_path, flags) do |temp_file| + temp_file.binmode if old_stat # Set correct permissions on new file begin diff --git a/test/rubygems/test_gem_util_atomic_file_writer.rb b/test/rubygems/test_gem_util_atomic_file_writer.rb new file mode 100644 index 00000000000000..e011a38ad42a04 --- /dev/null +++ b/test/rubygems/test_gem_util_atomic_file_writer.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require_relative "helper" +require "rubygems/util/atomic_file_writer" + +class TestGemUtilAtomicFileWriter < Gem::TestCase + def test_external_encoding + Gem::AtomicFileWriter.open(File.join(@tempdir, "test.txt")) do |file| + assert_equal(Encoding::ASCII_8BIT, file.external_encoding) + end + end +end From df479f41f81709daf00dd034e6ca3e1e88d2259d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 19 Jan 2026 15:27:20 +0900 Subject: [PATCH 2/7] [ruby/rubygems] Added another usage of pristine command ref. https://github.com/rubygems/guides/issues/388 https://github.com/ruby/rubygems/commit/43f3768f09 --- lib/rubygems/commands/pristine_command.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 93503d2b69915d..942b75fba172d3 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -88,6 +88,10 @@ def description # :nodoc: will revert them. All extensions are rebuilt and all bin stubs for the gem are regenerated after checking for modifications. +Rebuilding extensions also refreshes C-extension gems against updated system +libraries (for example after OS or package upgrades) to avoid mismatches like +outdated library version warnings. + If the cached gem cannot be found it will be downloaded. If --no-extensions is provided pristine will not attempt to restore a gem From 3c9ca23ba4957ce3efc6f1235b78922304f0feae Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 19 Jan 2026 08:32:54 +0100 Subject: [PATCH 3/7] [ruby/prism] Remove duplicate lex tests `RipperTest` already does this (added in https://github.com/ruby/prism/pull/3849) Since it doesn't use the token classes, it also lists out all the excludes instead of just claiming some are passing. https://github.com/ruby/prism/commit/e86a28263c --- test/prism/lex_test.rb | 52 ++++-------------------------------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index 68e47a096408cd..ea4606d2fb6251 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -6,43 +6,6 @@ module Prism class LexTest < TestCase - except = [ - # https://bugs.ruby-lang.org/issues/21756 - "spanning_heredoc.txt", - # Prism emits a single string in some cases when ripper splits them up - "whitequark/dedenting_heredoc.txt", - "heredocs_with_fake_newlines.txt", - # Prism emits BEG for `on_regexp_end` - "spanning_heredoc_newlines.txt", - ] - - if RUBY_VERSION < "3.3.0" - # This file has changed behavior in Ripper in Ruby 3.3, so we skip it if - # we're on an earlier version. - except << "seattlerb/pct_w_heredoc_interp_nested.txt" - - # Ruby < 3.3.0 cannot parse heredocs where there are leading whitespace - # characters in the heredoc start. - # Example: <<~' EOF' or <<-' EOF' - # https://bugs.ruby-lang.org/issues/19539 - except << "heredocs_leading_whitespace.txt" - except << "whitequark/ruby_bug_19539.txt" - - # https://bugs.ruby-lang.org/issues/19025 - except << "whitequark/numparam_ruby_bug_19025.txt" - # https://bugs.ruby-lang.org/issues/18878 - except << "whitequark/ruby_bug_18878.txt" - # https://bugs.ruby-lang.org/issues/19281 - except << "whitequark/ruby_bug_19281.txt" - end - - # https://bugs.ruby-lang.org/issues/21168#note-5 - except << "command_method_call_2.txt" - - Fixture.each_for_current_ruby(except: except) do |fixture| - define_method(fixture.test_name) { assert_lex(fixture) } - end - def test_lex_file assert_nothing_raised do Prism.lex_file(__FILE__) @@ -83,16 +46,11 @@ def test_parse_lex_file end end - private - - def assert_lex(fixture) - source = fixture.read - - result = Prism.lex_compat(source, version: "current") - assert_equal [], result.errors - - Prism.lex_ripper(source).zip(result.value).each do |(ripper, prism)| - assert_equal ripper, prism + if RUBY_VERSION >= "3.3" + def test_lex_compare + prism = Prism.lex_compat(File.read(__FILE__), version: "current").value + ripper = Prism.lex_ripper(File.read(__FILE__)) + assert_equal(ripper, prism) end end end From aa31754e560dc39faba3cb39e6b7bc0871a4131e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 02:08:31 +0000 Subject: [PATCH 4/7] Bump actions/cache in /.github/actions/setup/directories Bumps [actions/cache](https://github.com/actions/cache) from 5.0.1 to 5.0.2. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/9255dc7a253b0ccc959486e2bca901246202afeb...8b402f58fbc84540c8b491a91e594a4576fec3d7) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/actions/setup/directories/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 1f0463c9ca021f..0e8ffd59eff14e 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -100,7 +100,7 @@ runs: path: ${{ inputs.srcdir }} fetch-depth: ${{ inputs.fetch-depth }} - - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + - uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ${{ inputs.srcdir }}/.downloaded-cache key: ${{ runner.os }}-${{ runner.arch }}-downloaded-cache From e85fd7c8783b51e6a1fb274a4b71e41f448afac9 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 14 Dec 2025 15:07:21 +0100 Subject: [PATCH 5/7] [ruby/prism] Fix docs of opening_loc/closing_loc of BlockNode https://github.com/ruby/prism/commit/0b22d9060a --- prism/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/prism/config.yml b/prism/config.yml index 5e29d6fa181c34..4e5b077a351ff1 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -1269,17 +1269,17 @@ nodes: - name: opening_loc type: location comment: | - Represents the location of the opening `|`. + Represents the location of the opening `{` or `do`. [1, 2, 3].each { |i| puts x } - ^ + ^ - name: closing_loc type: location comment: | - Represents the location of the closing `|`. + Represents the location of the closing `}` or `end`. [1, 2, 3].each { |i| puts x } - ^ + ^ comment: | Represents a block of ruby code. From ae5efb55d1bd52e3a08c9ffb6ab4e0cef74cef12 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 14 Dec 2025 21:36:45 +0100 Subject: [PATCH 6/7] [ruby/prism] Simplify and optimize Prism::Node#tunnel * By comparing byte offsets which folds 3 branches into 1. * Also avoids allocation of Location objects. https://github.com/ruby/prism/commit/71fcb891e0 --- prism/templates/lib/prism/node.rb.erb | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index 066a0cea1b508f..181842e230b30a 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -184,24 +184,14 @@ module Prism queue = [self] #: Array[Prism::node] result = [] #: Array[Prism::node] + line_offset = source.offsets[line - 1] or raise + search_offset = line_offset + column + while (node = queue.shift) result << node node.each_child_node do |child_node| - child_location = child_node.location - - start_line = child_location.start_line - end_line = child_location.end_line - - if start_line == end_line - if line == start_line && column >= child_location.start_column && column < child_location.end_column - queue << child_node - break - end - elsif (line == start_line && column >= child_location.start_column) || (line == end_line && column < child_location.end_column) - queue << child_node - break - elsif line > start_line && line < end_line + if child_node.start_offset <= search_offset && search_offset < child_node.end_offset queue << child_node break end From 859920dfd272bc22670d779129a04cdf07632192 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 15 Dec 2025 10:46:24 +0100 Subject: [PATCH 7/7] [ruby/prism] Add Prism::Source#line_to_byte_offset and replace direct accesses to offsets https://github.com/ruby/prism/commit/ff81a29ba5 --- lib/prism/parse_result.rb | 9 +++++ prism/templates/lib/prism/node.rb.erb | 3 +- test/prism/ruby/source_test.rb | 47 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 test/prism/ruby/source_test.rb diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index 3570af136a4fb7..12d19da5629a76 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -76,6 +76,15 @@ def slice(byte_offset, length) source.byteslice(byte_offset, length) or raise end + # Converts the line number to a byte offset corresponding to the start of that line + def line_to_byte_offset(line) + l = line - @start_line + if l < 0 || l >= offsets.size + raise ArgumentError, "line #{line} is out of range" + end + offsets[l] + end + # Binary search through the offsets to find the line number for the given # byte offset. def line(byte_offset) diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index 181842e230b30a..8225bfb328e8da 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -184,8 +184,7 @@ module Prism queue = [self] #: Array[Prism::node] result = [] #: Array[Prism::node] - line_offset = source.offsets[line - 1] or raise - search_offset = line_offset + column + search_offset = source.line_to_byte_offset(line) + column while (node = queue.shift) result << node diff --git a/test/prism/ruby/source_test.rb b/test/prism/ruby/source_test.rb new file mode 100644 index 00000000000000..afd2825765f7e0 --- /dev/null +++ b/test/prism/ruby/source_test.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Prism + class SourceTest < TestCase + def test_line_to_byte_offset + parse_result = Prism.parse(<<~SRC) + abcd + efgh + ijkl + SRC + source = parse_result.source + + assert_equal 0, source.line_to_byte_offset(1) + assert_equal 5, source.line_to_byte_offset(2) + assert_equal 10, source.line_to_byte_offset(3) + assert_equal 15, source.line_to_byte_offset(4) + e = assert_raise(ArgumentError) { source.line_to_byte_offset(5) } + assert_equal "line 5 is out of range", e.message + e = assert_raise(ArgumentError) { source.line_to_byte_offset(0) } + assert_equal "line 0 is out of range", e.message + e = assert_raise(ArgumentError) { source.line_to_byte_offset(-1) } + assert_equal "line -1 is out of range", e.message + end + + def test_line_to_byte_offset_with_start_line + parse_result = Prism.parse(<<~SRC, line: 11) + abcd + efgh + ijkl + SRC + source = parse_result.source + + assert_equal 0, source.line_to_byte_offset(11) + assert_equal 5, source.line_to_byte_offset(12) + assert_equal 10, source.line_to_byte_offset(13) + assert_equal 15, source.line_to_byte_offset(14) + e = assert_raise(ArgumentError) { source.line_to_byte_offset(15) } + assert_equal "line 15 is out of range", e.message + e = assert_raise(ArgumentError) { source.line_to_byte_offset(10) } + assert_equal "line 10 is out of range", e.message + e = assert_raise(ArgumentError) { source.line_to_byte_offset(9) } + assert_equal "line 9 is out of range", e.message + end + end +end