Skip to content

Commit a7d6991

Browse files
committed
Initial support for the prism parser
Prism will be the parser in Ruby 3.3. We need to support 3.0+ so we will have to "dual boot" both parsers. Todo: - LexAll to support Prism lex output - Add tests that exercise both Ripper and prism codepaths on CI - Handle ruby/prism#1972 in `ripper_errors.rb` - Update docs to not mention Ripper explicitly - Consider different/cleaner APIs for separating out Ripper and Prism
1 parent 5afa62a commit a7d6991

6 files changed

Lines changed: 95 additions & 30 deletions

File tree

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ gem "standard"
1212
gem "ruby-prof"
1313

1414
gem "benchmark-ips"
15+
gem "prism"

Gemfile.lock

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,34 @@ GEM
77
remote: https://rubygems.org/
88
specs:
99
ast (2.4.2)
10-
benchmark-ips (2.9.2)
11-
diff-lcs (1.4.4)
10+
benchmark-ips (2.12.0)
11+
diff-lcs (1.5.0)
1212
json (2.7.0)
1313
language_server-protocol (3.17.0.3)
1414
lint_roller (1.1.0)
1515
parallel (1.23.0)
1616
parser (3.2.2.4)
1717
ast (~> 2.4.1)
1818
racc
19+
prism (0.18.0)
1920
racc (1.7.3)
2021
rainbow (3.1.1)
2122
rake (12.3.3)
2223
regexp_parser (2.8.3)
2324
rexml (3.2.6)
24-
rspec (3.10.0)
25-
rspec-core (~> 3.10.0)
26-
rspec-expectations (~> 3.10.0)
27-
rspec-mocks (~> 3.10.0)
28-
rspec-core (3.10.0)
29-
rspec-support (~> 3.10.0)
30-
rspec-expectations (3.10.0)
25+
rspec (3.12.0)
26+
rspec-core (~> 3.12.0)
27+
rspec-expectations (~> 3.12.0)
28+
rspec-mocks (~> 3.12.0)
29+
rspec-core (3.12.2)
30+
rspec-support (~> 3.12.0)
31+
rspec-expectations (3.12.3)
3132
diff-lcs (>= 1.2.0, < 2.0)
32-
rspec-support (~> 3.10.0)
33-
rspec-mocks (3.10.0)
33+
rspec-support (~> 3.12.0)
34+
rspec-mocks (3.12.6)
3435
diff-lcs (>= 1.2.0, < 2.0)
35-
rspec-support (~> 3.10.0)
36-
rspec-support (3.10.0)
36+
rspec-support (~> 3.12.0)
37+
rspec-support (3.12.1)
3738
rubocop (1.57.2)
3839
json (~> 2.3)
3940
language_server-protocol (>= 3.17.0)
@@ -50,9 +51,9 @@ GEM
5051
rubocop-performance (1.19.1)
5152
rubocop (>= 1.7.0, < 2.0)
5253
rubocop-ast (>= 0.4.0)
53-
ruby-prof (1.4.3)
54+
ruby-prof (1.6.3)
5455
ruby-progressbar (1.13.0)
55-
stackprof (0.2.16)
56+
stackprof (0.2.25)
5657
standard (1.32.1)
5758
language_server-protocol (~> 3.17.0.2)
5859
lint_roller (~> 1.0)
@@ -72,6 +73,7 @@ PLATFORMS
7273

7374
DEPENDENCIES
7475
benchmark-ips
76+
prism
7577
rake (~> 12.0)
7678
rspec (~> 3.0)
7779
ruby-prof
@@ -80,4 +82,4 @@ DEPENDENCIES
8082
syntax_suggest!
8183

8284
BUNDLED WITH
83-
2.3.14
85+
2.4.21

lib/syntax_suggest/api.rb

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,22 @@
55
require "tmpdir"
66
require "stringio"
77
require "pathname"
8-
require "ripper"
8+
9+
# rubocop:disable Style/IdenticalConditionalBranches
10+
if ENV["SYNTAX_SUGGEST_DISABLE_PRISM"] # For testing dual ripper/prism support
11+
require "ripper"
12+
else
13+
# TODO remove require
14+
# Allow both to be loaded to enable more atomic commits
15+
require "ripper"
16+
begin
17+
require "prism"
18+
rescue LoadError
19+
require "ripper"
20+
end
21+
end
22+
# rubocop:enable Style/IdenticalConditionalBranches
23+
924
require "timeout"
1025

1126
module SyntaxSuggest
@@ -16,6 +31,14 @@ module SyntaxSuggest
1631
class Error < StandardError; end
1732
TIMEOUT_DEFAULT = ENV.fetch("SYNTAX_SUGGEST_TIMEOUT", 1).to_i
1833

34+
# SyntaxSuggest.use_prism_parser? [Private]
35+
#
36+
# Tells us if the prism parser is available for use
37+
# or if we should fallback to `Ripper`
38+
def self.use_prism_parser?
39+
defined?(Prism)
40+
end
41+
1942
# SyntaxSuggest.handle_error [Public]
2043
#
2144
# Takes a `SyntaxError` exception, uses the
@@ -129,11 +152,20 @@ def self.valid_without?(without_lines:, code_lines:)
129152
# SyntaxSuggest.invalid? [Private]
130153
#
131154
# Opposite of `SyntaxSuggest.valid?`
132-
def self.invalid?(source)
133-
source = source.join if source.is_a?(Array)
134-
source = source.to_s
155+
if defined?(Prism)
156+
def self.invalid?(source)
157+
source = source.join if source.is_a?(Array)
158+
source = source.to_s
159+
160+
Prism.parse(source).failure?
161+
end
162+
else
163+
def self.invalid?(source)
164+
source = source.join if source.is_a?(Array)
165+
source = source.to_s
135166

136-
Ripper.new(source).tap(&:parse).error?
167+
Ripper.new(source).tap(&:parse).error?
168+
end
137169
end
138170

139171
# SyntaxSuggest.valid? [Private]
@@ -191,7 +223,9 @@ def self.valid?(source)
191223
require_relative "code_line"
192224
require_relative "code_block"
193225
require_relative "block_expand"
194-
require_relative "ripper_errors"
226+
if !SyntaxSuggest.use_prism_parser?
227+
require_relative "ripper_errors"
228+
end
195229
require_relative "priority_queue"
196230
require_relative "unvisited_lines"
197231
require_relative "around_block_scan"

lib/syntax_suggest/explain_syntax.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
require_relative "left_right_lex_count"
44

55
module SyntaxSuggest
6+
class GetParseErrors
7+
def self.errors(source)
8+
if SyntaxSuggest.use_prism_parser?
9+
Prism.parse(source).errors.map(&:message)
10+
else
11+
RipperErrors.new(source).call.errors
12+
end
13+
end
14+
end
15+
616
# Explains syntax errors based on their source
717
#
818
# example:
@@ -94,7 +104,7 @@ def why(miss)
94104
# on the original ripper error messages
95105
def errors
96106
if missing.empty?
97-
return RipperErrors.new(@code_lines.map(&:original).join).call.errors
107+
return GetParseErrors.errors(@code_lines.map(&:original).join)
98108
end
99109

100110
missing.map { |miss| why(miss) }

lib/syntax_suggest/lex_all.rb

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,40 @@ class LexAll
1111
include Enumerable
1212

1313
def initialize(source:, source_lines: nil)
14-
@lex = Ripper::Lexer.new(source, "-", 1).parse.sort_by(&:pos)
15-
lineno = @lex.last.pos.first + 1
14+
@lex = self.class.lex(source, 1)
15+
lineno = @lex.last[0][0] + 1
1616
source_lines ||= source.lines
1717
last_lineno = source_lines.length
1818

1919
until lineno >= last_lineno
2020
lines = source_lines[lineno..]
2121

2222
@lex.concat(
23-
Ripper::Lexer.new(lines.join, "-", lineno + 1).parse.sort_by(&:pos)
23+
self.class.lex(lines.join, lineno + 1)
2424
)
25-
lineno = @lex.last.pos.first + 1
25+
26+
lineno = @lex.last[0].first + 1
2627
end
2728

2829
last_lex = nil
2930
@lex.map! { |elem|
30-
last_lex = LexValue.new(elem.pos.first, elem.event, elem.tok, elem.state, last_lex)
31+
last_lex = LexValue.new(elem[0].first, elem[1], elem[2], elem[3], last_lex)
3132
}
3233
end
3334

35+
# rubocop:disable Style/IdenticalConditionalBranches
36+
if SyntaxSuggest.use_prism_parser?
37+
def self.lex(source, line_number)
38+
# Prism.lex_compat(source, line: line_number).value.sort_by {|values| values[0] }
39+
Ripper::Lexer.new(source, "-", line_number).parse.sort_by(&:pos)
40+
end
41+
else
42+
def self.lex(source, line_number)
43+
Ripper::Lexer.new(source, "-", line_number).parse.sort_by(&:pos)
44+
end
45+
end
46+
# rubocop:enable Style/IdenticalConditionalBranches
47+
3448
def to_a
3549
@lex
3650
end

spec/unit/explain_syntax_spec.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ module SyntaxSuggest
1414
).call
1515

1616
expect(explain.missing).to eq([])
17-
expect(explain.errors.join).to include("unterminated string")
17+
if SyntaxSuggest.use_prism_parser?
18+
expect(explain.errors.join).to include("Expected a closing delimiter for the interpolated string")
19+
else
20+
expect(explain.errors.join).to include("unterminated string")
21+
end
1822
end
1923

2024
it "handles %w[]" do
@@ -191,7 +195,7 @@ def meow
191195
).call
192196

193197
expect(explain.missing).to eq([])
194-
expect(explain.errors).to eq(RipperErrors.new(source).call.errors)
198+
expect(explain.errors).to eq(GetParseErrors.errors(source))
195199
end
196200

197201
it "handles an unexpected rescue" do

0 commit comments

Comments
 (0)