Module: SyntaxSuggest

Defined in:
lib/syntax_suggest/api.rb,
lib/syntax_suggest/cli.rb,
lib/syntax_suggest/token.rb,
lib/syntax_suggest/version.rb,
lib/syntax_suggest/visitor.rb,
lib/syntax_suggest/core_ext.rb,
lib/syntax_suggest/code_line.rb,
lib/syntax_suggest/code_block.rb,
lib/syntax_suggest/code_search.rb,
lib/syntax_suggest/block_expand.rb,
lib/syntax_suggest/scan_history.rb,
lib/syntax_suggest/code_frontier.rb,
lib/syntax_suggest/mini_stringio.rb,
lib/syntax_suggest/clean_document.rb,
lib/syntax_suggest/explain_syntax.rb,
lib/syntax_suggest/priority_queue.rb,
lib/syntax_suggest/unvisited_lines.rb,
lib/syntax_suggest/around_block_scan.rb,
lib/syntax_suggest/capture_code_context.rb,
lib/syntax_suggest/capture_code_context.rb,
lib/syntax_suggest/pathname_from_message.rb,
lib/syntax_suggest/priority_engulf_queue.rb,
lib/syntax_suggest/display_invalid_blocks.rb,
lib/syntax_suggest/left_right_token_count.rb,
lib/syntax_suggest/capture/falling_indent_lines.rb,
lib/syntax_suggest/parse_blocks_from_indent_line.rb,
lib/syntax_suggest/display_code_with_line_numbers.rb,
lib/syntax_suggest/capture/before_after_keyword_ends.rb

Defined Under Namespace

Modules: Capture Classes: AroundBlockScan, BlockExpand, CaptureCodeContext, CleanDocument, Cli, CodeBlock, CodeFrontier, CodeLine, CodeSearch, DisplayCodeWithLineNumbers, DisplayInvalidBlocks, Error, ExplainSyntax, GetParseErrors, LeftRightTokenCount, MiniStringIO, ParseBlocksFromIndentLine, PathnameFromMessage, PriorityEngulfQueue, PriorityQueue, ScanHistory, Token, UnvisitedLines, Visitor

Constant Summary collapse

DEFAULT_VALUE =

Used to indicate a default value that cannot be confused with another input.

Object.new.freeze
TIMEOUT_DEFAULT =
ENV.fetch("SYNTAX_SUGGEST_TIMEOUT", 1).to_i
VERSION =
"3.0.0"

Class Method Summary collapse

Class Method Details

.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: DEFAULT_VALUE, timeout: TIMEOUT_DEFAULT, io: $stderr) ⇒ Object

SyntaxSuggest.call [Private]

Main private interface



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/syntax_suggest/api.rb', line 66

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 SYNTAX_SUGGEST_DEBUG=1 for more info"
  io.puts e.backtrace.first(3).join($/)
end

.handle_error(e, re_raise: true, io: $stderr) ⇒ Object

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).



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/syntax_suggest/api.rb', line 43

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

.invalid?(source) ⇒ Boolean

SyntaxSuggest.invalid? [Private]

Opposite of SyntaxSuggest.valid?

Returns:



130
131
132
133
134
135
# File 'lib/syntax_suggest/api.rb', line 130

def self.invalid?(source)
  source = source.join if source.is_a?(Array)
  source = source.to_s

  Prism.parse(source).failure?
end

.module_for_detailed_messageObject

SyntaxSuggest.module_for_detailed_message [Private]

Used to monkeypatch SyntaxError via Module.prepend



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/syntax_suggest/core_ext.rb', line 7

def self.module_for_detailed_message
  Module.new {
    def detailed_message(highlight: true, syntax_suggest: true, **kwargs)
      return super unless syntax_suggest

      require "syntax_suggest/api" unless defined?(SyntaxSuggest::DEFAULT_VALUE)

      message = super

      if path
        file = Pathname.new(path)
        io = SyntaxSuggest::MiniStringIO.new

        SyntaxSuggest.call(
          io: io,
          source: file.read,
          filename: file,
          terminal: highlight
        )
        annotation = io.string

        annotation += "\n" unless annotation.end_with?("\n")

        annotation + message
      else
        message
      end
    rescue => e
      if ENV["SYNTAX_SUGGEST_DEBUG"]
        $stderr.warn(e.message)
        $stderr.warn(e.backtrace)
      end

      # Ignore internal errors
      message
    end
  }
end

.record_dir(dir) ⇒ Object

SyntaxSuggest.record_dir [Private]

Used to generate a unique directory to record search steps for debugging



91
92
93
94
95
96
97
98
99
100
# File 'lib/syntax_suggest/api.rb', line 91

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
    alias_dir = dir.join("last")
    FileUtils.rm_rf(alias_dir) if alias_dir.exist?
    FileUtils.ln_sf(time, alias_dir)
  }
end

.valid?(source) ⇒ Boolean

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.

Returns:



173
174
175
# File 'lib/syntax_suggest/api.rb', line 173

def self.valid?(source)
  !invalid?(source)
end

.valid_without?(without_lines:, code_lines:) ⇒ Boolean

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

Returns:



121
122
123
124
125
# File 'lib/syntax_suggest/api.rb', line 121

def self.valid_without?(without_lines:, code_lines:)
  lines = code_lines - Array(without_lines).flatten

  lines.empty? || valid?(lines)
end