summaryrefslogtreecommitdiff
path: root/lib/syntax_suggest/code_line.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/syntax_suggest/code_line.rb')
-rw-r--r--lib/syntax_suggest/code_line.rb133
1 files changed, 58 insertions, 75 deletions
diff --git a/lib/syntax_suggest/code_line.rb b/lib/syntax_suggest/code_line.rb
index dc738ab128..7fb1aae26a 100644
--- a/lib/syntax_suggest/code_line.rb
+++ b/lib/syntax_suggest/code_line.rb
@@ -26,34 +26,66 @@ module SyntaxSuggest
# Returns an array of CodeLine objects
# from the source string
- 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|
+ 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,
- lex: lex_array_for_line[index + 1]
+ tokens: tokens_for_line[index + 1],
+ consecutive: visitor.consecutive_lines.include?(index + 1)
)
end
end
- attr_reader :line, :index, :lex, :line_number, :indent
- def initialize(line:, index:, lex:)
- @lex = lex
+ # 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"
+ 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
+
+ attr_reader :line, :index, :tokens, :line_number, :indent
+ 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!
- if strip_line.empty?
- @empty = true
- @indent = 0
+ @indent = if (@empty = strip_line.empty?)
+ line.length - 1 # Newline removed from strip_line is not "whitespace"
else
- @empty = false
- @indent = line.length - strip_line.length
+ line.length - strip_line.length
end
set_kw_end
@@ -153,29 +185,16 @@ module SyntaxSuggest
index <=> other.index
end
- # [Not stable API]
- #
- # Lines that have a `on_ignored_nl` type token and NOT
- # a `BEG` 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
- def ignore_newline_not_beg?
- @ignore_newline_not_beg
+ # Can this line be logically joined together
+ # with the following line? Determined by walking
+ # the AST
+ def consecutive?
+ @consecutive
end
- # Determines if the given line has a trailing slash
+ # 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" \
@@ -183,55 +202,19 @@ module SyntaxSuggest
# expect(lines.first.trailing_slash?).to eq(true)
#
def trailing_slash?
- last = @lex.last
- return false unless last
- return false unless last.type == :on_sp
-
- last.token == TRAILING_SLASH
+ return unless (last = @tokens.last)
+ @line.byteindex(TRAILING_SLASH, last.location.end_column) != nil
end
- # Endless method detection
- #
- # From https://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
- #
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
+ @tokens.each do |token|
+ kw_count += 1 if token.is_kw?
+ end_count += 1 if token.is_end?
end
- kw_count -= oneliner_count
-
@is_kw = (kw_count - end_count) > 0
@is_end = (end_count - kw_count) > 0
end