Skip to content

Commit

Permalink
Ensure features work with aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
vinistock committed Jun 18, 2024
1 parent 315d6c7 commit 8c63345
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 16 deletions.
13 changes: 9 additions & 4 deletions lib/ruby_indexer/lib/ruby_indexer/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -439,11 +439,16 @@ class MethodAlias < Entry

sig { params(target: T.any(Member, MethodAlias), unresolved_alias: UnresolvedMethodAlias).void }
def initialize(target, unresolved_alias)
full_comments = ["Alias for #{target.name}\n"]
full_comments.concat(unresolved_alias.comments)
full_comments << "\n"
full_comments.concat(target.comments)

super(
unresolved_alias.new_name,
unresolved_alias.file_path,
unresolved_alias.location,
unresolved_alias.comments,
full_comments,
)

@target = target
Expand All @@ -459,9 +464,9 @@ def parameters
@target.parameters
end

sig { override.returns(T::Array[String]) }
def comments
@comments + @target.comments
sig { returns(String) }
def decorated_parameters
@target.decorated_parameters
end
end
end
Expand Down
37 changes: 27 additions & 10 deletions lib/ruby_indexer/lib/ruby_indexer/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,29 @@ def fuzzy_search(query)
results.flat_map(&:first)
end

sig { params(name: T.nilable(String), receiver_name: String).returns(T::Array[Entry]) }
sig do
params(
name: T.nilable(String),
receiver_name: String,
).returns(T::Array[T.any(Entry::Member, Entry::MethodAlias)])
end
def method_completion_candidates(name, receiver_name)
ancestors = linearized_ancestors_of(receiver_name)

candidates = name ? prefix_search(name).flatten : @entries.values.flatten
candidates.select! { |entry| entry.is_a?(Entry::Member) && ancestors.any?(entry.owner&.name) }
candidates
candidates = candidates.filter_map do |entry|
case entry
when Entry::Member, Entry::MethodAlias
entry if ancestors.any?(entry.owner&.name)
when Entry::UnresolvedMethodAlias
if ancestors.any?(entry.owner&.name)
resolved_alias = resolve_method_alias(entry, receiver_name)
resolved_alias if resolved_alias.is_a?(Entry::MethodAlias)
end
end
end

T.cast(candidates, T::Array[T.any(Entry::Member, Entry::MethodAlias)])
end

# Resolve a constant to its declaration based on its name and the nesting where the reference was found. Parameter
Expand Down Expand Up @@ -296,19 +312,20 @@ def resolve_method(method_name, receiver_name)

ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::"))
ancestors.each do |ancestor|
found = method_entries.select do |entry|
found = method_entries.filter_map do |entry|
case entry
when Entry::Member, Entry::MethodAlias
entry.owner&.name == ancestor
entry if entry.owner&.name == ancestor
when Entry::UnresolvedMethodAlias
resolved_alias = resolve_method_alias(entry, receiver_name)
resolved_alias.is_a?(Entry::MethodAlias) && resolved_alias.owner&.name == ancestor
else
next
# Resolve aliases lazily as we find them
if entry.owner&.name == ancestor
resolved_alias = resolve_method_alias(entry, receiver_name)
resolved_alias if resolved_alias.is_a?(Entry::MethodAlias)
end
end
end

return T.cast(found, T::Array[T.any(Entry::Member, Entry::MethodAlias)]) if found.any?
return found if found.any?
end

nil
Expand Down
37 changes: 37 additions & 0 deletions lib/ruby_indexer/test/index_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1435,5 +1435,42 @@ class Foo
entry = T.must(@index["bar"].first)
assert_kind_of(Entry::UnresolvedMethodAlias, entry)
end

def test_only_aliases_for_the_right_owner_are_resolved
index(<<~RUBY)
class Foo
attr_reader :name
alias_method :decorated_name, :name
end
class Bar
alias_method :decorated_name, :to_s
end
RUBY

methods = @index.resolve_method("decorated_name", "Foo")
refute_nil(methods)

entry = T.must(methods.first)
assert_kind_of(Entry::MethodAlias, entry)

target = entry.target
assert_equal("name", target.name)
assert_kind_of(Entry::Accessor, target)
assert_equal("Foo", T.must(target.owner).name)

other_decorated_name = T.must(@index["decorated_name"].find { |e| e.is_a?(Entry::UnresolvedMethodAlias) })
assert_kind_of(Entry::UnresolvedMethodAlias, other_decorated_name)
end

def test_completion_does_not_include_unresolved_aliases
index(<<~RUBY)
class Foo
alias_method :bar, :missing
end
RUBY

assert_empty(@index.method_completion_candidates("bar", "Foo"))
end
end
end
4 changes: 2 additions & 2 deletions lib/ruby_lsp/listeners/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ def complete_methods(node, name)
text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
kind: Constant::CompletionItemKind::METHOD,
data: {
owner_name: T.cast(entry, RubyIndexer::Entry::Member).owner&.name,
owner_name: entry.owner&.name,
},
)
end
Expand All @@ -309,7 +309,7 @@ def complete_methods(node, name)

sig do
params(
entry: RubyIndexer::Entry::Member,
entry: T.any(RubyIndexer::Entry::Member, RubyIndexer::Entry::MethodAlias),
node: Prism::CallNode,
).returns(Interface::CompletionItem)
end
Expand Down
25 changes: 25 additions & 0 deletions test/requests/completion_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,31 @@ def self.yeah
end
end

def test_completion_for_aliased_methods
source = +<<~RUBY
class Parent
def bar(a); end
end
class Child < Parent
alias baz bar
def do_something
b
end
end
RUBY

with_server(source, stub_no_typechecker: true) do |server, uri|
server.process_message(id: 1, method: "textDocument/completion", params: {
textDocument: { uri: uri },
position: { line: 8, character: 5 },
})
result = server.pop_response.response
assert_equal(["bar", "baz"], result.map(&:label))
end
end

private

def with_file_structure(server, &block)
Expand Down
27 changes: 27 additions & 0 deletions test/requests/definition_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,33 @@ def baz
end
end

def test_definition_for_aliased_methods
source = <<~RUBY
class Parent
def bar; end
end
class Child < Parent
alias baz bar
def do_something
baz
end
end
RUBY

with_server(source) do |server, uri|
server.process_message(
id: 1,
method: "textDocument/definition",
params: { textDocument: { uri: uri }, position: { character: 4, line: 8 } },
)
response = server.pop_response.response

assert_equal(5, response[0].range.start.line)
end
end

private

def create_definition_addon
Expand Down
30 changes: 30 additions & 0 deletions test/requests/hover_expectations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,36 @@ def baz
end
end

def test_hover_for_aliased_methods
source = <<~RUBY
class Parent
# Original
def bar; end
end
class Child < Parent
# Alias
alias baz bar
def do_something
baz
end
end
RUBY

with_server(source) do |server, uri|
server.process_message(
id: 1,
method: "textDocument/hover",
params: { textDocument: { uri: uri }, position: { character: 4, line: 10 } },
)

contents = server.pop_response.response.contents.value
assert_match("Alias", contents)
assert_match("Original", contents)
end
end

private

def create_hover_addon
Expand Down
29 changes: 29 additions & 0 deletions test/requests/signature_help_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,33 @@ def self.bar(a, b)
assert_equal(0, result.active_parameter)
end
end

def test_aliased_methods
source = <<~RUBY
class Parent
def bar(a); end
end
class Child < Parent
alias baz bar
def do_something
baz()
end
end
RUBY

with_server(source) do |server, uri|
server.process_message(id: 1, method: "textDocument/signatureHelp", params: {
textDocument: { uri: uri },
position: { line: 8, character: 8 },
context: {},
})
result = server.pop_response.response
signature = result.signatures.first

assert_equal("baz(a)", signature.label)
assert_equal(0, result.active_parameter)
end
end
end

0 comments on commit 8c63345

Please sign in to comment.