Skip to content

Commit

Permalink
Sync SyntaxSuggest
Browse files Browse the repository at this point in the history
```
$ tool/sync_default_gems.rb syntax_suggest
```
  • Loading branch information
schneems authored and hsbt committed Aug 19, 2022
1 parent a50df1a commit 490af8d
Show file tree
Hide file tree
Showing 26 changed files with 2,869 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lib/syntax_suggest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

require_relative "syntax_suggest/core_ext"
199 changes: 199 additions & 0 deletions lib/syntax_suggest/api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# 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"

0 comments on commit 490af8d

Please sign in to comment.