-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
``` $ tool/sync_default_gems.rb syntax_suggest ```
- Loading branch information
Showing
26 changed files
with
2,869 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative "syntax_suggest/core_ext" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
Oops, something went wrong.