Skip to content

Commit 9c5cd20

Browse files
Earlopainkddnewton
authored andcommitted
Add support for Prism.parse(foo, version: "current")
The docs currently say to use `Prism.parse(foo, version: RUBY_VERSION)` for this. By specifying "current" instead, we can have prism raise a more specifc error. Note: Does not use `ruby_version` from `ruby/version.h` because writing a test for that is not really possible. `RUBY_VERSION` is nicely stubbable for both the c-ext and FFI backend.
1 parent 8e88590 commit 9c5cd20

File tree

5 files changed

+74
-4
lines changed

5 files changed

+74
-4
lines changed

ext/prism/extension.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ VALUE rb_cPrismLexResult;
2525
VALUE rb_cPrismParseLexResult;
2626
VALUE rb_cPrismStringQuery;
2727
VALUE rb_cPrismScope;
28+
VALUE rb_cPrismCurrentVersionError;
2829

2930
VALUE rb_cPrismDebugEncoding;
3031

@@ -199,7 +200,13 @@ build_options_i(VALUE key, VALUE value, VALUE argument) {
199200
if (!NIL_P(value)) {
200201
const char *version = check_string(value);
201202

202-
if (!pm_options_version_set(options, version, RSTRING_LEN(value))) {
203+
if (RSTRING_LEN(value) == 7 && strncmp(version, "current", 7) == 0) {
204+
VALUE current_ruby_value = rb_const_get(rb_cObject, rb_intern("RUBY_VERSION"));
205+
const char *current_version = RSTRING_PTR(current_ruby_value);
206+
if (!pm_options_version_set(options, current_version, 3)) {
207+
rb_exc_raise(rb_exc_new_str(rb_cPrismCurrentVersionError, current_ruby_value));
208+
}
209+
} else if (!pm_options_version_set(options, version, RSTRING_LEN(value))) {
203210
rb_raise(rb_eArgError, "invalid version: %" PRIsVALUE, value);
204211
}
205212
}
@@ -888,7 +895,7 @@ parse_input(pm_string_t *input, const pm_options_t *options) {
888895
* version of Ruby syntax (which you can trigger with `nil` or
889896
* `"latest"`). You may also restrict the syntax to a specific version of
890897
* Ruby, e.g., with `"3.3.0"`. To parse with the same syntax version that
891-
* the current Ruby is running use `version: RUBY_VERSION`. Raises
898+
* the current Ruby is running use `version: "current"`. Raises
892899
* ArgumentError if the version is not currently supported by Prism.
893900
*/
894901
static VALUE
@@ -1364,6 +1371,8 @@ Init_prism(void) {
13641371
rb_cPrismStringQuery = rb_define_class_under(rb_cPrism, "StringQuery", rb_cObject);
13651372
rb_cPrismScope = rb_define_class_under(rb_cPrism, "Scope", rb_cObject);
13661373

1374+
rb_cPrismCurrentVersionError = rb_const_get(rb_cPrism, rb_intern("CurrentVersionError"));
1375+
13671376
// Intern all of the IDs eagerly that we support so that we don't have to do
13681377
// it every time we parse.
13691378
rb_id_option_command_line = rb_intern_const("command_line");

lib/prism.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@ module Prism
3737
private_constant :LexCompat
3838
private_constant :LexRipper
3939

40+
# Raised when requested to parse as the currently running Ruby version but Prism has no support for it.
41+
class CurrentVersionError < ArgumentError
42+
# Initialize a new exception for the given ruby version string.
43+
def initialize(version)
44+
message = +"invalid version: Requested to parse as `version: 'current'`; "
45+
gem_version =
46+
begin
47+
Gem::Version.new(version)
48+
rescue ArgumentError
49+
end
50+
51+
if gem_version && gem_version < Gem::Version.new("3.3.0")
52+
message << " #{version} is below the minimum supported syntax."
53+
else
54+
message << " #{version} is unknown. Please update the `prism` gem."
55+
end
56+
57+
super(message)
58+
end
59+
end
60+
4061
# :call-seq:
4162
# Prism::lex_compat(source, **options) -> LexCompat::Result
4263
#

lib/prism/ffi.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,9 @@ def dump_options_command_line(options)
423423

424424
# Return the value that should be dumped for the version option.
425425
def dump_options_version(version)
426-
case version
426+
current = version == "current"
427+
428+
case current ? RUBY_VERSION : version
427429
when nil, "latest"
428430
0 # Handled in pm_parser_init
429431
when /\A3\.3(\.\d+)?\z/
@@ -433,7 +435,11 @@ def dump_options_version(version)
433435
when /\A3\.5(\.\d+)?\z/
434436
3
435437
else
436-
raise ArgumentError, "invalid version: #{version}"
438+
if current
439+
raise CurrentVersionError, RUBY_VERSION
440+
else
441+
raise ArgumentError, "invalid version: #{version}"
442+
end
437443
end
438444
end
439445

templates/sig/prism.rbs.erb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ module Prism
22
BACKEND: :CEXT | :FFI
33
VERSION: String
44

5+
class CurrentVersionError < ArgumentError
6+
def initialize: (String version) -> void
7+
end
8+
59
# Methods taking a Ruby source code string:
610
<%-
711
{

test/prism/api/parse_test.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,25 @@ def test_version
140140
end
141141
end
142142

143+
def test_version_current
144+
if RUBY_VERSION >= "3.3"
145+
assert Prism.parse_success?("1 + 1", version: "current")
146+
else
147+
assert_raise(CurrentVersionError) { Prism.parse_success?("1 + 1", version: "current") }
148+
end
149+
150+
version = RUBY_VERSION.split(".").tap { |segments| segments[0] = segments[0].succ }.join(".")
151+
stub_ruby_version(version) do
152+
error = assert_raise(CurrentVersionError) { Prism.parse("1 + 1", version: "current") }
153+
assert_includes error.message, "unknown"
154+
end
155+
156+
stub_ruby_version("2.7.0") do
157+
error = assert_raise(CurrentVersionError) { Prism.parse("1 + 1", version: "current") }
158+
assert_includes error.message, "minimum"
159+
end
160+
end
161+
143162
def test_scopes
144163
assert_kind_of Prism::CallNode, Prism.parse_statement("foo")
145164
assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]])
@@ -167,5 +186,16 @@ def find_source_file_node(program)
167186
queue.concat(node.compact_child_nodes)
168187
end
169188
end
189+
190+
def stub_ruby_version(version)
191+
old_version = RUBY_VERSION
192+
193+
Object.send(:remove_const, :RUBY_VERSION)
194+
Object.const_set(:RUBY_VERSION, version)
195+
yield
196+
ensure
197+
Object.send(:remove_const, :RUBY_VERSION)
198+
Object.const_set(:RUBY_VERSION, old_version)
199+
end
170200
end
171201
end

0 commit comments

Comments
 (0)