summaryrefslogtreecommitdiff
path: root/lib/syntax_suggest
diff options
context:
space:
mode:
Diffstat (limited to 'lib/syntax_suggest')
-rw-r--r--lib/syntax_suggest/api.rb47
-rw-r--r--lib/syntax_suggest/around_block_scan.rb4
-rw-r--r--lib/syntax_suggest/block_expand.rb2
-rw-r--r--lib/syntax_suggest/capture_code_context.rb2
-rw-r--r--lib/syntax_suggest/clean_document.rb14
-rw-r--r--lib/syntax_suggest/code_block.rb2
-rw-r--r--lib/syntax_suggest/code_frontier.rb2
-rw-r--r--lib/syntax_suggest/code_line.rb17
-rw-r--r--lib/syntax_suggest/code_search.rb4
-rw-r--r--lib/syntax_suggest/core_ext.rb2
-rw-r--r--lib/syntax_suggest/display_invalid_blocks.rb2
-rw-r--r--lib/syntax_suggest/explain_syntax.rb22
-rw-r--r--lib/syntax_suggest/lex_all.rb39
-rw-r--r--lib/syntax_suggest/parse_blocks_from_indent_line.rb2
-rw-r--r--lib/syntax_suggest/pathname_from_message.rb2
-rw-r--r--lib/syntax_suggest/ripper_errors.rb5
-rw-r--r--lib/syntax_suggest/scan_history.rb2
-rw-r--r--lib/syntax_suggest/syntax_suggest.gemspec2
-rw-r--r--lib/syntax_suggest/version.rb2
19 files changed, 126 insertions, 48 deletions
diff --git a/lib/syntax_suggest/api.rb b/lib/syntax_suggest/api.rb
index 74e53c2563..65660ec5e5 100644
--- a/lib/syntax_suggest/api.rb
+++ b/lib/syntax_suggest/api.rb
@@ -5,9 +5,28 @@ require_relative "version"
require "tmpdir"
require "stringio"
require "pathname"
-require "ripper"
require "timeout"
+# We need Ripper loaded for `Prism.lex_compat` even if we're using Prism
+# for lexing and parsing
+require "ripper"
+
+# Prism is the new parser, replacing Ripper
+#
+# We need to "dual boot" both for now because syntax_suggest
+# supports older rubies that do not ship with syntax suggest.
+#
+# We also need the ability to control loading of this library
+# so we can test that both modes work correctly in CI.
+if (value = ENV["SYNTAX_SUGGEST_DISABLE_PRISM"])
+ warn "Skipping loading prism due to SYNTAX_SUGGEST_DISABLE_PRISM=#{value}"
+else
+ begin
+ require "prism"
+ rescue LoadError
+ end
+end
+
module SyntaxSuggest
# Used to indicate a default value that cannot
# be confused with another input.
@@ -16,6 +35,14 @@ module SyntaxSuggest
class Error < StandardError; end
TIMEOUT_DEFAULT = ENV.fetch("SYNTAX_SUGGEST_TIMEOUT", 1).to_i
+ # SyntaxSuggest.use_prism_parser? [Private]
+ #
+ # Tells us if the prism parser is available for use
+ # or if we should fallback to `Ripper`
+ def self.use_prism_parser?
+ defined?(Prism)
+ end
+
# SyntaxSuggest.handle_error [Public]
#
# Takes a `SyntaxError` exception, uses the
@@ -129,11 +156,20 @@ module SyntaxSuggest
# SyntaxSuggest.invalid? [Private]
#
# Opposite of `SyntaxSuggest.valid?`
- def self.invalid?(source)
- source = source.join if source.is_a?(Array)
- source = source.to_s
+ if defined?(Prism)
+ def self.invalid?(source)
+ source = source.join if source.is_a?(Array)
+ source = source.to_s
- Ripper.new(source).tap(&:parse).error?
+ Prism.parse(source).failure?
+ end
+ else
+ def self.invalid?(source)
+ source = source.join if source.is_a?(Array)
+ source = source.to_s
+
+ Ripper.new(source).tap(&:parse).error?
+ end
end
# SyntaxSuggest.valid? [Private]
@@ -191,7 +227,6 @@ require_relative "lex_all"
require_relative "code_line"
require_relative "code_block"
require_relative "block_expand"
-require_relative "ripper_errors"
require_relative "priority_queue"
require_relative "unvisited_lines"
require_relative "around_block_scan"
diff --git a/lib/syntax_suggest/around_block_scan.rb b/lib/syntax_suggest/around_block_scan.rb
index ce00431b3a..dd9af729c5 100644
--- a/lib/syntax_suggest/around_block_scan.rb
+++ b/lib/syntax_suggest/around_block_scan.rb
@@ -118,7 +118,7 @@ module SyntaxSuggest
end
# Scanning is intentionally conservative because
- # we have no way of rolling back an agressive block (at this time)
+ # we have no way of rolling back an aggressive block (at this time)
#
# If a block was stopped for some trivial reason, (like an empty line)
# but the next line would have caused it to be balanced then we
@@ -224,7 +224,7 @@ module SyntaxSuggest
@scanner.lines
end
- # Managable rspec errors
+ # Manageable rspec errors
def inspect
"#<#{self.class}:0x0000123843lol >"
end
diff --git a/lib/syntax_suggest/block_expand.rb b/lib/syntax_suggest/block_expand.rb
index e9b486c720..2751ae2a64 100644
--- a/lib/syntax_suggest/block_expand.rb
+++ b/lib/syntax_suggest/block_expand.rb
@@ -157,7 +157,7 @@ module SyntaxSuggest
end
end
- # Managable rspec errors
+ # Manageable rspec errors
def inspect
"#<SyntaxSuggest::CodeBlock:0x0000123843lol >"
end
diff --git a/lib/syntax_suggest/capture_code_context.rb b/lib/syntax_suggest/capture_code_context.rb
index 6dc7047176..1f232cfae3 100644
--- a/lib/syntax_suggest/capture_code_context.rb
+++ b/lib/syntax_suggest/capture_code_context.rb
@@ -26,7 +26,7 @@ module SyntaxSuggest
# they can't add extra data that's not present.
#
# In the case of known ambiguious cases, this class adds context
- # back to the ambiguitiy so the programmer has full information.
+ # back to the ambiguity so the programmer has full information.
#
# Beyond handling these ambiguities, it also captures surrounding
# code context information:
diff --git a/lib/syntax_suggest/clean_document.rb b/lib/syntax_suggest/clean_document.rb
index 2c26061bfc..2790ccae86 100644
--- a/lib/syntax_suggest/clean_document.rb
+++ b/lib/syntax_suggest/clean_document.rb
@@ -47,9 +47,9 @@ module SyntaxSuggest
# ## Heredocs
#
# A heredoc is an way of defining a multi-line string. They can cause many
- # problems. If left as a single line, Ripper would try to parse the contents
+ # problems. If left as a single line, the parser would try to parse the contents
# as ruby code rather than as a string. Even without this problem, we still
- # hit an issue with indentation
+ # hit an issue with indentation:
#
# 1 foo = <<~HEREDOC
# 2 "Be yourself; everyone else is already taken.""
@@ -224,7 +224,7 @@ module SyntaxSuggest
#
def join_consecutive!
consecutive_groups = @document.select(&:ignore_newline_not_beg?).map do |code_line|
- take_while_including(code_line.index..-1) do |line|
+ take_while_including(code_line.index..) do |line|
line.ignore_newline_not_beg?
end
end
@@ -245,7 +245,7 @@ module SyntaxSuggest
# expect(lines[1].to_s).to eq("")
def join_trailing_slash!
trailing_groups = @document.select(&:trailing_slash?).map do |code_line|
- take_while_including(code_line.index..-1) { |x| x.trailing_slash? }
+ take_while_including(code_line.index..) { |x| x.trailing_slash? }
end
join_groups(trailing_groups)
self
@@ -267,7 +267,7 @@ module SyntaxSuggest
groups.each do |lines|
line = lines.first
- # Handle the case of multiple groups in a a row
+ # Handle the case of multiple groups in a row
# if one is already replaced, move on
next if @document[line.index].empty?
@@ -279,7 +279,7 @@ module SyntaxSuggest
)
# Hide the rest of the lines
- lines[1..-1].each do |line|
+ lines[1..].each do |line|
# The above lines already have newlines in them, if add more
# then there will be double newline, use an empty line instead
@document[line.index] = CodeLine.new(line: "", index: line.index, lex: [])
@@ -293,7 +293,7 @@ module SyntaxSuggest
# Like `take_while` except when it stops
# iterating, it also returns the line
# that caused it to stop
- def take_while_including(range = 0..-1)
+ def take_while_including(range = 0..)
take_next_and_stop = false
@document[range].take_while do |line|
next if take_next_and_stop
diff --git a/lib/syntax_suggest/code_block.rb b/lib/syntax_suggest/code_block.rb
index 61e7986da4..d842890300 100644
--- a/lib/syntax_suggest/code_block.rb
+++ b/lib/syntax_suggest/code_block.rb
@@ -81,7 +81,7 @@ module SyntaxSuggest
# lines then the result cannot be invalid
#
# That means there's no reason to re-check all
- # lines with ripper (which is expensive).
+ # lines with the parser (which is expensive).
# Benchmark in commit message
@valid = if lines.all? { |l| l.hidden? || l.empty? }
true
diff --git a/lib/syntax_suggest/code_frontier.rb b/lib/syntax_suggest/code_frontier.rb
index 8e93b32514..0f870d0df0 100644
--- a/lib/syntax_suggest/code_frontier.rb
+++ b/lib/syntax_suggest/code_frontier.rb
@@ -117,7 +117,7 @@ module SyntaxSuggest
if ENV["SYNTAX_SUGGEST_DEBUG"]
puts "```"
- puts @queue.peek.to_s
+ puts @queue.peek
puts "```"
puts " @frontier indent: #{frontier_indent}"
puts " @unvisited indent: #{unvisited_indent}"
diff --git a/lib/syntax_suggest/code_line.rb b/lib/syntax_suggest/code_line.rb
index a20f34afa4..58197e95d0 100644
--- a/lib/syntax_suggest/code_line.rb
+++ b/lib/syntax_suggest/code_line.rb
@@ -180,12 +180,19 @@ module SyntaxSuggest
# EOM
# expect(lines.first.trailing_slash?).to eq(true)
#
- def trailing_slash?
- last = @lex.last
- return false unless last
- return false unless last.type == :on_sp
+ if SyntaxSuggest.use_prism_parser?
+ def trailing_slash?
+ last = @lex.last
+ last&.type == :on_tstring_end
+ end
+ else
+ def trailing_slash?
+ last = @lex.last
+ return false unless last
+ return false unless last.type == :on_sp
- last.token == TRAILING_SLASH
+ last.token == TRAILING_SLASH
+ end
end
# Endless method detection
diff --git a/lib/syntax_suggest/code_search.rb b/lib/syntax_suggest/code_search.rb
index 2a86dfea90..7628dcd131 100644
--- a/lib/syntax_suggest/code_search.rb
+++ b/lib/syntax_suggest/code_search.rb
@@ -43,7 +43,7 @@ module SyntaxSuggest
def initialize(source, record_dir: DEFAULT_VALUE)
record_dir = if record_dir == DEFAULT_VALUE
- ENV["SYNTAX_SUGGEST_RECORD_DIR"] || ENV["SYNTAX_SUGGEST_DEBUG"] ? "tmp" : nil
+ (ENV["SYNTAX_SUGGEST_RECORD_DIR"] || ENV["SYNTAX_SUGGEST_DEBUG"]) ? "tmp" : nil
else
record_dir
end
@@ -73,7 +73,7 @@ module SyntaxSuggest
if ENV["SYNTAX_SUGGEST_DEBUG"]
puts "\n\n==== #{filename} ===="
puts "\n```#{block.starts_at}..#{block.ends_at}"
- puts block.to_s
+ puts block
puts "```"
puts " block indent: #{block.current_indent}"
end
diff --git a/lib/syntax_suggest/core_ext.rb b/lib/syntax_suggest/core_ext.rb
index e0fd62b81c..c299627bb7 100644
--- a/lib/syntax_suggest/core_ext.rb
+++ b/lib/syntax_suggest/core_ext.rb
@@ -21,7 +21,7 @@ if SyntaxError.method_defined?(:detailed_message)
attr_reader :string
end
- # SyntaxSuggest.record_dir [Private]
+ # SyntaxSuggest.module_for_detailed_message [Private]
#
# Used to monkeypatch SyntaxError via Module.prepend
def self.module_for_detailed_message
diff --git a/lib/syntax_suggest/display_invalid_blocks.rb b/lib/syntax_suggest/display_invalid_blocks.rb
index 32ec0021a3..5e79b3a262 100644
--- a/lib/syntax_suggest/display_invalid_blocks.rb
+++ b/lib/syntax_suggest/display_invalid_blocks.rb
@@ -14,7 +14,7 @@ module SyntaxSuggest
@filename = filename
@code_lines = code_lines
- @terminal = terminal == DEFAULT_VALUE ? io.isatty : terminal
+ @terminal = (terminal == DEFAULT_VALUE) ? io.isatty : terminal
end
def document_ok?
diff --git a/lib/syntax_suggest/explain_syntax.rb b/lib/syntax_suggest/explain_syntax.rb
index 142ed2e269..0d80c4d869 100644
--- a/lib/syntax_suggest/explain_syntax.rb
+++ b/lib/syntax_suggest/explain_syntax.rb
@@ -2,7 +2,21 @@
require_relative "left_right_lex_count"
+if !SyntaxSuggest.use_prism_parser?
+ require_relative "ripper_errors"
+end
+
module SyntaxSuggest
+ class GetParseErrors
+ def self.errors(source)
+ if SyntaxSuggest.use_prism_parser?
+ Prism.parse(source).errors.map(&:message)
+ else
+ RipperErrors.new(source).call.errors
+ end
+ end
+ end
+
# Explains syntax errors based on their source
#
# example:
@@ -15,8 +29,8 @@ module SyntaxSuggest
# # => "Unmatched keyword, missing `end' ?"
#
# When the error cannot be determined by lexical counting
- # then ripper is run against the input and the raw ripper
- # errors returned.
+ # then the parser is run against the input and the raw
+ # errors are returned.
#
# Example:
#
@@ -91,10 +105,10 @@ module SyntaxSuggest
# Returns an array of syntax error messages
#
# If no missing pairs are found it falls back
- # on the original ripper error messages
+ # on the original error messages
def errors
if missing.empty?
- return RipperErrors.new(@code_lines.map(&:original).join).call.errors
+ return GetParseErrors.errors(@code_lines.map(&:original).join).uniq
end
missing.map { |miss| why(miss) }
diff --git a/lib/syntax_suggest/lex_all.rb b/lib/syntax_suggest/lex_all.rb
index 132cba9f5d..c16fbb52d3 100644
--- a/lib/syntax_suggest/lex_all.rb
+++ b/lib/syntax_suggest/lex_all.rb
@@ -3,34 +3,53 @@
module SyntaxSuggest
# Ripper.lex is not guaranteed to lex the entire source document
#
- # lex = LexAll.new(source: source)
- # lex.each do |value|
- # puts value.line
- # end
+ # This class guarantees the whole document is lex-ed by iteratively
+ # lexing the document where ripper stopped.
+ #
+ # Prism likely doesn't have the same problem. Once ripper support is removed
+ # we can likely reduce the complexity here if not remove the whole concept.
+ #
+ # Example usage:
+ #
+ # lex = LexAll.new(source: source)
+ # lex.each do |value|
+ # puts value.line
+ # end
class LexAll
include Enumerable
def initialize(source:, source_lines: nil)
- @lex = Ripper::Lexer.new(source, "-", 1).parse.sort_by(&:pos)
- lineno = @lex.last.pos.first + 1
+ @lex = self.class.lex(source, 1)
+ lineno = @lex.last[0][0] + 1
source_lines ||= source.lines
last_lineno = source_lines.length
until lineno >= last_lineno
- lines = source_lines[lineno..-1]
+ lines = source_lines[lineno..]
@lex.concat(
- Ripper::Lexer.new(lines.join, "-", lineno + 1).parse.sort_by(&:pos)
+ self.class.lex(lines.join, lineno + 1)
)
- lineno = @lex.last.pos.first + 1
+
+ lineno = @lex.last[0].first + 1
end
last_lex = nil
@lex.map! { |elem|
- last_lex = LexValue.new(elem.pos.first, elem.event, elem.tok, elem.state, last_lex)
+ last_lex = LexValue.new(elem[0].first, elem[1], elem[2], elem[3], last_lex)
}
end
+ if SyntaxSuggest.use_prism_parser?
+ def self.lex(source, line_number)
+ Prism.lex_compat(source, line: line_number).value.sort_by { |values| values[0] }
+ end
+ else
+ def self.lex(source, line_number)
+ Ripper::Lexer.new(source, "-", line_number).parse.sort_by(&:pos)
+ end
+ end
+
def to_a
@lex
end
diff --git a/lib/syntax_suggest/parse_blocks_from_indent_line.rb b/lib/syntax_suggest/parse_blocks_from_indent_line.rb
index 241ed6acb4..39dfca55d2 100644
--- a/lib/syntax_suggest/parse_blocks_from_indent_line.rb
+++ b/lib/syntax_suggest/parse_blocks_from_indent_line.rb
@@ -8,7 +8,7 @@ module SyntaxSuggest
# grabbing one that contains only an "end". In this example:
#
# def dog
- # begonn # mispelled `begin`
+ # begonn # misspelled `begin`
# puts "bark"
# end
# end
diff --git a/lib/syntax_suggest/pathname_from_message.rb b/lib/syntax_suggest/pathname_from_message.rb
index b6fe1617be..ab90227427 100644
--- a/lib/syntax_suggest/pathname_from_message.rb
+++ b/lib/syntax_suggest/pathname_from_message.rb
@@ -13,7 +13,7 @@ module SyntaxSuggest
# # => "/tmp/scratch.rb"
#
class PathnameFromMessage
- EVAL_RE = /^\(eval\):\d+/
+ EVAL_RE = /^\(eval.*\):\d+/
STREAMING_RE = /^-:\d+/
attr_reader :name
diff --git a/lib/syntax_suggest/ripper_errors.rb b/lib/syntax_suggest/ripper_errors.rb
index 48eb206e48..4e2bc90948 100644
--- a/lib/syntax_suggest/ripper_errors.rb
+++ b/lib/syntax_suggest/ripper_errors.rb
@@ -1,7 +1,10 @@
# frozen_string_literal: true
module SyntaxSuggest
- # Capture parse errors from ripper
+ # Capture parse errors from Ripper
+ #
+ # Prism returns the errors with their messages, but Ripper
+ # does not. To get them we must make a custom subclass.
#
# Example:
#
diff --git a/lib/syntax_suggest/scan_history.rb b/lib/syntax_suggest/scan_history.rb
index d15597c440..dc36e6ba2e 100644
--- a/lib/syntax_suggest/scan_history.rb
+++ b/lib/syntax_suggest/scan_history.rb
@@ -118,7 +118,7 @@ module SyntaxSuggest
# Returns an array of all the CodeLines that exist after
# the currently scanned block
private def after_lines
- @code_lines[@after_index.next..-1] || []
+ @code_lines[@after_index.next..] || []
end
private def current
diff --git a/lib/syntax_suggest/syntax_suggest.gemspec b/lib/syntax_suggest/syntax_suggest.gemspec
index 0e611c13d0..756a85bf63 100644
--- a/lib/syntax_suggest/syntax_suggest.gemspec
+++ b/lib/syntax_suggest/syntax_suggest.gemspec
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
spec.description = 'When you get an "unexpected end" in your syntax this gem helps you find it'
spec.homepage = "https://github.com/ruby/syntax_suggest.git"
spec.license = "MIT"
- spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "https://github.com/ruby/syntax_suggest.git"
diff --git a/lib/syntax_suggest/version.rb b/lib/syntax_suggest/version.rb
index ac8c2f62e5..4320adb218 100644
--- a/lib/syntax_suggest/version.rb
+++ b/lib/syntax_suggest/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module SyntaxSuggest
- VERSION = "1.1.0"
+ VERSION = "2.0.0"
end