mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00

When parent scopes around an eval are forwarding parameters (like
*, **, &, or ...) we need to know that information when we are in
the parser. As such, we need to support passing that information
into the scopes option. In order to do this, unfortunately we need
a bunch of changes.
The scopes option was previously an array of array of strings.
These corresponded to the names of the locals in the parent scopes.
We still support this, but now additionally support passing in a
Prism::Scope instance at each index in the array. This Prism::Scope
class holds both the names of the locals as well as an array of
forwarding parameter names (symbols corresponding to the forwarding
parameters). There is convenience function on the Prism module that
creates a Prism::Scope object using Prism.scope.
In JavaScript, we now additionally support an object much the same
as the Ruby side. In Java, we now have a ParsingOptions.Scope class
that holds that information. In the dump APIs, these objects in all
3 languages will add an additional byte for the forwarding flags in
the middle of the scopes serialization.
All of this is in service of properly parsing the following code:
```ruby
def foo(*) = eval("bar(*)")
```
21abb6b7c4
171 lines
5.2 KiB
Ruby
171 lines
5.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "../test_helper"
|
|
|
|
module Prism
|
|
class ParseTest < TestCase
|
|
def test_parse_result
|
|
result = Prism.parse("")
|
|
assert_kind_of ParseResult, result
|
|
|
|
result = Prism.parse_file(__FILE__)
|
|
assert_kind_of ParseResult, result
|
|
end
|
|
|
|
def test_parse_empty_string
|
|
result = Prism.parse("")
|
|
assert_equal [], result.value.statements.body
|
|
end
|
|
|
|
def test_parse_takes_file_path
|
|
filepath = "filepath.rb"
|
|
result = Prism.parse("def foo; __FILE__; end", filepath: filepath)
|
|
|
|
assert_equal filepath, find_source_file_node(result.value).filepath
|
|
end
|
|
|
|
def test_parse_takes_line
|
|
line = 4
|
|
result = Prism.parse("def foo\n __FILE__\nend", line: line)
|
|
|
|
assert_equal line, result.value.location.start_line
|
|
assert_equal line + 1, find_source_file_node(result.value).location.start_line
|
|
|
|
result = Prism.parse_lex("def foo\n __FILE__\nend", line: line)
|
|
assert_equal line, result.value.first.location.start_line
|
|
end
|
|
|
|
def test_parse_takes_negative_lines
|
|
line = -2
|
|
result = Prism.parse("def foo\n __FILE__\nend", line: line)
|
|
|
|
assert_equal line, result.value.location.start_line
|
|
assert_equal line + 1, find_source_file_node(result.value).location.start_line
|
|
|
|
result = Prism.parse_lex("def foo\n __FILE__\nend", line: line)
|
|
assert_equal line, result.value.first.location.start_line
|
|
end
|
|
|
|
def test_parse_file
|
|
node = Prism.parse_file(__FILE__).value
|
|
assert_kind_of ProgramNode, node
|
|
|
|
error = assert_raise Errno::ENOENT do
|
|
Prism.parse_file("idontexist.rb")
|
|
end
|
|
|
|
assert_equal "No such file or directory - idontexist.rb", error.message
|
|
|
|
assert_raise TypeError do
|
|
Prism.parse_file(nil)
|
|
end
|
|
end
|
|
|
|
def test_parse_tempfile
|
|
Tempfile.create(["test_parse_tempfile", ".rb"]) do |t|
|
|
t.puts ["begin\n", " end\n"]
|
|
t.flush
|
|
Prism.parse_file(t.path)
|
|
end
|
|
end
|
|
|
|
if RUBY_ENGINE != "truffleruby"
|
|
def test_parse_nonascii
|
|
Dir.mktmpdir do |dir|
|
|
path = File.join(dir, "\u{3042 3044 3046 3048 304a}.rb".encode(Encoding::Windows_31J))
|
|
File.write(path, "ok")
|
|
Prism.parse_file(path)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_parse_directory
|
|
error = nil
|
|
|
|
begin
|
|
Prism.parse_file(__dir__)
|
|
rescue SystemCallError => error
|
|
end
|
|
|
|
assert_kind_of Errno::EISDIR, error
|
|
end
|
|
|
|
def test_partial_script
|
|
assert Prism.parse_failure?("break")
|
|
assert Prism.parse_success?("break", partial_script: true)
|
|
|
|
assert Prism.parse_failure?("next")
|
|
assert Prism.parse_success?("next", partial_script: true)
|
|
|
|
assert Prism.parse_failure?("redo")
|
|
assert Prism.parse_success?("redo", partial_script: true)
|
|
|
|
assert Prism.parse_failure?("yield")
|
|
assert Prism.parse_success?("yield", partial_script: true)
|
|
end
|
|
|
|
def test_version
|
|
assert Prism.parse_success?("1 + 1", version: "3.3")
|
|
assert Prism.parse_success?("1 + 1", version: "3.3.0")
|
|
assert Prism.parse_success?("1 + 1", version: "3.3.1")
|
|
assert Prism.parse_success?("1 + 1", version: "3.3.9")
|
|
assert Prism.parse_success?("1 + 1", version: "3.3.10")
|
|
|
|
assert Prism.parse_success?("1 + 1", version: "3.4")
|
|
assert Prism.parse_success?("1 + 1", version: "3.4.0")
|
|
assert Prism.parse_success?("1 + 1", version: "3.4.9")
|
|
assert Prism.parse_success?("1 + 1", version: "3.4.10")
|
|
|
|
assert Prism.parse_success?("1 + 1", version: "3.5")
|
|
assert Prism.parse_success?("1 + 1", version: "3.5.0")
|
|
|
|
assert Prism.parse_success?("1 + 1", version: "latest")
|
|
|
|
# Test edge case
|
|
error = assert_raise(ArgumentError) { Prism.parse("1 + 1", version: "latest2") }
|
|
assert_equal "invalid version: latest2", error.message
|
|
|
|
assert_raise ArgumentError do
|
|
Prism.parse("1 + 1", version: "3.3.a")
|
|
end
|
|
|
|
# Not supported version (too old)
|
|
assert_raise ArgumentError do
|
|
Prism.parse("1 + 1", version: "3.2.0")
|
|
end
|
|
|
|
# Not supported version (too new)
|
|
assert_raise ArgumentError do
|
|
Prism.parse("1 + 1", version: "3.6.0")
|
|
end
|
|
end
|
|
|
|
def test_scopes
|
|
assert_kind_of Prism::CallNode, Prism.parse_statement("foo")
|
|
assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]])
|
|
assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [Prism.scope(locals: [:foo])])
|
|
|
|
assert Prism.parse_failure?("foo(*)")
|
|
assert Prism.parse_success?("foo(*)", scopes: [Prism.scope(forwarding: [:*])])
|
|
|
|
assert Prism.parse_failure?("foo(**)")
|
|
assert Prism.parse_success?("foo(**)", scopes: [Prism.scope(forwarding: [:**])])
|
|
|
|
assert Prism.parse_failure?("foo(&)")
|
|
assert Prism.parse_success?("foo(&)", scopes: [Prism.scope(forwarding: [:&])])
|
|
|
|
assert Prism.parse_failure?("foo(...)")
|
|
assert Prism.parse_success?("foo(...)", scopes: [Prism.scope(forwarding: [:"..."])])
|
|
end
|
|
|
|
private
|
|
|
|
def find_source_file_node(program)
|
|
queue = [program]
|
|
while (node = queue.shift)
|
|
return node if node.is_a?(SourceFileNode)
|
|
queue.concat(node.compact_child_nodes)
|
|
end
|
|
end
|
|
end
|
|
end
|