Module: SyntaxSuggest

Defined in:
lib/syntax_suggest/api.rb,
lib/syntax_suggest/cli.rb,
lib/syntax_suggest/lex_all.rb,
lib/syntax_suggest/version.rb,
lib/syntax_suggest/core_ext.rb,
lib/syntax_suggest/code_line.rb,
lib/syntax_suggest/lex_value.rb,
lib/syntax_suggest/code_block.rb,
lib/syntax_suggest/code_search.rb,
lib/syntax_suggest/block_expand.rb,
lib/syntax_suggest/code_frontier.rb,
lib/syntax_suggest/ripper_errors.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/left_right_lex_count.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/parse_blocks_from_indent_line.rb,
lib/syntax_suggest/display_code_with_line_numbers.rb

Defined Under Namespace

Classes: AroundBlockScan, BlockExpand, CaptureCodeContext, CleanDocument, Cli, CodeBlock, CodeFrontier, CodeLine, CodeSearch, DisplayCodeWithLineNumbers, DisplayInvalidBlocks, Error, ExplainSyntax, LeftRightLexCount, LexAll, LexValue, MiniStringIO, ParseBlocksFromIndentLine, PathnameFromMessage, PriorityEngulfQueue, PriorityQueue, RipperErrors, UnvisitedLines

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 =
"1.0.2"

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



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 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 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`).



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 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

SyntaxSuggest.invalid? [Private]

Opposite of SyntaxSuggest.valid?

Returns:

  • (Boolean)


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

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

.module_for_detailed_messageObject

SyntaxSuggest.record_dir [Private]

Used to monkeypatch SyntaxError via Module.prepend



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 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 + 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



89
90
91
92
93
94
95
96
# 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
    FileUtils.ln_sf(time, dir.join("last"))
  }
end

.valid?(source) ⇒ Boolean

SyntaxSuggest.valid? [Private]

Returns truthy if a given input source is valid syntax

SyntaxSuggest.valid?("def foo\nend\n") # => true

SyntaxSuggest.valid?("def foo\n  def bar # Syntax error here\nend\n") # => false

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:

  • (Boolean)


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:

  • (Boolean)


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

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

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