Class: RubyIndexer::Index

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/ruby_indexer/lib/ruby_indexer/index.rb

Defined Under Namespace

Classes: Entry

Constant Summary collapse

ENTRY_SIMILARITY_THRESHOLD =

The minimum Jaro-Winkler similarity score for an entry to be considered a match for a given fuzzy search query

0.7

Instance Method Summary collapse

Constructor Details

#initializeIndex

Returns a new instance of Index.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/ruby_indexer/lib/ruby_indexer/index.rb', line 12

def initialize
  # Holds all entries in the index using the following format:
  # {
  #  "Foo" => [#<Entry::Class>, #<Entry::Class>],
  #  "Foo::Bar" => [#<Entry::Class>],
  # }
  @entries = T.let({}, T::Hash[String, T::Array[Entry]])

  # Holds references to where entries where discovered so that we can easily delete them
  # {
  #  "/my/project/foo.rb" => [#<Entry::Class>, #<Entry::Class>],
  #  "/my/project/bar.rb" => [#<Entry::Class>],
  # }
  @files_to_entries = T.let({}, T::Hash[String, T::Array[Entry]])
end

Instance Method Details

#<<(entry) ⇒ Object



46
47
48
49
# File 'lib/ruby_indexer/lib/ruby_indexer/index.rb', line 46

def <<(entry)
  (@entries[entry.name] ||= []) << entry
  (@files_to_entries[entry.file_path] ||= []) << entry
end

#[](fully_qualified_name) ⇒ Object



52
53
54
# File 'lib/ruby_indexer/lib/ruby_indexer/index.rb', line 52

def [](fully_qualified_name)
  @entries[fully_qualified_name.delete_prefix("::")]
end

#delete(path) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/ruby_indexer/lib/ruby_indexer/index.rb', line 29

def delete(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|
    entries = @entries[entry.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
    @entries.delete(entry.name) if entries.empty?
  end

  @files_to_entries.delete(path)
end

#fuzzy_search(query) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/ruby_indexer/lib/ruby_indexer/index.rb', line 58

def fuzzy_search(query)
  return @entries.flat_map { |_name, entries| entries } unless query

  normalized_query = query.gsub("::", "").downcase

  results = @entries.filter_map do |name, entries|
    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

#index_all(paths: RubyIndexer.configuration.files_to_index) ⇒ Object



89
90
91
# File 'lib/ruby_indexer/lib/ruby_indexer/index.rb', line 89

def index_all(paths: RubyIndexer.configuration.files_to_index)
  paths.each { |path| index_single(path) }
end

#index_single(path, source = nil) ⇒ Object



94
95
96
97
98
99
100
# File 'lib/ruby_indexer/lib/ruby_indexer/index.rb', line 94

def index_single(path, source = nil)
  content = source || File.read(path)
  visitor = IndexVisitor.new(self, YARP.parse(content), path)
  visitor.run
rescue Errno::EISDIR
  # If `path` is a directory, just ignore it and continue indexing
end

#resolve(name, nesting) ⇒ Object



77
78
79
80
81
82
83
84
85
86
# File 'lib/ruby_indexer/lib/ruby_indexer/index.rb', line 77

def resolve(name, nesting)
  (nesting.length + 1).downto(0).each do |i|
    prefix = T.must(nesting[0...i]).join("::")
    full_name = prefix.empty? ? name : "#{prefix}::#{name}"
    entries = @entries[full_name]
    return entries if entries
  end

  nil
end