Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Replace IndexablePath with ResourceUri concept" #2523

Merged
merged 1 commit into from
Sep 4, 2024
Merged
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
6 changes: 3 additions & 3 deletions exe/ruby-lsp
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ if options[:doctor]

puts "Globbing for indexable files"

index.configuration.indexables.each do |uri|
puts "indexing: #{uri}"
index.index_single(uri)
index.configuration.indexables.each do |indexable|
puts "indexing: #{indexable.full_path}"
index.index_single(indexable)
end
return
end
Expand Down
6 changes: 3 additions & 3 deletions exe/ruby-lsp-check
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ puts "Verifying that indexing executes successfully. This may take a while..."
index = RubyIndexer::Index.new
indexables = index.configuration.indexables

indexables.each_with_index do |uri, i|
index.index_single(uri)
indexables.each_with_index do |indexable, i|
index.index_single(indexable)
rescue => e
errors[uri.to_standarized_path] = e
errors[indexable.full_path] = e
ensure
print("\033[M\033[0KIndexed #{i + 1}/#{indexables.length}") unless ENV["CI"]
end
Expand Down
14 changes: 7 additions & 7 deletions lib/ruby_indexer/lib/ruby_indexer/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def initialize
)
end

sig { returns(T::Array[FileUri]) }
sig { returns(T::Array[IndexablePath]) }
def indexables
excluded_gems = @excluded_gems - @included_gems
locked_gems = Bundler.locked_gems&.specs
Expand All @@ -74,7 +74,7 @@ def indexables
load_path_entry = $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
end

ResourceUri.file(path, load_path_entry)
IndexablePath.new(load_path_entry, path)
end
end

Expand All @@ -91,7 +91,7 @@ def indexables
# Remove user specified patterns
indexables.reject! do |indexable|
excluded_patterns.any? do |pattern|
File.fnmatch?(pattern, indexable.to_standardized_path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
File.fnmatch?(pattern, indexable.full_path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
end
end

Expand Down Expand Up @@ -123,12 +123,12 @@ def indexables
# If the default_path is a directory, we index all the Ruby files in it
indexables.concat(
Dir.glob(File.join(default_path, "**", "*.rb"), File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
ResourceUri.file(path, RbConfig::CONFIG["rubylibdir"])
IndexablePath.new(RbConfig::CONFIG["rubylibdir"], path)
end,
)
elsif pathname.extname == ".rb"
# If the default_path is a Ruby file, we index it
indexables << ResourceUri.file(default_path, RbConfig::CONFIG["rubylibdir"])
indexables << IndexablePath.new(RbConfig::CONFIG["rubylibdir"], default_path)
end
end

Expand All @@ -146,7 +146,7 @@ def indexables
indexables.concat(
spec.require_paths.flat_map do |require_path|
load_path_entry = File.join(spec.full_gem_path, require_path)
Dir.glob(File.join(load_path_entry, "**", "*.rb")).map! { |path| ResourceUri.file(path, load_path_entry) }
Dir.glob(File.join(load_path_entry, "**", "*.rb")).map! { |path| IndexablePath.new(load_path_entry, path) }
end,
)
rescue Gem::MissingSpecError
Expand All @@ -155,7 +155,7 @@ def indexables
# just ignore if they're missing
end

indexables.uniq!(&:to_s)
indexables.uniq!(&:full_path)
indexables
end

Expand Down
103 changes: 47 additions & 56 deletions lib/ruby_indexer/lib/ruby_indexer/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def initialize
@files_to_entries = T.let({}, T::Hash[String, T::Array[Entry]])

# Holds all require paths for every indexed item so that we can provide autocomplete for requires
@require_paths_tree = T.let(PrefixTree[ResourceUri].new, PrefixTree[ResourceUri])
@require_paths_tree = T.let(PrefixTree[IndexablePath].new, PrefixTree[IndexablePath])

# Holds the linearized ancestors list for every namespace
@ancestors = T.let({}, T::Hash[String, T::Array[String]])
Expand Down Expand Up @@ -63,35 +63,31 @@ def register_included_hook(module_name, &hook)
(@included_hooks[module_name] ||= []) << hook
end

sig { params(uri: ResourceUri).void }
def delete(uri)
path = uri.to_standardized_path

if path
# For each constant discovered in `path`, delete the associated entry from the index. If there are no entries
# left, delete the constant from the index.
@files_to_entries[path]&.each do |entry|
name = entry.name
entries = @entries[name]
next unless entries

# Delete the specific entry from the list for this name
entries.delete(entry)

# If all entries were deleted, then remove the name from the hash and from the prefix tree. Otherwise, update
# the prefix tree with the current entries
if entries.empty?
@entries.delete(name)
@entries_tree.delete(name)
else
@entries_tree.insert(name, entries)
end
end
sig { params(indexable: IndexablePath).void }
def delete(indexable)
# For each constant discovered in `path`, delete the associated entry from the index. If there are no entries
# left, delete the constant from the index.
@files_to_entries[indexable.full_path]&.each do |entry|
name = entry.name
entries = @entries[name]
next unless entries

@files_to_entries.delete(path)
# Delete the specific entry from the list for this name
entries.delete(entry)

# If all entries were deleted, then remove the name from the hash and from the prefix tree. Otherwise, update
# the prefix tree with the current entries
if entries.empty?
@entries.delete(name)
@entries_tree.delete(name)
else
@entries_tree.insert(name, entries)
end
end

require_path = uri.require_path
@files_to_entries.delete(indexable.full_path)

require_path = indexable.require_path
@require_paths_tree.delete(require_path) if require_path
end

Expand All @@ -109,7 +105,7 @@ def [](fully_qualified_name)
@entries[fully_qualified_name.delete_prefix("::")]
end

sig { params(query: String).returns(T::Array[ResourceUri]) }
sig { params(query: String).returns(T::Array[IndexablePath]) }
def search_require_paths(query)
@require_paths_tree.search(query)
end
Expand Down Expand Up @@ -296,53 +292,49 @@ def resolve(name, nesting, seen_names = [])
nil
end

# Index all files for the given uris, which defaults to what is configured. A block can be used to track and control
# indexing progress. That block is invoked with the current progress percentage and should return `true` to continue
# indexing or `false` to stop indexing.
# Index all files for the given indexable paths, which defaults to what is configured. A block can be used to track
# and control indexing progress. That block is invoked with the current progress percentage and should return `true`
# to continue indexing or `false` to stop indexing.
sig do
params(
uris: T::Array[ResourceUri],
indexable_paths: T::Array[IndexablePath],
block: T.nilable(T.proc.params(progress: Integer).returns(T::Boolean)),
).void
end
def index_all(uris: @configuration.indexables, &block)
def index_all(indexable_paths: @configuration.indexables, &block)
RBSIndexer.new(self).index_ruby_core
# Calculate how many paths are worth 1% of progress
progress_step = (uris.length / 100.0).ceil
progress_step = (indexable_paths.length / 100.0).ceil

uris.each_with_index do |uri, index|
indexable_paths.each_with_index do |path, index|
if block && index % progress_step == 0
progress = (index / progress_step) + 1
break unless block.call(progress)
end

index_single(uri)
index_single(path)
end
end

sig { params(uri: ResourceUri, source: T.nilable(String)).void }
def index_single(uri, source = nil)
path = uri.to_standardized_path
# Remove once we support indexing non file URIs
return unless path

content = source || File.read(path)
sig { params(indexable_path: IndexablePath, source: T.nilable(String)).void }
def index_single(indexable_path, source = nil)
content = source || File.read(indexable_path.full_path)
dispatcher = Prism::Dispatcher.new

result = Prism.parse(content)
listener = DeclarationListener.new(
self,
dispatcher,
result,
path,
indexable_path.full_path,
enhancements: @enhancements,
)
dispatcher.dispatch(result.value)

indexing_errors = listener.indexing_errors.uniq

require_path = uri.require_path
@require_paths_tree.insert(require_path, uri) if require_path
require_path = indexable_path.require_path
@require_paths_tree.insert(require_path, indexable_path) if require_path

if indexing_errors.any?
indexing_errors.each do |error|
Expand All @@ -354,7 +346,7 @@ def index_single(uri, source = nil)
# it
rescue SystemStackError => e
if e.backtrace&.first&.include?("prism")
$stderr.puts "Prism error indexing #{uri}: #{e.message}"
$stderr.puts "Prism error indexing #{indexable_path.full_path}: #{e.message}"
else
raise
end
Expand Down Expand Up @@ -551,17 +543,16 @@ def instance_variable_completion_candidates(name, owner_name)
variables
end

# Synchronizes a change made to the given uri. This method will ensure that new declarations are indexed, removed
# declarations removed and that the ancestor linearization cache is cleared if necessary
sig { params(uri: ResourceUri).void }
def handle_change(uri)
path = T.must(uri.to_standardized_path)
original_entries = @files_to_entries[path]
# Synchronizes a change made to the given indexable path. This method will ensure that new declarations are indexed,
# removed declarations removed and that the ancestor linearization cache is cleared if necessary
sig { params(indexable: IndexablePath).void }
def handle_change(indexable)
original_entries = @files_to_entries[indexable.full_path]

delete(uri)
index_single(uri)
delete(indexable)
index_single(indexable)

updated_entries = @files_to_entries[path]
updated_entries = @files_to_entries[indexable.full_path]

return unless original_entries && updated_entries

Expand Down
29 changes: 29 additions & 0 deletions lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# typed: strict
# frozen_string_literal: true

module RubyIndexer
class IndexablePath
extend T::Sig

sig { returns(T.nilable(String)) }
attr_reader :require_path

sig { returns(String) }
attr_reader :full_path

# An IndexablePath is instantiated with a load_path_entry and a full_path. The load_path_entry is where the file can
# be found in the $LOAD_PATH, which we use to determine the require_path. The load_path_entry may be `nil` if the
# indexer is configured to go through files that do not belong in the $LOAD_PATH. For example,
# `sorbet/tapioca/require.rb` ends up being a part of the paths to be indexed because it's a Ruby file inside the
# project, but the `sorbet` folder is not a part of the $LOAD_PATH. That means that both its load_path_entry and
# require_path will be `nil`, since it cannot be required by the project
sig { params(load_path_entry: T.nilable(String), full_path: String).void }
def initialize(load_path_entry, full_path)
@full_path = full_path
@require_path = T.let(
load_path_entry ? full_path.delete_prefix("#{load_path_entry}/").delete_suffix(".rb") : nil,
T.nilable(String),
)
end
end
end
Loading
Loading