123456789_123456789_123456789_123456789_123456789_

Module: SyntaxSuggest

Relationships & Source Files
Namespace Children
Modules:
Classes:
Exceptions:
Defined in: lib/syntax_suggest/api.rb,
lib/syntax_suggest/around_block_scan.rb,
lib/syntax_suggest/block_expand.rb,
lib/syntax_suggest/capture_code_context.rb,
lib/syntax_suggest/capture_code_context.rb,
lib/syntax_suggest/clean_document.rb,
lib/syntax_suggest/cli.rb,
lib/syntax_suggest/code_block.rb,
lib/syntax_suggest/code_frontier.rb,
lib/syntax_suggest/code_line.rb,
lib/syntax_suggest/code_search.rb,
lib/syntax_suggest/core_ext.rb,
lib/syntax_suggest/display_code_with_line_numbers.rb,
lib/syntax_suggest/display_invalid_blocks.rb,
lib/syntax_suggest/explain_syntax.rb,
lib/syntax_suggest/left_right_lex_count.rb,
lib/syntax_suggest/lex_all.rb,
lib/syntax_suggest/lex_value.rb,
lib/syntax_suggest/parse_blocks_from_indent_line.rb,
lib/syntax_suggest/pathname_from_message.rb,
lib/syntax_suggest/priority_engulf_queue.rb,
lib/syntax_suggest/priority_queue.rb,
lib/syntax_suggest/ripper_errors.rb,
lib/syntax_suggest/scan_history.rb,
lib/syntax_suggest/unvisited_lines.rb,
lib/syntax_suggest/version.rb,
lib/syntax_suggest/capture/before_after_keyword_ends.rb,
lib/syntax_suggest/capture/falling_indent_lines.rb

Constant Summary

Class Method Summary

Class Method Details

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

call [Private]

Main private interface

[ GitHub ]

  
# File 'lib/syntax_suggest/api.rb', line 64

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)

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

[ GitHub ]

  
# File 'lib/syntax_suggest/api.rb', line 41

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

invalid? [Private]

Opposite of .valid?

[ GitHub ]

  
# File 'lib/syntax_suggest/api.rb', line 132

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

  Ripper.new(source).tap(&:parse).error?
end

.module_for_detailed_message

.record_dir [Private]

Used to monkeypatch SyntaxError via Module.prepend

[ GitHub ]

  
# File 'lib/syntax_suggest/core_ext.rb', line 27

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)

record_dir [Private]

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

[ GitHub ]

  
# File 'lib/syntax_suggest/api.rb', line 89

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

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 ::SyntaxSuggest::CodeLine class instances respond to to_s so passing a ::SyntaxSuggest::CodeLine in as an object or as an array will convert it to it’s code representation.

[ GitHub ]

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

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

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

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
[ GitHub ]

  
# File 'lib/syntax_suggest/api.rb', line 119

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

  if lines.empty?
    true
  else
    valid?(lines)
  end
end