lib/ruby_indexer/lib/ruby_indexer/index.rb in ruby-lsp-0.17.3 vs lib/ruby_indexer/lib/ruby_indexer/index.rb in ruby-lsp-0.17.4
- old
+ new
@@ -63,17 +63,17 @@
require_path = indexable.require_path
@require_paths_tree.delete(require_path) if require_path
end
- sig { params(entry: Entry).void }
- def <<(entry)
+ sig { params(entry: Entry, skip_prefix_tree: T::Boolean).void }
+ def add(entry, skip_prefix_tree: false)
name = entry.name
(@entries[name] ||= []) << entry
(@files_to_entries[entry.file_path] ||= []) << entry
- @entries_tree.insert(name, T.must(@entries[name]))
+ @entries_tree.insert(name, T.must(@entries[name])) unless skip_prefix_tree
end
sig { params(fully_qualified_name: String).returns(T.nilable(T::Array[Entry])) }
def [](fully_qualified_name)
@entries[fully_qualified_name.delete_prefix("::")]
@@ -116,30 +116,53 @@
end
# Fuzzy searches index entries based on Jaro-Winkler similarity. If no query is provided, all entries are returned
sig { params(query: T.nilable(String)).returns(T::Array[Entry]) }
def fuzzy_search(query)
- return @entries.flat_map { |_name, entries| entries } unless query
+ unless query
+ entries = @entries.filter_map do |_name, entries|
+ next if entries.first.is_a?(Entry::SingletonClass)
+ entries
+ end
+
+ return entries.flatten
+ end
+
normalized_query = query.gsub("::", "").downcase
results = @entries.filter_map do |name, entries|
+ next if entries.first.is_a?(Entry::SingletonClass)
+
similarity = DidYouMean::JaroWinkler.distance(name.gsub("::", "").downcase, normalized_query)
[entries, -similarity] if similarity > ENTRY_SIMILARITY_THRESHOLD
end
results.sort_by!(&:last)
results.flat_map(&:first)
end
- sig { params(name: 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 = prefix_search(name).flatten
- candidates.select! do |entry|
- entry.is_a?(RubyIndexer::Entry::Member) && ancestors.any?(entry.owner&.name)
+
+ candidates = name ? prefix_search(name).flatten : @entries.values.flatten
+ 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
- candidates
end
# Resolve a constant to its declaration based on its name and the nesting where the reference was found. Parameter
# documentation:
#
@@ -221,10 +244,16 @@
require_path = indexable_path.require_path
@require_paths_tree.insert(require_path, indexable_path) if require_path
rescue Errno::EISDIR, Errno::ENOENT
# If `path` is a directory, just ignore it and continue indexing. If the file doesn't exist, then we also ignore
# it
+ rescue SystemStackError => e
+ if e.backtrace&.first&.include?("prism")
+ $stderr.puts "Prism error indexing #{indexable_path.full_path}: #{e.message}"
+ else
+ raise
+ end
end
# Follows aliases in a namespace. The algorithm keeps checking if the name is an alias and then recursively follows
# it. The idea is that we test the name in parts starting from the complete name to the first namespace. For
# `Foo::Bar::Baz`, we would test:
@@ -267,24 +296,36 @@
real_parts.join("::")
end
# Attempts to find methods for a resolved fully qualified receiver name.
# Returns `nil` if the method does not exist on that receiver
- sig { params(method_name: String, receiver_name: String).returns(T.nilable(T::Array[Entry::Member])) }
+ sig do
+ params(
+ method_name: String,
+ receiver_name: String,
+ ).returns(T.nilable(T::Array[T.any(Entry::Member, Entry::MethodAlias)]))
+ end
def resolve_method(method_name, receiver_name)
method_entries = self[method_name]
- ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::"))
return unless method_entries
+ ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::"))
ancestors.each do |ancestor|
- found = method_entries.select do |entry|
- next unless entry.is_a?(Entry::Member)
-
- entry.owner&.name == ancestor
+ found = method_entries.filter_map do |entry|
+ case entry
+ when Entry::Member, Entry::MethodAlias
+ entry if entry.owner&.name == ancestor
+ when Entry::UnresolvedMethodAlias
+ # 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[Entry::Member]) if found.any?
+ return found if found.any?
end
nil
rescue NonExistingNamespaceError
nil
@@ -507,16 +548,16 @@
seen_names: T::Array[String],
).returns(T.nilable(T::Array[Entry]))
end
def lookup_ancestor_chain(name, nesting, seen_names)
*nesting_parts, constant_name = build_non_redundant_full_name(name, nesting).split("::")
- return if T.must(nesting_parts).empty?
+ return if nesting_parts.empty?
- namespace_entries = resolve(T.must(nesting_parts).join("::"), [], seen_names)
+ namespace_entries = resolve(nesting_parts.join("::"), [], seen_names)
return unless namespace_entries
- ancestors = T.must(nesting_parts).empty? ? [] : linearized_ancestors_of(T.must(namespace_entries.first).name)
+ ancestors = nesting_parts.empty? ? [] : linearized_ancestors_of(T.must(namespace_entries.first).name)
ancestors.each do |ancestor_name|
entries = direct_or_aliased_constant("#{ancestor_name}::#{constant_name}", seen_names)
return entries if entries
end
@@ -564,8 +605,29 @@
end
sig { params(name: String, seen_names: T::Array[String]).returns(T.nilable(T::Array[Entry])) }
def search_top_level(name, seen_names)
@entries[name]&.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e, seen_names) : e }
+ end
+
+ # Attempt to resolve a given unresolved method alias. This method returns the resolved alias if we managed to
+ # identify the target or the same unresolved alias entry if we couldn't
+ sig do
+ params(
+ entry: Entry::UnresolvedMethodAlias,
+ receiver_name: String,
+ ).returns(T.any(Entry::MethodAlias, Entry::UnresolvedMethodAlias))
+ end
+ def resolve_method_alias(entry, receiver_name)
+ return entry if entry.new_name == entry.old_name
+
+ target_method_entries = resolve_method(entry.old_name, receiver_name)
+ return entry unless target_method_entries
+
+ resolved_alias = Entry::MethodAlias.new(T.must(target_method_entries.first), entry)
+ original_entries = T.must(@entries[entry.new_name])
+ original_entries.delete(entry)
+ original_entries << resolved_alias
+ resolved_alias
end
end
end