This represents the result of a call to ::parse or ::parse_file. It contains the requested structure, any comments that were encounters, and any errors that were encountered.
This is a result specific to the ‘parse` and `parse_file` methods.
An error that indicates we weren’t able to fetch some data from a source
Signals that a remote operation cannot be conducted, probably due to not being connected (or just not finding host).
The installer installs the files contained in the .gem into the Gem.home.
Gem::Installer
does the work of putting files in all the right places on the filesystem including unpacking the gem into its gem dir, installing the gemspec in the specifications dir, storing the cached gem in the cache dir, and installing either wrappers or symlinks for executables.
The installer invokes pre and post install hooks. Hooks can be added either through a rubygems_plugin.rb file in an installed gem or via a rubygems/defaults/#{RUBY_ENGINE}.rb or rubygems/defaults/operating_system.rb file. See Gem.pre_install
and Gem.post_install
for details.
Represents a gem of name name
at version
of platform
. These wrap the data returned from the indexes.
RemoteFetcher
handles the details of fetching gems and gem information from a remote source.
SilentUI
is a UI choice that is absolutely silent.
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, the parser 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
Represents a single line of code of a given source file
This object contains metadata about the line such as amount of indentation, if it is empty or not, and lexical data, such as if it has an ‘end` or a keyword in it.
Visibility of lines can be toggled off. Marking a line as invisible indicates that it should not be used for syntax checks. It’s functionally the same as commenting it out.
Example:
line = CodeLine.from_source("def foo\n").first line.number => 1 line.empty? # => false line.visible? # => true line.mark_invisible line.visible? # => false
Searches code for a syntax error
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 part.
The bulk of the heavy lifting is done in:
- CodeFrontier (Holds information for generating blocks and determining if we can stop searching) - ParseBlocksFromLine (Creates blocks into the frontier) - BlockExpand (Expands existing blocks to search more code)
## Syntax error detection
When the frontier holds the syntax error, we can stop searching
search = CodeSearch.new(<<~EOM) def dog def lol end EOM search.call search.invalid_blocks.map(&:to_s) # => # => ["def lol\n"]
Outputs code with highlighted lines
Whatever is passed to this class will be rendered even if it is “marked invisible” any filtering of output should be done before calling this class.
DisplayCodeWithLineNumbers.new( lines: lines, highlight_lines: [lines[2], lines[3]] ).call # => 1 2 def cat > 3 Dir.chdir > 4 end 5 end 6
Ripper.lex
is not guaranteed to lex the entire source document
This class guarantees the whole document is lex-ed by iteratively lexing the document where ripper stopped.
Prism
likely doesn’t have the same problem. Once ripper support is removed we can likely reduce the complexity here if not remove the whole concept.
Example usage:
lex = LexAll.new(source: source) lex.each do |value| puts value.line end
Value object for accessing lex values
This lex:
[1, 0], :on_ident, "describe", CMDARG
Would translate into:
lex.line # => 1 lex.type # => :on_indent lex.token # => "describe"
Not a URI
.
URI
is valid, bad usage is not.
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
Raised on an attempt to access an object which was moved in Ractor#send
or Ractor.yield
.
r = Ractor.new { sleep } ary = [1, 2, 3] r.send(ary, move: true) ary.inspect # Ractor::MovedError (can not send any methods to a moved object)
exception to wait for reading. see IO.select
.
exception to wait for writing. see IO.select
.