Skip to content

Commit 457c1eb

Browse files
authored
Merge pull request #211 from ruby/schneems/prism-numero-uno-rebased
Add support for the Prism parser
2 parents 5afa62a + becf097 commit 457c1eb

15 files changed

Lines changed: 159 additions & 51 deletions

File tree

.github/workflows/ci.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,22 @@ jobs:
4343
- name: test
4444
run: bin/rake test
4545
continue-on-error: ${{ matrix.ruby == 'head' }}
46+
47+
test-disable-prism:
48+
needs: ruby-versions
49+
runs-on: ubuntu-latest
50+
strategy:
51+
fail-fast: false
52+
matrix:
53+
ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
54+
steps:
55+
- name: Checkout code
56+
uses: actions/checkout@v4.1.1
57+
- name: Set up Ruby
58+
uses: ruby/setup-ruby@v1
59+
with:
60+
ruby-version: ${{ matrix.ruby }}
61+
bundler-cache: true
62+
- name: test
63+
run: SYNTAX_SUGGEST_DISABLE_PRISM=1 bin/rake test
64+
continue-on-error: ${{ matrix.ruby == 'head' }}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## HEAD (unreleased)
22

3+
- Support prism parser (https://github.com/ruby/syntax_suggest/pull/208).
34
- No longer supports EOL versions of Ruby. (https://github.com/ruby/syntax_suggest/pull/210)
45
- Handle Ruby 3.3 new eval source location format (https://github.com/ruby/syntax_suggest/pull/200).
56

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

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Unmatched `(', missing `)' ?
122122
5 end
123123
```
124124

125-
- Any ambiguous or unknown errors will be annotated by the original ripper error output:
125+
- Any ambiguous or unknown errors will be annotated by the original parser error output:
126126

127127
<!--
128128
class Dog
@@ -133,7 +133,7 @@ end
133133
-->
134134

135135
```
136-
syntax error, unexpected end-of-input
136+
Expected an expression after the operator
137137
138138
1 class Dog
139139
2 def meals_last_month

lib/syntax_suggest/api.rb

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,28 @@
55
require "tmpdir"
66
require "stringio"
77
require "pathname"
8-
require "ripper"
98
require "timeout"
109

10+
# We need Ripper loaded for `Prism.lex_compat` even if we're using Prism
11+
# for lexing and parsing
12+
require "ripper"
13+
14+
# Prism is the new parser, replacing Ripper
15+
#
16+
# We need to "dual boot" both for now because syntax_suggest
17+
# supports older rubies that do not ship with syntax suggest.
18+
#
19+
# We also need the ability to control loading of this library
20+
# so we can test that both modes work correctly in CI.
21+
if (value = ENV["SYNTAX_SUGGEST_DISABLE_PRISM"])
22+
warn "Skipping loading prism due to SYNTAX_SUGGEST_DISABLE_PRISM=#{value}"
23+
else
24+
begin
25+
require "prism"
26+
rescue LoadError
27+
end
28+
end
29+
1130
module SyntaxSuggest
1231
# Used to indicate a default value that cannot
1332
# be confused with another input.
@@ -16,6 +35,14 @@ module SyntaxSuggest
1635
class Error < StandardError; end
1736
TIMEOUT_DEFAULT = ENV.fetch("SYNTAX_SUGGEST_TIMEOUT", 1).to_i
1837

38+
# SyntaxSuggest.use_prism_parser? [Private]
39+
#
40+
# Tells us if the prism parser is available for use
41+
# or if we should fallback to `Ripper`
42+
def self.use_prism_parser?
43+
defined?(Prism)
44+
end
45+
1946
# SyntaxSuggest.handle_error [Public]
2047
#
2148
# Takes a `SyntaxError` exception, uses the
@@ -129,11 +156,20 @@ def self.valid_without?(without_lines:, code_lines:)
129156
# SyntaxSuggest.invalid? [Private]
130157
#
131158
# Opposite of `SyntaxSuggest.valid?`
132-
def self.invalid?(source)
133-
source = source.join if source.is_a?(Array)
134-
source = source.to_s
159+
if defined?(Prism)
160+
def self.invalid?(source)
161+
source = source.join if source.is_a?(Array)
162+
source = source.to_s
135163

136-
Ripper.new(source).tap(&:parse).error?
164+
Prism.parse(source).failure?
165+
end
166+
else
167+
def self.invalid?(source)
168+
source = source.join if source.is_a?(Array)
169+
source = source.to_s
170+
171+
Ripper.new(source).tap(&:parse).error?
172+
end
137173
end
138174

139175
# SyntaxSuggest.valid? [Private]
@@ -191,7 +227,6 @@ def self.valid?(source)
191227
require_relative "code_line"
192228
require_relative "code_block"
193229
require_relative "block_expand"
194-
require_relative "ripper_errors"
195230
require_relative "priority_queue"
196231
require_relative "unvisited_lines"
197232
require_relative "around_block_scan"

lib/syntax_suggest/clean_document.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ module SyntaxSuggest
4747
# ## Heredocs
4848
#
4949
# A heredoc is an way of defining a multi-line string. They can cause many
50-
# problems. If left as a single line, Ripper would try to parse the contents
50+
# problems. If left as a single line, the parser would try to parse the contents
5151
# as ruby code rather than as a string. Even without this problem, we still
52-
# hit an issue with indentation
52+
# hit an issue with indentation:
5353
#
5454
# 1 foo = <<~HEREDOC
5555
# 2 "Be yourself; everyone else is already taken.""

lib/syntax_suggest/code_block.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def valid?
8181
# lines then the result cannot be invalid
8282
#
8383
# That means there's no reason to re-check all
84-
# lines with ripper (which is expensive).
84+
# lines with the parser (which is expensive).
8585
# Benchmark in commit message
8686
@valid = if lines.all? { |l| l.hidden? || l.empty? }
8787
true

lib/syntax_suggest/code_line.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,19 @@ def ignore_newline_not_beg?
180180
# EOM
181181
# expect(lines.first.trailing_slash?).to eq(true)
182182
#
183-
def trailing_slash?
184-
last = @lex.last
185-
return false unless last
186-
return false unless last.type == :on_sp
183+
if SyntaxSuggest.use_prism_parser?
184+
def trailing_slash?
185+
last = @lex.last
186+
last&.type == :on_tstring_end
187+
end
188+
else
189+
def trailing_slash?
190+
last = @lex.last
191+
return false unless last
192+
return false unless last.type == :on_sp
187193

188-
last.token == TRAILING_SLASH
194+
last.token == TRAILING_SLASH
195+
end
189196
end
190197

191198
# Endless method detection

lib/syntax_suggest/explain_syntax.rb

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,21 @@
22

33
require_relative "left_right_lex_count"
44

5+
if !SyntaxSuggest.use_prism_parser?
6+
require_relative "ripper_errors"
7+
end
8+
59
module SyntaxSuggest
10+
class GetParseErrors
11+
def self.errors(source)
12+
if SyntaxSuggest.use_prism_parser?
13+
Prism.parse(source).errors.map(&:message)
14+
else
15+
RipperErrors.new(source).call.errors
16+
end
17+
end
18+
end
19+
620
# Explains syntax errors based on their source
721
#
822
# example:
@@ -15,8 +29,8 @@ module SyntaxSuggest
1529
# # => "Unmatched keyword, missing `end' ?"
1630
#
1731
# When the error cannot be determined by lexical counting
18-
# then ripper is run against the input and the raw ripper
19-
# errors returned.
32+
# then the parser is run against the input and the raw
33+
# errors are returned.
2034
#
2135
# Example:
2236
#
@@ -91,10 +105,10 @@ def why(miss)
91105
# Returns an array of syntax error messages
92106
#
93107
# If no missing pairs are found it falls back
94-
# on the original ripper error messages
108+
# on the original error messages
95109
def errors
96110
if missing.empty?
97-
return RipperErrors.new(@code_lines.map(&:original).join).call.errors
111+
return GetParseErrors.errors(@code_lines.map(&:original).join).uniq
98112
end
99113

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

0 commit comments

Comments
 (0)