Skip to content

Commit

Permalink
Use location links for namespace and method definition response (#2206)
Browse files Browse the repository at this point in the history
* Make resolve return types more specific

* Store name location for namespaces and methods

* Use location link for namespace and method definition

* Use name_location in supertypes
  • Loading branch information
vinistock authored Jun 20, 2024
1 parent a782ea7 commit 530ceca
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 74 deletions.
31 changes: 25 additions & 6 deletions lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ def initialize(index, dispatcher, parse_result, file_path)
sig { params(node: Prism::ClassNode).void }
def on_class_node_enter(node)
@visibility_stack.push(Entry::Visibility::PUBLIC)
name = node.constant_path.location.slice
constant_path = node.constant_path
name = constant_path.slice

comments = collect_comments(node)

Expand Down Expand Up @@ -93,6 +94,7 @@ def on_class_node_enter(node)
nesting,
@file_path,
node.location,
constant_path.location,
comments,
parent_class,
)
Expand All @@ -112,12 +114,13 @@ def on_class_node_leave(node)
sig { params(node: Prism::ModuleNode).void }
def on_module_node_enter(node)
@visibility_stack.push(Entry::Visibility::PUBLIC)
name = node.constant_path.location.slice
constant_path = node.constant_path
name = constant_path.slice

comments = collect_comments(node)

nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
entry = Entry::Module.new(nesting, @file_path, node.location, comments)
entry = Entry::Module.new(nesting, @file_path, node.location, constant_path.location, comments)

@owner_stack << entry
@index.add(entry)
Expand Down Expand Up @@ -145,9 +148,16 @@ def on_singleton_class_node_enter(node)

if existing_entries
entry = T.must(existing_entries.first)
entry.update_singleton_information(node.location, collect_comments(node))
entry.update_singleton_information(node.location, expression.location, collect_comments(node))
else
entry = Entry::SingletonClass.new(@stack, @file_path, node.location, collect_comments(node), nil)
entry = Entry::SingletonClass.new(
@stack,
@file_path,
node.location,
expression.location,
collect_comments(node),
nil,
)
@index.add(entry, skip_prefix_tree: true)
end

Expand Down Expand Up @@ -297,6 +307,7 @@ def on_def_node_enter(node)
method_name,
@file_path,
node.location,
node.name_loc,
comments,
list_params(node.parameters),
current_visibility,
Expand All @@ -309,6 +320,7 @@ def on_def_node_enter(node)
method_name,
@file_path,
node.location,
node.name_loc,
comments,
list_params(node.parameters),
current_visibility,
Expand Down Expand Up @@ -704,7 +716,14 @@ def singleton_klass

# If not available, create the singleton class lazily
nesting = @stack + ["<Class:#{@stack.last}>"]
entry = Entry::SingletonClass.new(nesting, @file_path, attached_class.location, [], nil)
entry = Entry::SingletonClass.new(
nesting,
@file_path,
attached_class.location,
attached_class.name_location,
[],
nil,
)
@index.add(entry, skip_prefix_tree: true)
entry
end
Expand Down
58 changes: 52 additions & 6 deletions lib/ruby_indexer/lib/ruby_indexer/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class Visibility < T::Enum
sig { returns(RubyIndexer::Location) }
attr_reader :location

alias_method :name_location, :location

sig { returns(T::Array[String]) }
attr_reader :comments

Expand Down Expand Up @@ -95,20 +97,39 @@ class Namespace < Entry
sig { returns(T::Array[String]) }
attr_reader :nesting

# Returns the location of the constant name, excluding the parent class or the body
sig { returns(Location) }
attr_reader :name_location

sig do
params(
nesting: T::Array[String],
file_path: String,
location: T.any(Prism::Location, RubyIndexer::Location),
name_location: T.any(Prism::Location, Location),
comments: T::Array[String],
).void
end
def initialize(nesting, file_path, location, comments)
def initialize(nesting, file_path, location, name_location, comments)
@name = T.let(nesting.join("::"), String)
# The original nesting where this namespace was discovered
@nesting = nesting

super(@name, file_path, location, comments)

@name_location = T.let(
if name_location.is_a?(Prism::Location)
Location.new(
name_location.start_line,
name_location.end_line,
name_location.start_column,
name_location.end_column,
)
else
name_location
end,
RubyIndexer::Location,
)
end

sig { returns(T::Array[String]) }
Expand Down Expand Up @@ -146,12 +167,13 @@ class Class < Namespace
nesting: T::Array[String],
file_path: String,
location: T.any(Prism::Location, RubyIndexer::Location),
name_location: T.any(Prism::Location, Location),
comments: T::Array[String],
parent_class: T.nilable(String),
).void
end
def initialize(nesting, file_path, location, comments, parent_class)
super(nesting, file_path, location, comments)
def initialize(nesting, file_path, location, name_location, comments, parent_class) # rubocop:disable Metrics/ParameterLists
super(nesting, file_path, location, name_location, comments)
@parent_class = parent_class
end

Expand All @@ -164,15 +186,21 @@ def ancestor_hash
class SingletonClass < Class
extend T::Sig

sig { params(location: Prism::Location, comments: T::Array[String]).void }
def update_singleton_information(location, comments)
sig { params(location: Prism::Location, name_location: Prism::Location, comments: T::Array[String]).void }
def update_singleton_information(location, name_location, comments)
# Create a new RubyIndexer::Location object from the Prism location
@location = Location.new(
location.start_line,
location.end_line,
location.start_column,
location.end_column,
)
@name_location = Location.new(
name_location.start_line,
name_location.end_line,
name_location.start_column,
name_location.end_column,
)
@comments.concat(comments)
end
end
Expand Down Expand Up @@ -309,20 +337,38 @@ class Method < Member
sig { override.returns(T::Array[Parameter]) }
attr_reader :parameters

# Returns the location of the method name, excluding parameters or the body
sig { returns(Location) }
attr_reader :name_location

sig do
params(
name: String,
file_path: String,
location: T.any(Prism::Location, RubyIndexer::Location),
name_location: T.any(Prism::Location, Location),
comments: T::Array[String],
parameters: T::Array[Parameter],
visibility: Visibility,
owner: T.nilable(Entry::Namespace),
).void
end
def initialize(name, file_path, location, comments, parameters, visibility, owner) # rubocop:disable Metrics/ParameterLists
def initialize(name, file_path, location, name_location, comments, parameters, visibility, owner) # rubocop:disable Metrics/ParameterLists
super(name, file_path, location, comments, visibility, owner)
@parameters = parameters
@name_location = T.let(
if name_location.is_a?(Prism::Location)
Location.new(
name_location.start_line,
name_location.end_line,
name_location.start_column,
name_location.end_column,
)
else
name_location
end,
RubyIndexer::Location,
)
end
end

Expand Down
41 changes: 36 additions & 5 deletions lib/ruby_indexer/lib/ruby_indexer/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ def method_completion_candidates(name, receiver_name)
name: String,
nesting: T::Array[String],
seen_names: T::Array[String],
).returns(T.nilable(T::Array[Entry]))
).returns(T.nilable(T::Array[T.any(
Entry::Namespace,
Entry::Alias,
Entry::UnresolvedAlias,
)]))
end
def resolve(name, nesting, seen_names = [])
# If we have a top level reference, then we just search for it straight away ignoring the nesting
Expand Down Expand Up @@ -521,7 +525,11 @@ def resolve_alias(entry, seen_names)
name: String,
nesting: T::Array[String],
seen_names: T::Array[String],
).returns(T.nilable(T::Array[Entry]))
).returns(T.nilable(T::Array[T.any(
Entry::Namespace,
Entry::Alias,
Entry::UnresolvedAlias,
)]))
end
def lookup_enclosing_scopes(name, nesting, seen_names)
nesting.length.downto(1).each do |i|
Expand All @@ -546,7 +554,11 @@ def lookup_enclosing_scopes(name, nesting, seen_names)
name: String,
nesting: T::Array[String],
seen_names: T::Array[String],
).returns(T.nilable(T::Array[Entry]))
).returns(T.nilable(T::Array[T.any(
Entry::Namespace,
Entry::Alias,
Entry::UnresolvedAlias,
)]))
end
def lookup_ancestor_chain(name, nesting, seen_names)
*nesting_parts, constant_name = build_non_redundant_full_name(name, nesting).split("::")
Expand Down Expand Up @@ -598,10 +610,29 @@ def build_non_redundant_full_name(name, nesting)
end
end

sig { params(full_name: String, seen_names: T::Array[String]).returns(T.nilable(T::Array[Entry])) }
sig do
params(
full_name: String,
seen_names: T::Array[String],
).returns(
T.nilable(T::Array[T.any(
Entry::Namespace,
Entry::Alias,
Entry::UnresolvedAlias,
)]),
)
end
def direct_or_aliased_constant(full_name, seen_names)
entries = @entries[full_name] || @entries[follow_aliased_namespace(full_name)]
entries&.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e, seen_names) : e }

T.cast(
entries&.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e, seen_names) : e },
T.nilable(T::Array[T.any(
Entry::Namespace,
Entry::Alias,
Entry::UnresolvedAlias,
)]),
)
end

# Attempt to resolve a given unresolved method alias. This method returns the resolved alias if we managed to
Expand Down
8 changes: 4 additions & 4 deletions lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def handle_class_declaration(declaration, pathname)
location = to_ruby_indexer_location(declaration.location)
comments = Array(declaration.comment&.string)
parent_class = declaration.super_class&.name&.name&.to_s
class_entry = Entry::Class.new(nesting, file_path, location, comments, parent_class)
class_entry = Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
add_declaration_mixins_to_entry(declaration, class_entry)
@index.add(class_entry)
declaration.members.each do |member|
Expand All @@ -64,7 +64,7 @@ def handle_module_declaration(declaration, pathname)
file_path = pathname.to_s
location = to_ruby_indexer_location(declaration.location)
comments = Array(declaration.comment&.string)
module_entry = Entry::Module.new(nesting, file_path, location, comments)
module_entry = Entry::Module.new(nesting, file_path, location, location, comments)
add_declaration_mixins_to_entry(declaration, module_entry)
@index.add(module_entry)
declaration.members.each do |member|
Expand Down Expand Up @@ -123,7 +123,7 @@ def handle_method(member, owner)
end

real_owner = member.singleton? ? existing_or_new_singleton_klass(owner) : owner
@index.add(Entry::Method.new(name, file_path, location, comments, [], visibility, real_owner))
@index.add(Entry::Method.new(name, file_path, location, location, comments, [], visibility, real_owner))
end

sig { params(owner: Entry::Namespace).returns(T.nilable(Entry::Class)) }
Expand All @@ -139,7 +139,7 @@ def existing_or_new_singleton_klass(owner)

# If not available, create the singleton class lazily
nesting = owner.nesting + ["<Class:#{name}>"]
entry = Entry::SingletonClass.new(nesting, owner.file_path, owner.location, [], nil)
entry = Entry::SingletonClass.new(nesting, owner.file_path, owner.location, owner.name_location, [], nil)
@index.add(entry, skip_prefix_tree: true)
entry
end
Expand Down
30 changes: 30 additions & 0 deletions lib/ruby_indexer/test/classes_and_modules_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -516,5 +516,35 @@ class Bar

assert_entry("Foo::<Class:Foo>::Bar", Entry::Class, "/fake/path/foo.rb:2-4:3-7")
end

def test_name_location_points_to_constant_path_location
index(<<~RUBY)
class Foo
def foo; end
end
module Bar
def bar; end
end
RUBY

foo = T.must(@index["Foo"].first)
refute_equal(foo.location, foo.name_location)

name_location = foo.name_location
assert_equal(1, name_location.start_line)
assert_equal(1, name_location.end_line)
assert_equal(6, name_location.start_column)
assert_equal(9, name_location.end_column)

bar = T.must(@index["Bar"].first)
refute_equal(bar.location, bar.name_location)

name_location = bar.name_location
assert_equal(5, name_location.start_line)
assert_equal(5, name_location.end_line)
assert_equal(7, name_location.start_column)
assert_equal(10, name_location.end_column)
end
end
end
20 changes: 20 additions & 0 deletions lib/ruby_indexer/test/method_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -428,5 +428,25 @@ def baz; end
# the exact same
assert_same(bar_owner, baz_owner)
end

def test_name_location_points_to_method_identifier_location
index(<<~RUBY)
class Foo
def bar
a = 123
a + 456
end
end
RUBY

entry = T.must(@index["bar"].first)
refute_equal(entry.location, entry.name_location)

name_location = entry.name_location
assert_equal(2, name_location.start_line)
assert_equal(2, name_location.end_line)
assert_equal(6, name_location.start_column)
assert_equal(9, name_location.end_column)
end
end
end
Loading

0 comments on commit 530ceca

Please sign in to comment.