Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 102 additions & 4 deletions lib/solargraph/parser/parser_gem/node_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,113 @@ def find_recipient_node cursor
end
prev = node
end
nil
find_recipient_node_by_text(source, offset)
end

# Text-based fallback for finding a method call recipient when the AST
# is unavailable (e.g., unparseable source with syntax errors).
#
# Scans backward from cursor offset to find '(' and the method name
# before it, then creates a minimal :send node.
#
# @param source [Solargraph::Source]
# @param offset [Integer]
# @return [Parser::AST::Node, nil]
def find_recipient_node_by_text source, offset
code = source.code
return nil if offset.nil? || offset <= 0 || offset > code.length

# The '(' could be at offset-1 (cursor after '(') or at offset (cursor on '(')
start_pos = offset - 1
if start_pos > 0 && code[start_pos] != '(' && code[offset] == '('
start_pos = offset
end

# Scan backward to find the matching '(' (handle nested parens)
depth = 0
paren_pos = nil
pos = start_pos
while pos >= 0
case code[pos]
when ')'
depth += 1
when '('
if depth == 0
paren_pos = pos
break
end
depth -= 1
end
pos -= 1
end
return nil if paren_pos.nil?

# Skip whitespace before '(' to find method name
idx = paren_pos - 1
while idx >= 0 && code[idx] =~ /\s/
idx -= 1
end
return nil if idx < 0

# Read method name (including ? and !)
name_end = idx + 1
while idx >= 0 && code[idx] =~ /[a-zA-Z0-9_?!]/
idx -= 1
end
name_start = idx + 1
return nil if name_start >= name_end
method_name = code[name_start...name_end]
return nil if method_name.empty?

# Check for receiver pattern: receiver.method( or receiver::method(
idx = name_start - 1
while idx >= 0 && code[idx] =~ /\s/
idx -= 1
end
if idx >= 0 && code[idx] == '.'
dot_pos = idx
idx -= 1
while idx >= 0 && code[idx] =~ /\s/
idx -= 1
end
recv_end = idx + 1
while idx >= 0 && code[idx] =~ /[a-zA-Z0-9_@$]/
idx -= 1
end
recv_start = idx + 1
if recv_start < recv_end
recv_name = code[recv_start...recv_end]
unless recv_name.empty?
receiver_node = ::Parser::AST::Node.new(:send, [nil, recv_name.to_sym])
return ::Parser::AST::Node.new(:send, [receiver_node, method_name.to_sym])
end
end
elsif idx >= 0 && idx > 0 && code[idx-1] == ':' && code[idx] == ':'
const_end = idx - 1
const_start = const_end
while const_start > 0 && code[const_start-1] =~ /[a-zA-Z0-9_]/
const_start -= 1
end
const_name = code[const_start...const_end]
unless const_name.empty? || method_name.empty?
const_node = ::Parser::AST::Node.new(:const, [nil, const_name.to_sym])
return ::Parser::AST::Node.new(:send, [const_node, method_name.to_sym])
end
end

# Simple method call without receiver
::Parser::AST::Node.new(:send, [nil, method_name.to_sym])
end

# @param cursor [Solargraph::Source::Cursor]
# @return [Parser::AST::Node, nil]
def repaired_find_recipient_node cursor
cursor = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1])
node = cursor.source.tree_at(cursor.position.line, cursor.position.column).first
return node if node && node.type == :send
c = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1])
tree = c.source.tree_at(c.position.line, c.position.column)
tree.each do |node|
return node if node.type == :send
end
find_recipient_node_by_text(cursor.source, cursor.offset)
end

#
Expand Down
12 changes: 11 additions & 1 deletion lib/solargraph/source/cursor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,17 @@ def string?
def recipient
@recipient ||= begin
node = recipient_node
node ? Cursor.new(source, Range.from_node(node).ending) : nil
if node.nil?
nil
else
rng = Range.from_node(node)
if rng
Cursor.new(source, rng.ending)
else
pos = Position.new(position.line, [position.column - 1, 0].max)
Cursor.new(source, pos)
end
end
end
end
alias receiver recipient
Expand Down
7 changes: 6 additions & 1 deletion lib/solargraph/source_map/clip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,13 @@ def complete
# @return [Array<Pin::Method>]
def signify
return [] unless cursor.argument?
return [] if cursor.recipient_node.nil?
chain = Parser.chain(cursor.recipient_node, cursor.filename)
chain.define(api_map, context_pin, locals).select { |pin| pin.is_a?(Pin::Method) }
name_pin = context_pin
if name_pin.nil?
name_pin = Pin::ProxyType.anonymous(ComplexType.try_parse('::Object'))
end
chain.define(api_map, name_pin, locals).select { |pin| pin.is_a?(Pin::Method) }
end

# @return [ComplexType]
Expand Down