diff options
Diffstat (limited to 'lib/syntax_suggest')
-rw-r--r-- | lib/syntax_suggest/api.rb | 47 | ||||
-rw-r--r-- | lib/syntax_suggest/around_block_scan.rb | 4 | ||||
-rw-r--r-- | lib/syntax_suggest/block_expand.rb | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/capture_code_context.rb | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/clean_document.rb | 14 | ||||
-rw-r--r-- | lib/syntax_suggest/code_block.rb | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/code_frontier.rb | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/code_line.rb | 17 | ||||
-rw-r--r-- | lib/syntax_suggest/code_search.rb | 4 | ||||
-rw-r--r-- | lib/syntax_suggest/core_ext.rb | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/display_invalid_blocks.rb | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/explain_syntax.rb | 22 | ||||
-rw-r--r-- | lib/syntax_suggest/lex_all.rb | 39 | ||||
-rw-r--r-- | lib/syntax_suggest/parse_blocks_from_indent_line.rb | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/pathname_from_message.rb | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/ripper_errors.rb | 5 | ||||
-rw-r--r-- | lib/syntax_suggest/scan_history.rb | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/syntax_suggest.gemspec | 2 | ||||
-rw-r--r-- | lib/syntax_suggest/version.rb | 2 |
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 |