Class: SyntaxSuggest::CodeLine
Relationships & Source Files | |
Inherits: | Object |
Defined in: | lib/syntax_suggest/code_line.rb |
Overview
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
Constant Summary
-
TRAILING_SLASH =
# File 'lib/syntax_suggest/code_line.rb', line 25("\\" + $/).freeze
Class Method Summary
-
.from_source(source, lines: nil)
Returns an array of
CodeLine
objects from the source string. - .new(line:, index:, lex:) ⇒ CodeLine constructor
Instance Attribute Summary
-
#empty? ⇒ Boolean
readonly
An #empty? line is one that was originally left empty in the source code, while a “hidden” line is one that we’ve since marked as “invisible”.
- #hidden? ⇒ Boolean readonly
-
#ignore_newline_not_beg? ⇒ Boolean
readonly
[Not stable API].
- #indent readonly
- #index readonly
-
#is_end? ⇒ Boolean
readonly
Returns true if the code line is determined to contain an
end
keyword. -
#is_kw? ⇒ Boolean
readonly
Returns true if the code line is determined to contain a keyword that matches with an
end
- #lex readonly
- #line readonly
- #line_number (also: #number) readonly
- #not_empty? ⇒ Boolean readonly
-
#number
readonly
Alias for #line_number.
-
#original
readonly
When the code line is marked invisible we retain the original value of it’s line this is useful for debugging and for showing extra context.
-
#trailing_slash? ⇒ Boolean
readonly
Determines if the given line has a trailing slash.
-
#visible? ⇒ Boolean
readonly
Means the line was marked as “invisible” Confusingly, “empty” lines are visible…they just don’t contain any source code other than a newline (“n”).
Instance Method Summary
-
#<=>(other)
Comparison operator, needed for equality and sorting.
-
#indent_index
Used for stable sort via indentation level.
-
#mark_invisible
Used to hide lines.
-
#to_s
Renders the given line.
-
#set_kw_end
private
Endless method detection.
Constructor Details
.new(line:, index:, lex:) ⇒ CodeLine
# File 'lib/syntax_suggest/code_line.rb', line 42
def initialize(line:, index:, lex:) @lex = lex @line = line @index = index @original = line @line_number = @index + 1 strip_line = line.dup strip_line.lstrip! @indent = if (@empty = strip_line.empty?) line.length - 1 # Newline removed from strip_line is not "whitespace" else line.length - strip_line.length end set_kw_end end
Class Method Details
.from_source(source, lines: nil)
Returns an array of CodeLine
objects from the source string
# File 'lib/syntax_suggest/code_line.rb', line 29
def self.from_source(source, lines: nil) lines ||= source.lines lex_array_for_line = LexAll.new(source: source, source_lines: lines).each_with_object(Hash.new { |h, k| h[k] = [] }) { |lex, hash| hash[lex.line] << lex } lines.map.with_index do |line, index| CodeLine.new( line: line, index: index, lex: lex_array_for_line[index + 1] ) end end
Instance Attribute Details
#empty? ⇒ Boolean
(readonly)
An empty?
line is one that was originally left empty in the source code, while a “hidden” line is one that we’ve since marked as “invisible”
# File 'lib/syntax_suggest/code_line.rb', line 115
def empty? @empty end
#ignore_newline_not_beg? ⇒ Boolean
(readonly)
- Not stable API
-
Lines that have a
on_ignored_nl
type token and NOT aBEG
type seem to be a good proxy for the ability to join multiple lines into one.This predicate method is used to determine when those two criteria have been met.
The one known case this doesn’t handle is:
Ripper.lex <<~EOM a && b || c EOM
For some reason this introduces
on_ignore_newline
but with BEG type
# File 'lib/syntax_suggest/code_line.rb', line 172
def ignore_newline_not_beg? @ignore_newline_not_beg end
#indent (readonly)
[ GitHub ]# File 'lib/syntax_suggest/code_line.rb', line 41
attr_reader :line, :index, :lex, :line_number, :indent
#index (readonly)
[ GitHub ]# File 'lib/syntax_suggest/code_line.rb', line 41
attr_reader :line, :index, :lex, :line_number, :indent
#is_end? ⇒ Boolean
(readonly)
Returns true if the code line is determined to contain an end
keyword
# File 'lib/syntax_suggest/code_line.rb', line 87
def is_end? @is_end end
#is_kw? ⇒ Boolean
(readonly)
Returns true if the code line is determined to contain a keyword that matches with an end
For example: def
, do
, begin
, ensure
, etc.
# File 'lib/syntax_suggest/code_line.rb', line 81
def is_kw? @is_kw end
#lex (readonly)
[ GitHub ]# File 'lib/syntax_suggest/code_line.rb', line 41
attr_reader :line, :index, :lex, :line_number, :indent
#line (readonly)
[ GitHub ]# File 'lib/syntax_suggest/code_line.rb', line 41
attr_reader :line, :index, :lex, :line_number, :indent
#line_number (readonly) Also known as: #number
[ GitHub ]
#not_empty? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/syntax_suggest/code_line.rb', line 120
def not_empty? !empty? end
#number (readonly)
Alias for #line_number.
# File 'lib/syntax_suggest/code_line.rb', line 75
alias_method :number, :line_number
#original (readonly)
When the code line is marked invisible we retain the original value of it’s line this is useful for debugging and for showing extra context
DisplayCodeWithLineNumbers
will render all lines given to it, not just visible lines, it uses the original method to obtain them.
# File 'lib/syntax_suggest/code_line.rb', line 146
attr_reader :original
#trailing_slash? ⇒ Boolean
(readonly)
Determines if the given line has a trailing slash
lines = CodeLine.from_source(<<~EOM)
it "foo" \
EOM
expect(lines.first.trailing_slash?).to eq(true)
# File 'lib/syntax_suggest/code_line.rb', line 183
def trailing_slash? last = @lex.last return false unless last return false unless last.type == :on_sp last.token == TRAILING_SLASH end
#visible? ⇒ Boolean
(readonly)
Means the line was marked as “invisible” Confusingly, “empty” lines are visible…they just don’t contain any source code other than a newline (“n”).
Instance Method Details
#<=>(other)
Comparison operator, needed for equality and sorting
#indent_index
Used for stable sort via indentation level
Ruby’s sort is not “stable” meaning that when multiple elements have the same value, they are not guaranteed to return in the same order they were put in.
So when multiple code lines have the same indentation level, they’re sorted by their index value which is unique and consistent.
This is mostly needed for consistency of the test suite
#mark_invisible
Used to hide lines
The search alorithm will group lines into blocks then if those blocks are determined to represent valid code they will be hidden
# File 'lib/syntax_suggest/code_line.rb', line 96
def mark_invisible @line = "" end
#set_kw_end (private)
Endless method detection
From github.com/ruby/irb/commit/826ae909c9c93a2ddca6f9cfcd9c94dbf53d44ab Detecting a “oneliner” seems to need a state machine. This can be done by looking mostly at the “state” (last value):
ENDFN -> BEG (token = '=' ) -> END
# File 'lib/syntax_suggest/code_line.rb', line 199
private def set_kw_end oneliner_count = 0 in_oneliner_def = nil kw_count = 0 end_count = 0 @ignore_newline_not_beg = false @lex.each do |lex| kw_count += 1 if lex.is_kw? end_count += 1 if lex.is_end? if lex.type == :on_ignored_nl @ignore_newline_not_beg = !lex.expr_beg? end if in_oneliner_def.nil? in_oneliner_def = :ENDFN if lex.state.allbits?(Ripper::EXPR_ENDFN) elsif lex.state.allbits?(Ripper::EXPR_ENDFN) # Continue elsif lex.state.allbits?(Ripper::EXPR_BEG) in_oneliner_def = :BODY if lex.token == "=" elsif lex.state.allbits?(Ripper::EXPR_END) # We found an endless method, count it oneliner_count += 1 if in_oneliner_def == :BODY in_oneliner_def = nil else in_oneliner_def = nil end end kw_count -= oneliner_count @is_kw = (kw_count - end_count) > 0 @is_end = (end_count - kw_count) > 0 end
#to_s
Renders the given line
Also allows us to represent source code as an array of code lines.
When we have an array of code line elements calling join
on the array will call to_s
on each element, which essentially converts it back into it’s original source string.
# File 'lib/syntax_suggest/code_line.rb', line 133
def to_s line end