Class: SyntaxSuggest::CodeLine
- Inherits:
-
Object
- Object
- SyntaxSuggest::CodeLine
- 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 collapse
- TRAILING_SLASH =
("\\" + $/).freeze
Instance Attribute Summary collapse
-
#indent ⇒ Object
readonly
Returns the value of attribute indent.
-
#index ⇒ Object
readonly
Returns the value of attribute index.
-
#line ⇒ Object
readonly
Returns the value of attribute line.
-
#line_number ⇒ Object
(also: #number)
readonly
Returns the value of attribute line_number.
-
#original ⇒ Object
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 ⇒ Object
readonly
Returns the value of attribute tokens.
Class Method Summary collapse
-
.clean_comments!(source, comments) ⇒ Object
Remove comments that apear on their own in source.
-
.from_source(source) ⇒ Object
Returns an array of CodeLine objects from the source string.
Instance Method Summary collapse
-
#<=>(other) ⇒ Object
Comparison operator, needed for equality and sorting.
-
#consecutive? ⇒ Boolean
Can this line be logically joined together with the following line? Determined by walking the AST.
-
#empty? ⇒ Boolean
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
Opposite or ‘visible?` (note: different than `empty?`).
-
#indent_index ⇒ Object
Used for stable sort via indentation level.
-
#initialize(line:, index:, tokens:, consecutive:) ⇒ CodeLine
constructor
A new instance of CodeLine.
-
#is_end? ⇒ Boolean
Returns true if the code line is determined to contain an ‘end` keyword.
-
#is_kw? ⇒ Boolean
Returns true if the code line is determined to contain a keyword that matches with an ‘end`.
-
#mark_invisible ⇒ Object
Used to hide lines.
-
#not_empty? ⇒ Boolean
Opposite of ‘empty?` (note: different than `visible?`).
-
#to_s ⇒ Object
Renders the given line.
-
#trailing_slash? ⇒ Boolean
Determines if the given line has a trailing slash.
-
#visible? ⇒ Boolean
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”).
Constructor Details
#initialize(line:, index:, tokens:, consecutive:) ⇒ CodeLine
Returns a new instance of CodeLine.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# 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 |
Instance Attribute Details
#indent ⇒ Object (readonly)
Returns the value of attribute indent.
74 75 76 |
# File 'lib/syntax_suggest/code_line.rb', line 74 def indent @indent end |
#index ⇒ Object (readonly)
Returns the value of attribute index.
74 75 76 |
# File 'lib/syntax_suggest/code_line.rb', line 74 def index @index end |
#line ⇒ Object (readonly)
Returns the value of attribute line.
74 75 76 |
# File 'lib/syntax_suggest/code_line.rb', line 74 def line @line end |
#line_number ⇒ Object (readonly) Also known as: number
Returns the value of attribute line_number.
74 75 76 |
# File 'lib/syntax_suggest/code_line.rb', line 74 def line_number @line_number end |
#original ⇒ Object (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.
180 181 182 |
# File 'lib/syntax_suggest/code_line.rb', line 180 def original @original end |
#tokens ⇒ Object (readonly)
Returns the value of attribute tokens.
74 75 76 |
# File 'lib/syntax_suggest/code_line.rb', line 74 def tokens @tokens end |
Class Method Details
.clean_comments!(source, comments) ⇒ Object
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"
65 66 67 68 69 70 71 72 |
# 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) ⇒ Object
Returns an array of CodeLine objects from the source string
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 |
# 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 Method Details
#<=>(other) ⇒ Object
Comparison operator, needed for equality and sorting
184 185 186 |
# File 'lib/syntax_suggest/code_line.rb', line 184 def <=>(other) index <=> other.index end |
#consecutive? ⇒ Boolean
Can this line be logically joined together with the following line? Determined by walking the AST
191 192 193 |
# File 'lib/syntax_suggest/code_line.rb', line 191 def consecutive? @consecutive end |
#empty? ⇒ Boolean
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”
149 150 151 |
# File 'lib/syntax_suggest/code_line.rb', line 149 def empty? @empty end |
#hidden? ⇒ Boolean
Opposite or ‘visible?` (note: different than `empty?`)
142 143 144 |
# File 'lib/syntax_suggest/code_line.rb', line 142 def hidden? !visible? end |
#indent_index ⇒ Object
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
106 107 108 |
# File 'lib/syntax_suggest/code_line.rb', line 106 def indent_index @indent_index ||= [indent, index] end |
#is_end? ⇒ Boolean
Returns true if the code line is determined to contain an ‘end` keyword
121 122 123 |
# File 'lib/syntax_suggest/code_line.rb', line 121 def is_end? @is_end end |
#is_kw? ⇒ Boolean
Returns true if the code line is determined to contain a keyword that matches with an ‘end`
For example: ‘def`, `do`, `begin`, `ensure`, etc.
115 116 117 |
# File 'lib/syntax_suggest/code_line.rb', line 115 def is_kw? @is_kw end |
#mark_invisible ⇒ Object
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
130 131 132 |
# File 'lib/syntax_suggest/code_line.rb', line 130 def mark_invisible @line = "" end |
#not_empty? ⇒ Boolean
Opposite of ‘empty?` (note: different than `visible?`)
154 155 156 |
# File 'lib/syntax_suggest/code_line.rb', line 154 def not_empty? !empty? end |
#to_s ⇒ Object
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.
167 168 169 |
# File 'lib/syntax_suggest/code_line.rb', line 167 def to_s line end |
#trailing_slash? ⇒ Boolean
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)
204 205 206 207 |
# 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
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”).
137 138 139 |
# File 'lib/syntax_suggest/code_line.rb', line 137 def visible? !line.empty? end |