ruby-changes:72975
From: schneems <ko1@a...>
Date: Fri, 19 Aug 2022 10:02:39 +0900 (JST)
Subject: [ruby-changes:72975] 490af8dbdb (master): Sync SyntaxSuggest
https://git.ruby-lang.org/ruby.git/commit/?id=490af8dbdb From 490af8dbdb66263f29d0b4e43752fbb298b94862 Mon Sep 17 00:00:00 2001 From: schneems <richard.schneeman+foo@g...> Date: Tue, 26 Jul 2022 15:21:09 -0500 Subject: Sync SyntaxSuggest ``` $ tool/sync_default_gems.rb syntax_suggest ``` --- lib/syntax_suggest.rb | 3 + lib/syntax_suggest/api.rb | 199 ++++++++++++++ lib/syntax_suggest/around_block_scan.rb | 224 +++++++++++++++ lib/syntax_suggest/block_expand.rb | 74 +++++ lib/syntax_suggest/capture_code_context.rb | 233 ++++++++++++++++ lib/syntax_suggest/clean_document.rb | 304 +++++++++++++++++++++ lib/syntax_suggest/cli.rb | 129 +++++++++ lib/syntax_suggest/code_block.rb | 100 +++++++ lib/syntax_suggest/code_frontier.rb | 178 ++++++++++++ lib/syntax_suggest/code_line.rb | 239 ++++++++++++++++ lib/syntax_suggest/code_search.rb | 139 ++++++++++ lib/syntax_suggest/core_ext.rb | 101 +++++++ .../display_code_with_line_numbers.rb | 70 +++++ lib/syntax_suggest/display_invalid_blocks.rb | 84 ++++++ lib/syntax_suggest/explain_syntax.rb | 103 +++++++ lib/syntax_suggest/left_right_lex_count.rb | 168 ++++++++++++ lib/syntax_suggest/lex_all.rb | 55 ++++ lib/syntax_suggest/lex_value.rb | 70 +++++ .../parse_blocks_from_indent_line.rb | 60 ++++ lib/syntax_suggest/pathname_from_message.rb | 59 ++++ lib/syntax_suggest/priority_engulf_queue.rb | 63 +++++ lib/syntax_suggest/priority_queue.rb | 105 +++++++ lib/syntax_suggest/ripper_errors.rb | 36 +++ lib/syntax_suggest/syntax_suggest.gemspec | 32 +++ lib/syntax_suggest/unvisited_lines.rb | 36 +++ lib/syntax_suggest/version.rb | 5 + 26 files changed, 2869 insertions(+) create mode 100644 lib/syntax_suggest.rb create mode 100644 lib/syntax_suggest/api.rb create mode 100644 lib/syntax_suggest/around_block_scan.rb create mode 100644 lib/syntax_suggest/block_expand.rb create mode 100644 lib/syntax_suggest/capture_code_context.rb create mode 100644 lib/syntax_suggest/clean_document.rb create mode 100644 lib/syntax_suggest/cli.rb create mode 100644 lib/syntax_suggest/code_block.rb create mode 100644 lib/syntax_suggest/code_frontier.rb create mode 100644 lib/syntax_suggest/code_line.rb create mode 100644 lib/syntax_suggest/code_search.rb create mode 100644 lib/syntax_suggest/core_ext.rb create mode 100644 lib/syntax_suggest/display_code_with_line_numbers.rb create mode 100644 lib/syntax_suggest/display_invalid_blocks.rb create mode 100644 lib/syntax_suggest/explain_syntax.rb create mode 100644 lib/syntax_suggest/left_right_lex_count.rb create mode 100644 lib/syntax_suggest/lex_all.rb create mode 100644 lib/syntax_suggest/lex_value.rb create mode 100644 lib/syntax_suggest/parse_blocks_from_indent_line.rb create mode 100644 lib/syntax_suggest/pathname_from_message.rb create mode 100644 lib/syntax_suggest/priority_engulf_queue.rb create mode 100644 lib/syntax_suggest/priority_queue.rb create mode 100644 lib/syntax_suggest/ripper_errors.rb create mode 100644 lib/syntax_suggest/syntax_suggest.gemspec create mode 100644 lib/syntax_suggest/unvisited_lines.rb create mode 100644 lib/syntax_suggest/version.rb diff --git a/lib/syntax_suggest.rb b/lib/syntax_suggest.rb new file mode 100644 index 0000000000..1a45dfa676 --- /dev/null +++ b/lib/syntax_suggest.rb @@ -0,0 +1,3 @@ https://github.com/ruby/ruby/blob/trunk/lib/syntax_suggest.rb#L1 +# frozen_string_literal: true + +require_relative "syntax_suggest/core_ext" diff --git a/lib/syntax_suggest/api.rb b/lib/syntax_suggest/api.rb new file mode 100644 index 0000000000..5b725e13d7 --- /dev/null +++ b/lib/syntax_suggest/api.rb @@ -0,0 +1,199 @@ https://github.com/ruby/ruby/blob/trunk/lib/syntax_suggest/api.rb#L1 +# frozen_string_literal: true + +require_relative "version" + +require "tmpdir" +require "stringio" +require "pathname" +require "ripper" +require "timeout" + +module SyntaxSuggest + # Used to indicate a default value that cannot + # be confused with another input. + DEFAULT_VALUE = Object.new.freeze + + class Error < StandardError; end + TIMEOUT_DEFAULT = ENV.fetch("SYNTAX_SUGGEST_TIMEOUT", 1).to_i + + # SyntaxSuggest.handle_error [Public] + # + # Takes a `SyntaxError` exception, uses the + # error message to locate the file. Then the file + # will be analyzed to find the location of the syntax + # error and emit that location to stderr. + # + # Example: + # + # begin + # require 'bad_file' + # rescue => e + # SyntaxSuggest.handle_error(e) + # end + # + # By default it will re-raise the exception unless + # `re_raise: false`. The message output location + # can be configured using the `io: $stderr` input. + # + # If a valid filename cannot be determined, the original + # exception will be re-raised (even with + # `re_raise: false`). + def self.handle_error(e, re_raise: true, io: $stderr) + unless e.is_a?(SyntaxError) + io.puts("SyntaxSuggest: Must pass a SyntaxError, got: #{e.class}") + raise e + end + + file = PathnameFromMessage.new(e.message, io: io).call.name + raise e unless file + + io.sync = true + + call( + io: io, + source: file.read, + filename: file + ) + + raise e if re_raise + end + + # SyntaxSuggest.call [Private] + # + # Main private interface + def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: DEFAULT_VALUE, timeout: TIMEOUT_DEFAULT, io: $stderr) + search = nil + filename = nil if filename == DEFAULT_VALUE + Timeout.timeout(timeout) do + record_dir ||= ENV["DEBUG"] ? "tmp" : nil + search = CodeSearch.new(source, record_dir: record_dir).call + end + + blocks = search.invalid_blocks + DisplayInvalidBlocks.new( + io: io, + blocks: blocks, + filename: filename, + terminal: terminal, + code_lines: search.code_lines + ).call + rescue Timeout::Error => e + io.puts "Search timed out SYNTAX_SUGGEST_TIMEOUT=#{timeout}, run with DEBUG=1 for more info" + io.puts e.backtrace.first(3).join($/) + end + + # SyntaxSuggest.record_dir [Private] + # + # Used to generate a unique directory to record + # search steps for debugging + def self.record_dir(dir) + time = Time.now.strftime("%Y-%m-%d-%H-%M-%s-%N") + dir = Pathname(dir) + dir.join(time).tap { |path| + path.mkpath + FileUtils.ln_sf(time, dir.join("last")) + } + end + + # SyntaxSuggest.valid_without? [Private] + # + # This will tell you if the `code_lines` would be valid + # if you removed the `without_lines`. In short it's a + # way to detect if we've found the lines with syntax errors + # in our document yet. + # + # code_lines = [ + # CodeLine.new(line: "def foo\n", index: 0) + # CodeLine.new(line: " def bar\n", index: 1) + # CodeLine.new(line: "end\n", index: 2) + # ] + # + # SyntaxSuggest.valid_without?( + # without_lines: code_lines[1], + # code_lines: code_lines + # ) # => true + # + # SyntaxSuggest.valid?(code_lines) # => false + def self.valid_without?(without_lines:, code_lines:) + lines = code_lines - Array(without_lines).flatten + + if lines.empty? + true + else + valid?(lines) + end + end + + # SyntaxSuggest.invalid? [Private] + # + # Opposite of `SyntaxSuggest.valid?` + def self.invalid?(source) + source = source.join if source.is_a?(Array) + source = source.to_s + + Ripper.new(source).tap(&:parse).error? + end + + # SyntaxSuggest.valid? [Private] + # + # Returns truthy if a given input source is valid syntax + # + # SyntaxSuggest.valid?(<<~EOM) # => true + # def foo + # end + # EOM + # + # SyntaxSuggest.valid?(<<~EOM) # => false + # def foo + # def bar # Syntax error here + # end + # EOM + # + # You can also pass in an array of lines and they'll be + # joined before evaluating + # + # SyntaxSuggest.valid?( + # [ + # "def foo\n", + # "end\n" + # ] + # ) # => true + # + # SyntaxSuggest.valid?( + # [ + # "def foo\n", + # " def bar\n", # Syntax error here + # "end\n" + # ] + # ) # => false + # + # As an FYI the CodeLine class instances respond to `to_s` + # so passing a CodeLine in as an object or as an array + # will convert it to it's code representation. + def self.valid?(source) + !invalid?(source) + end +end + +# Integration +require_relative "cli" + +# Core logic +require_relative "code_search" +require_relative "code_frontier" +require_relative "explain_syntax" +require_relative "clean_document" + +# Helpers +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" +require_relative "priority_engulf_queue" +require_relative "pathname_from_message" +require_relative "display_invalid_blocks" +require_relative "parse_blocks_from_indent_line" diff --git a/lib/syntax_suggest/around_block_scan.rb b/lib/syntax_suggest/around_block_scan.rb new file mode 100644 index 0000000000..2a57d1b19e --- /dev/null +++ b/lib/syntax_suggest/around_block_scan.rb @@ -0,0 +1,224 @@ https://github.com/ruby/ruby/blob/trunk/lib/syntax_suggest/around_block_scan.rb#L1 +# frozen_string_literal: true + +module SyntaxSuggest + # This class is useful for exploring contents before and after + # a block + # + # It searches above and below the passed in block to match for + # whatever criteria you give it: + # + # Examp (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/