Signals that a remote operation cannot be conducted, probably due to not being connected (or just not finding host).
Raised by Gem::Validator
when something is not right in a gem.
Raised by Resolver when a dependency requests a gem for which there is no spec.
This Gem::StreamUI
subclass records input and output to StringIO
for retrieval during tests.
This class is responsible for taking a code block that exists at a far indentaion and then iteratively increasing the block so that it captures everything within the same indentation block.
def dog puts "bow" puts "wow" end
block = BlockExpand.new
(code_lines: code_lines)
.call(CodeBlock.new(lines: code_lines[1]))
puts block.to_s # => puts “bow”
puts "wow"
Once a code block has captured everything at a given indentation level then it will expand to capture surrounding indentation.
block = BlockExpand.new
(code_lines: code_lines)
.call(block)
block.to_s # => def dog
puts "bow" puts "wow" end
Parses and sanitizes source into a lexically aware document
Internally the document is represented by an array with each index containing a CodeLine
correlating to a line from the source code.
There are three main phases in the algorithm:
Sanitize/format input source
Search for invalid blocks
Format invalid blocks into something meaninful
This class handles the first part.
The reason this class exists is to format input source for better/easier/cleaner exploration.
The CodeSearch
class operates at the line level so we must be careful to not introduce lines that look valid by themselves, but when removed will trigger syntax errors or strange behavior.
## Join Trailing slashes
Code with a trailing slash is logically treated as a single line:
1 it "code can be split" \ 2 "across multiple lines" do
In this case removing line 2 would add a syntax error. We get around this by internally joining the two lines into a single “line” object
## Logically Consecutive lines
Code that can be broken over multiple lines such as method calls are on different lines:
1 User. 2 where(name: "schneems"). 3 first
Removing line 2 can introduce a syntax error. To fix this, all lines are joined into one.
## Heredocs
A heredoc is an way of defining a multi-line string. They can cause many problems. If left as a single line, Ripper
would try to parse the contents as ruby code rather than as a string. Even without this problem, we still hit an issue with indentation
1 foo = <<~HEREDOC 2 "Be yourself; everyone else is already taken."" 3 ― Oscar Wilde 4 puts "I look like ruby code" # but i'm still a heredoc 5 HEREDOC
If we didn’t join these lines then our algorithm would think that line 4 is separate from the rest, has a higher indentation, then look at it first and remove it.
If the code evaluates line 5 by itself it will think line 5 is a constant, remove it, and introduce a syntax errror.
All of these problems are fixed by joining the whole heredoc into a single line.
## Comments and whitespace
Comments can throw off the way the lexer tells us that the line logically belongs with the next line. This is valid ruby but results in a different lex output than before:
1 User. 2 where(name: "schneems"). 3 # Comment here 4 first
To handle this we can replace comment lines with empty lines and then re-lex the source. This removal and re-lexing preserves line index and document size, but generates an easier to work with document.
Multiple lines form a singular CodeBlock
Source code is made of multiple CodeBlocks.
Example:
code_block.to_s # => # def foo # puts "foo" # end code_block.valid? # => true code_block.in_valid? # => false
There are three main phases in the algorithm:
Sanitize/format input source
Search for invalid blocks
Format invalid blocks into something meaninful
The Code frontier is a critical part of the second step
## Knowing where we’ve been
Once a code block is generated it is added onto the frontier. Then it will be sorted by indentation and frontier can be filtered. Large blocks that fully enclose a smaller block will cause the smaller block to be evicted.
CodeFrontier#<<(block) # Adds block to frontier CodeFrontier#pop # Removes block from frontier
## Knowing where we can go
Internally the frontier keeps track of “unvisited” lines which are exposed via ‘next_indent_line` when called, this method returns, a line of code with the highest indentation.
The returned line of code can be used to build a CodeBlock
and then that code block is added back to the frontier. Then, the lines are removed from the “unvisited” so we don’t double-create the same block.
CodeFrontier#next_indent_line # Shows next line CodeFrontier#register_indent_block(block) # Removes lines from unvisited
## Knowing when to stop
The frontier knows how to check the entire document for a syntax error. When blocks are added onto the frontier, they’re removed from the document. When all code containing syntax errors has been added to the frontier, the document will be parsable without a syntax error and the search can stop.
CodeFrontier#holds_all_syntax_errors? # Returns true when frontier holds all syntax errors
## Filtering false positives
Once the search is completed, the frontier may have multiple blocks that do not contain the syntax error. To limit the result to the smallest subset of “invalid blocks” call:
CodeFrontier#detect_invalid_blocks
Used for formatting invalid blocks
Converts a SyntaxError
message to a path
Handles the case where the filename has a colon in it such as on a windows file system: github.com/ruby/syntax_suggest/issues/111
Example:
message = "/tmp/scratch:2:in `require_relative': /private/tmp/bad.rb:1: syntax error, unexpected `end' (SyntaxError)" puts PathnameFromMessage.new(message).call.name # => "/tmp/scratch.rb"
Keeps track of what elements are in the queue in priority and also ensures that when one element engulfs/covers/eats another that the larger element evicts the smaller element
Holds elements in a priority heap on insert
Instead of constantly calling ‘sort!`, put the element where it belongs the first time around
Example:
queue = PriorityQueue.new queue << 33 queue << 44 queue << 1 puts queue.peek # => 44
Capture parse errors from ripper
Example:
puts RipperErrors.new(" def foo").call.errors # => ["syntax error, unexpected end-of-input, expecting ';' or '\\n'"]
Raised by Timeout.timeout
when the block times out.
Base class for all URI
exceptions.
Not a URI
.
Not a URI
component.
URI
is valid, bad usage is not.
RefError
is raised when a referenced object has been recycled by the garbage collector
Raised when a mathematical function is evaluated outside of its domain of definition.
For example, since cos
returns values in the range -1..1, its inverse function acos
is only defined on that interval:
Math.acos(42)
produces:
Math::DomainError: Numerical argument is out of domain - "acos"
Raised on attempt to Ractor#take
if there was an uncaught exception in the Ractor
. Its cause
will contain the original exception, and ractor
is the original ractor it was raised in.
r = Ractor.new { raise "Something weird happened" } begin r.take rescue => e p e # => #<Ractor::RemoteError: thrown by remote Ractor.> p e.ractor == r # => true p e.cause # => #<RuntimeError: Something weird happened> end