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/scan_history.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/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/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, LeftRightLexCount, LexAll, LexValue, MiniStringIO, ParseBlocksFromIndentLine, PathnameFromMessage, PriorityEngulfQueue, PriorityQueue, RipperErrors, ScanHistory, 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 =
"2.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



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/syntax_suggest/api.rb', line 91

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



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

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

Returns:

  • (Boolean)


160
161
162
163
164
165
# File 'lib/syntax_suggest/api.rb', line 160

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



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
63
64
# 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) ⇒ Object

SyntaxSuggest.record_dir [Private]

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



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

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

.use_prism_parser?Boolean

SyntaxSuggest.use_prism_parser? [Private]

Tells us if the prism parser is available for use or if we should fallback to ‘Ripper`

Returns:

  • (Boolean)


42
43
44
# File 'lib/syntax_suggest/api.rb', line 42

def self.use_prism_parser?
  defined?(Prism)
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:

  • (Boolean)


211
212
213
# File 'lib/syntax_suggest/api.rb', line 211

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)


146
147
148
149
150
151
152
153
154
# File 'lib/syntax_suggest/api.rb', line 146

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

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