Skip to content
Closed
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
74 changes: 56 additions & 18 deletions lib/ruby_indexer/lib/ruby_indexer/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,35 @@ class Entry
#: Symbol
attr_accessor :visibility

#: String?
attr_accessor :file_name

#: String?
attr_accessor :file_path

#: (String name, URI::Generic uri, Location location, String? comments) -> void
def initialize(name, uri, location, comments)
@name = name
@uri = uri
@comments = comments
@visibility = :public #: Symbol
@location = location
@file_path = @uri.full_path #: String?
@file_name = if @uri.scheme == "untitled"
@uri.opaque
else
File.basename(
@file_path, #: as !nil
)
end #: String?
@in_dependencies = if @file_path
::RubyLsp::BUNDLE_PATH &&
@file_path.start_with?(
::RubyLsp::BUNDLE_PATH, #: as !nil
) || @file_path.start_with?(RbConfig::CONFIG["rubylibdir"])
else
false
end #: bool
end

#: -> bool
Expand All @@ -41,20 +63,14 @@ def private?
@visibility == :private
end

#: -> String
def file_name
if @uri.scheme == "untitled"
@uri.opaque #: as !nil
else
File.basename(
file_path, #: as !nil
)
end
#: -> bool
def resolved?
true
end

#: -> String?
def file_path
@uri.full_path
#: -> bool
def in_dependencies?
@in_dependencies
end

#: -> String
Expand Down Expand Up @@ -373,6 +389,11 @@ def initialize(target, nesting, name, uri, location, comments) # rubocop:disable
@target = target
@nesting = nesting
end

#: -> bool
def resolved?
false
end
end

# Alias represents a resolved alias, which points to an existing constant target
Expand All @@ -386,11 +407,17 @@ def initialize(target, unresolved_alias)
unresolved_alias.name,
unresolved_alias.uri,
unresolved_alias.location,
unresolved_alias.comments,
nil,
)

@visibility = unresolved_alias.visibility
@target = target
@unresolved_alias = unresolved_alias
end

#: -> String?
def comments
@comments ||= @unresolved_alias.comments
end
end

Expand Down Expand Up @@ -439,6 +466,11 @@ def initialize(new_name, old_name, owner, uri, location, comments) # rubocop:dis
@old_name = old_name
@owner = owner
end

#: -> bool
def resolved?
false
end
end

# A method alias is a resolved alias entry that points to the exact method target it refers to
Expand All @@ -451,19 +483,25 @@ class MethodAlias < Entry

#: ((Member | MethodAlias) target, UnresolvedMethodAlias unresolved_alias) -> void
def initialize(target, unresolved_alias)
full_comments = +"Alias for #{target.name}\n"
full_comments << "#{unresolved_alias.comments}\n"
full_comments << target.comments

super(
unresolved_alias.new_name,
unresolved_alias.uri,
unresolved_alias.location,
full_comments,
nil,
)

@target = target
@owner = unresolved_alias.owner #: Entry::Namespace?
@unresolved_alias = unresolved_alias
end

#: -> String
def comments
@comments ||= <<~COMMENTS.chomp
Alias for #{@target.name}
#{@unresolved_alias.comments}
#{@target.comments}
COMMENTS
end

#: -> String
Expand Down
35 changes: 26 additions & 9 deletions lib/ruby_indexer/lib/ruby_indexer/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,11 @@ def prefix_search(query, nesting = nil)
#: (String? query) -> Array[Entry]
def fuzzy_search(query)
unless query
entries = @entries.filter_map do |_name, entries|
entries = @entries.values.filter_map do |entries|
next if entries.first.is_a?(Entry::SingletonClass)

entries
end

return entries.flatten
end

Expand Down Expand Up @@ -369,14 +368,14 @@ def index_all(uris: @configuration.indexable_uris, &block)
break unless block.call(progress)
end

index_file(uri, collect_comments: false)
index_file(uri, collect_comments: false, process_unresolved: false)
end

@initial_indexing_completed = true
end

#: (URI::Generic uri, String source, ?collect_comments: bool) -> void
def index_single(uri, source, collect_comments: true)
#: (URI::Generic uri, String source, ?collect_comments: bool, ?process_unresolved: bool) -> void
def index_single(uri, source, collect_comments: true, process_unresolved: true)
dispatcher = Prism::Dispatcher.new

result = Prism.parse(source)
Expand All @@ -386,6 +385,8 @@ def index_single(uri, source, collect_comments: true)
require_path = uri.require_path
@require_paths_tree.insert(require_path, uri) if require_path

process_unresolved_entries if process_unresolved

indexing_errors = listener.indexing_errors.uniq
indexing_errors.each { |error| $stderr.puts(error) } if indexing_errors.any?
rescue SystemStackError => e
Expand All @@ -397,10 +398,10 @@ def index_single(uri, source, collect_comments: true)
end

# Indexes a File URI by reading the contents from disk
#: (URI::Generic uri, ?collect_comments: bool) -> void
def index_file(uri, collect_comments: true)
#: (URI::Generic uri, ?collect_comments: bool, ?process_unresolved: bool) -> void
def index_file(uri, collect_comments: true, process_unresolved: true)
path = uri.full_path #: as !nil
index_single(uri, File.read(path), collect_comments: collect_comments)
index_single(uri, File.read(path), collect_comments: collect_comments, process_unresolved: process_unresolved)
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
Expand Down Expand Up @@ -731,6 +732,20 @@ def entries_for(uri, type = nil)
entries&.grep(type)
end

#: -> void
def process_unresolved_entries
@entries.values.each do |entries|
entries.each do |entry|
case entry
when Entry::UnresolvedConstantAlias
resolve_alias(entry, [])
when Entry::UnresolvedMethodAlias
resolve_method_alias(entry, entry.owner&.name || "", [])
end
end
end
end

private

# Always returns the linearized ancestors for the attached class, regardless of whether `name` refers to a singleton
Expand Down Expand Up @@ -903,10 +918,12 @@ def resolve_alias(entry, seen_names)
return entry if seen_names.include?(alias_name)

seen_names << alias_name

target = resolve(entry.target, entry.nesting, seen_names)
return entry unless target

# Self referential alias can be unresolved we should bail out from resolving
return entry if target.first == entry

target_name = target.first #: as !nil
.name
resolved_alias = Entry::ConstantAlias.new(target_name, entry)
Expand Down
57 changes: 44 additions & 13 deletions lib/ruby_indexer/lib/ruby_indexer/uri.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ class Generic
# NOTE: We also define this in the shim
PARSER = const_defined?(:RFC2396_PARSER) ? RFC2396_PARSER : DEFAULT_PARSER

# This unsafe regex is the same one used in the URI::RFC2396_REGEXP class with the exception of the fact that we
# do not include colon as a safe character. VS Code URIs always escape colons and we need to ensure we do the
# same to avoid inconsistencies in our URIs, which are used to identify resources
UNSAFE_REGEX = %r{[^\-_.!~*'()a-zA-Z\d;/?@&=+$,\[\]]}

class << self
#: (path: String, ?fragment: String?, ?scheme: String, ?load_path_entry: String?) -> URI::Generic
def from_path(path:, fragment: nil, scheme: "file", load_path_entry: nil)
# This unsafe regex is the same one used in the URI::RFC2396_REGEXP class with the exception of the fact that we
# do not include colon as a safe character. VS Code URIs always escape colons and we need to ensure we do the
# same to avoid inconsistencies in our URIs, which are used to identify resources
unsafe_regex = %r{[^\-_.!~*'()a-zA-Z\d;/?@&=+$,\[\]]}

def from_win_path(path:, fragment: nil, scheme: "file", load_path_entry: nil)
# On Windows, if the path begins with the disk name, we need to add a leading slash to make it a valid URI
escaped_path = if /^[A-Z]:/i.match?(path)
PARSER.escape("/#{path}", unsafe_regex)
PARSER.escape("/#{path}", UNSAFE_REGEX)
elsif path.start_with?("//?/")
# Some paths on Windows start with "//?/". This is a special prefix that allows for long file paths
PARSER.escape(path.delete_prefix("//?"), unsafe_regex)
PARSER.escape(path.delete_prefix("//?"), UNSAFE_REGEX)
else
PARSER.escape(path, unsafe_regex)
PARSER.escape(path, UNSAFE_REGEX)
end

uri = build(scheme: scheme, path: escaped_path, fragment: fragment)
Expand All @@ -38,6 +38,21 @@ def from_path(path:, fragment: nil, scheme: "file", load_path_entry: nil)

uri
end

#: (path: String, ?fragment: String?, ?scheme: String, ?load_path_entry: String?) -> URI::Generic
def from_unix_path(path:, fragment: nil, scheme: "file", load_path_entry: nil)
escaped_path = PARSER.escape(path, UNSAFE_REGEX)

uri = build(scheme: scheme, path: escaped_path, fragment: fragment)

if load_path_entry
uri.require_path = path.delete_prefix("#{load_path_entry}/").delete_suffix(".rb")
end

uri
end

alias_method :from_path, Gem.win_platform? ? :from_win_path : :from_unix_path
end

#: String?
Expand All @@ -52,21 +67,37 @@ def add_require_path_from_load_entry(load_path_entry)
end

#: -> String?
def to_standardized_path
# On Windows, when we're getting the file system path back from the URI, we need to remove the leading forward
# slash
def to_standardized_win_path
parsed_path = path

return unless parsed_path

# we can bail out parsing if there is nothing to unescape
return parsed_path unless parsed_path.match?(/%[0-9A-Fa-f]{2}/)

unescaped_path = PARSER.unescape(parsed_path)

# On Windows, when we're getting the file system path back from the URI, we need to remove the leading forward
# slash
if %r{^/[A-Z]:}i.match?(unescaped_path)
unescaped_path.delete_prefix("/")
else
unescaped_path
end
end

alias_method :full_path, :to_standardized_path
#: -> String?
def to_standardized_unix_path
unescaped_path = path
return unless unescaped_path

# we can bail out parsing if there is nothing to be unescaped
return unescaped_path unless unescaped_path.match?(/%[0-9A-Fa-f]{2}/)

PARSER.unescape(unescaped_path)
end

alias_method :to_standardized_path, Gem.win_platform? ? :to_standardized_win_path : :to_standardized_unix_path
alias_method :full_path, Gem.win_platform? ? :to_standardized_win_path : :to_standardized_unix_path
end
end
Loading
Loading