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
-
.clean_comments!(source, comments)
Remove comments that apear on their own in source.
-
.from_source(source)
Returns an array of
CodeLineobjects from the source string. - .new(line:, index:, tokens:, consecutive:) ⇒ CodeLine constructor
Instance Attribute Summary
-
#consecutive? ⇒ Boolean
readonly
Can this line be logically joined together with the following line? Determined by walking the AST.
-
#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
- #indent readonly
- #index readonly
-
#is_end? ⇒ Boolean
readonly
Returns true if the code line is determined to contain an
endkeyword. -
#is_kw? ⇒ Boolean
readonly
Returns true if the code line is determined to contain a keyword that matches with an
end - #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.
- #tokens readonly
-
#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
Constructor Details
.new(line:, index:, tokens:, consecutive:) ⇒ CodeLine
# File 'lib/syntax_suggest/code_line.rb', line 75
def initialize(line:, index:, tokens:, consecutive:) @tokens = tokens @line = line @index = index @consecutive = consecutive @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
.clean_comments!(source, comments)
Remove comments that apear on their own in source. They will never be the cause of syntax errors and are just visual noise. Example:
source = +<<~RUBY
# Comment-only line
foo # Inline comment
RUBY
CodeLine.clean_comments!(source, Prism.parse(source).comments)
source # => "\nfoo # Inline comment\n"
# File 'lib/syntax_suggest/code_line.rb', line 65
def self.clean_comments!(source, comments) # Iterate backwards since we are modifying the source in place and must preserve # the offsets. Prism comments are sorted by their location in the source. comments.reverse_each do |comment| next if comment.trailing? source.bytesplice(comment.location.start_offset, comment.location.length, "") end end
.from_source(source)
Returns an array of CodeLine objects
from the source string
# File 'lib/syntax_suggest/code_line.rb', line 29
def self.from_source(source) source = +source parse_result = Prism.parse_lex(source) ast, tokens = parse_result.value clean_comments!(source, parse_result.comments) visitor = Visitor.new visitor.visit(ast) tokens.sort_by! { |token, _state| token.location.start_line } prev_token = nil tokens.map! do |token, _state| prev_token = Token.new(token, prev_token, visitor) end tokens_for_line = tokens.each_with_object(Hash.new { |h, k| h[k] = [] }) { |token, hash| hash[token.line] << token } source.lines.map.with_index do |line, index| CodeLine.new( line: line, index: index, tokens: tokens_for_line[index + 1], consecutive: visitor.consecutive_lines.include?(index + 1) ) end end
Instance Attribute Details
#consecutive? ⇒ Boolean (readonly)
Can this line be logically joined together with the following line? Determined by walking the AST
# File 'lib/syntax_suggest/code_line.rb', line 191
def consecutive? @consecutive end
#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 149
def empty? @empty end
#indent (readonly)
[ GitHub ]# File 'lib/syntax_suggest/code_line.rb', line 74
attr_reader :line, :index, :tokens, :line_number, :indent
#index (readonly)
[ GitHub ]# File 'lib/syntax_suggest/code_line.rb', line 74
attr_reader :line, :index, :tokens, :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 121
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 115
def is_kw? @is_kw end
#line (readonly)
[ GitHub ]# File 'lib/syntax_suggest/code_line.rb', line 74
attr_reader :line, :index, :tokens, :line_number, :indent
#line_number (readonly) Also known as: #number
[ GitHub ]
#not_empty? ⇒ Boolean (readonly)
[ GitHub ]
# File 'lib/syntax_suggest/code_line.rb', line 154
def not_empty? !empty? end
#number (readonly)
Alias for #line_number.
# File 'lib/syntax_suggest/code_line.rb', line 109
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 180
attr_reader :original
#tokens (readonly)
[ GitHub ]# File 'lib/syntax_suggest/code_line.rb', line 74
attr_reader :line, :index, :tokens, :line_number, :indent
#trailing_slash? ⇒ Boolean (readonly)
Determines if the given line has a trailing slash. Simply check if the line contains a backslash after the content of the last token.
lines = CodeLine.from_source(<<~EOM)
it "foo" \
EOM
expect(lines.first.trailing_slash?).to eq(true)
# File 'lib/syntax_suggest/code_line.rb', line 204
def trailing_slash? return unless (last = @tokens.last) @line.byteindex(TRAILING_SLASH, last.location.end_column) != nil 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 130
def mark_invisible @line = "" end
#set_kw_end (private)
[ GitHub ]#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 167
def to_s line end