From 3f6014dc5315d3f120de38ca495ac30675556b55 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 16 Mar 2026 20:13:39 +0100 Subject: [PATCH] Ensure Source#offsets is set correctly in all cases * See https://github.com/ruby/prism/issues/3861 --- lib/prism/parse_result.rb | 3 ++- rbi/generated/prism/dsl.rbi | 6 +++++- rbi/generated/prism/parse_result.rbi | 3 ++- sig/generated/prism/dsl.rbs | 9 ++++++++- sig/generated/prism/parse_result.rbs | 3 ++- templates/lib/prism/dsl.rb.erb | 20 +++++++++++++++++--- 6 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index 4d1fa2c296..4f7bcf07d6 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -26,7 +26,8 @@ class Source # source is a subset of a larger source or if this is an eval. offsets is an # array of byte offsets for the start of each line in the source code, which # can be calculated by iterating through the source code and recording the - # byte offset whenever a newline character is encountered. + # byte offset whenever a newline character is encountered. The first + # element is always 0 to mark the first line. #-- #: (String source, Integer start_line, Array[Integer] offsets) -> Source def self.for(source, start_line, offsets) diff --git a/rbi/generated/prism/dsl.rbi b/rbi/generated/prism/dsl.rbi index ac4e68bfa7..148d4724de 100644 --- a/rbi/generated/prism/dsl.rbi +++ b/rbi/generated/prism/dsl.rbi @@ -4,7 +4,7 @@ module Prism # The DSL module provides a set of methods that can be used to create prism # nodes in a more concise manner. For example, instead of writing: # - # source = Prism::Source.for("[1]", 1, []) + # source = Prism::Source.for("[1]", 1, [0]) # # Prism::ArrayNode.new( # source, @@ -743,5 +743,9 @@ module Prism # required node field. sig { params(source: Source, location: Location).returns(Node) } private def default_node(source, location); end + + # Build the newline byte offset array for the given source string. + sig { params(source: String).returns(T::Array[Integer]) } + private def build_offsets(source); end end end diff --git a/rbi/generated/prism/parse_result.rbi b/rbi/generated/prism/parse_result.rbi index f20ba90ef5..4d065b5be1 100644 --- a/rbi/generated/prism/parse_result.rbi +++ b/rbi/generated/prism/parse_result.rbi @@ -16,7 +16,8 @@ module Prism # source is a subset of a larger source or if this is an eval. offsets is an # array of byte offsets for the start of each line in the source code, which # can be calculated by iterating through the source code and recording the - # byte offset whenever a newline character is encountered. + # byte offset whenever a newline character is encountered. The first + # element is always 0 to mark the first line. sig { params(source: String, start_line: Integer, offsets: T::Array[Integer]).returns(Source) } def self.for(source, start_line, offsets); end diff --git a/sig/generated/prism/dsl.rbs b/sig/generated/prism/dsl.rbs index 4b09efccd3..fc0c198705 100644 --- a/sig/generated/prism/dsl.rbs +++ b/sig/generated/prism/dsl.rbs @@ -4,7 +4,7 @@ module Prism # The DSL module provides a set of methods that can be used to create prism # nodes in a more concise manner. For example, instead of writing: # - # source = Prism::Source.for("[1]", 1, []) + # source = Prism::Source.for("[1]", 1, [0]) # # Prism::ArrayNode.new( # source, @@ -917,5 +917,12 @@ module Prism # -- # : (Source source, Location location) -> node def default_node: (Source source, Location location) -> node + + private + + # Build the newline byte offset array for the given source string. + # -- + # : (String source) -> Array[Integer] + def build_offsets: (String source) -> Array[Integer] end end diff --git a/sig/generated/prism/parse_result.rbs b/sig/generated/prism/parse_result.rbs index 1f3b8a8d54..f005f17375 100644 --- a/sig/generated/prism/parse_result.rbs +++ b/sig/generated/prism/parse_result.rbs @@ -22,7 +22,8 @@ module Prism # source is a subset of a larger source or if this is an eval. offsets is an # array of byte offsets for the start of each line in the source code, which # can be calculated by iterating through the source code and recording the - # byte offset whenever a newline character is encountered. + # byte offset whenever a newline character is encountered. The first + # element is always 0 to mark the first line. # -- # : (String source, Integer start_line, Array[Integer] offsets) -> Source def self.for: (String source, Integer start_line, Array[Integer] offsets) -> Source diff --git a/templates/lib/prism/dsl.rb.erb b/templates/lib/prism/dsl.rb.erb index 6dcbbec100..95c4dac71a 100644 --- a/templates/lib/prism/dsl.rb.erb +++ b/templates/lib/prism/dsl.rb.erb @@ -5,7 +5,7 @@ module Prism # The DSL module provides a set of methods that can be used to create prism # nodes in a more concise manner. For example, instead of writing: # - # source = Prism::Source.for("[1]", 1, []) + # source = Prism::Source.for("[1]", 1, [0]) # # Prism::ArrayNode.new( # source, @@ -62,7 +62,7 @@ module Prism #-- #: (String string) -> Source def source(string) - Source.for(string, 1, []) + Source.for(string, 1, build_offsets(string)) end # Create a new Location object. @@ -136,7 +136,7 @@ module Prism #-- #: () -> Source def default_source - Source.for("", 1, []) + Source.for("", 1, [0]) end # The default location object that gets attached to nodes if no location is @@ -154,5 +154,19 @@ module Prism def default_node(source, location) MissingNode.new(source, -1, location, 0) end + + private + + # Build the newline byte offset array for the given source string. + #-- + #: (String source) -> Array[Integer] + def build_offsets(source) + offsets = [0] + start = 0 + while (index = source.byteindex("\n", start)) + offsets << (start = index + 1) + end + offsets + end end end