ruby-lsp 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c2f16b0f0e93ade0e4465fcf71148ac156f6d65b1354f07af2df4007c600c15
4
- data.tar.gz: 7c6f4668f461315039e37f04354dddadb1678dd89ff9c249a268897992dfa4c0
3
+ metadata.gz: 98731986ff16a22d1854fe971bfa188d55212665128ad635aabbf519c1097d9f
4
+ data.tar.gz: fe416e79e672448b3d2477909fec47451c6f878020ec92e2178259d933c51cb4
5
5
  SHA512:
6
- metadata.gz: 184683630988346ae617e3df3d6d04c66b822d6d9734f595a41caa3603b38046e5ee5a7a4361abede0f3b8d1584371730be9af905d006fa881d4b718f3381947
7
- data.tar.gz: 7c10ef841c059110b1d5bc38cde57de243140c37d7095ab8c7ce16d28e4c520c402f2bfcf604f7002a7e06d7db031b8ab9e52ce28bf3c50be0dc2671d5728170
6
+ metadata.gz: ad9a36886ffb6b1370b2530ffdb84a89244e22ec2329ee111d70bea8e90ad7ebe6c92b87dd437e283270cb09a7d564b3471d7ddcdc51df86df54d4bdfd09ada8
7
+ data.tar.gz: 9a89e7a763d1a74a320ca8dd544a52ead86a3bd573204ad86585dce7dc7797ac993f3716b98e9e6cdfd9d5b6b7a8f17e9743aa149866945d099e7d9210719a28
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.3
1
+ 0.10.0
@@ -24,11 +24,11 @@ module RubyIndexer
24
24
 
25
25
  @excluded_gems = T.let(excluded_gem_names, T::Array[String])
26
26
  @included_gems = T.let([], T::Array[String])
27
- @excluded_patterns = T.let(["**/*_test.rb"], T::Array[String])
27
+ @excluded_patterns = T.let([File.join("**", "*_test.rb")], T::Array[String])
28
28
  path = Bundler.settings["path"]
29
- @excluded_patterns << "#{File.expand_path(path, Dir.pwd)}/**/*.rb" if path
29
+ @excluded_patterns << File.join(File.expand_path(path, Dir.pwd), "**", "*.rb") if path
30
30
 
31
- @included_patterns = T.let(["#{Dir.pwd}/**/*.rb"], T::Array[String])
31
+ @included_patterns = T.let([File.join(Dir.pwd, "**", "*.rb")], T::Array[String])
32
32
  @excluded_magic_comments = T.let(
33
33
  [
34
34
  "frozen_string_literal:",
@@ -61,8 +61,8 @@ module RubyIndexer
61
61
  raise e, "Syntax error while loading .index.yml configuration: #{e.message}"
62
62
  end
63
63
 
64
- sig { returns(T::Array[String]) }
65
- def files_to_index
64
+ sig { returns(T::Array[IndexablePath]) }
65
+ def indexables
66
66
  excluded_gems = @excluded_gems - @included_gems
67
67
  locked_gems = Bundler.locked_gems&.specs
68
68
 
@@ -70,19 +70,22 @@ module RubyIndexer
70
70
  # having duplicates if BUNDLE_PATH is set to a folder inside the project structure
71
71
 
72
72
  # Add user specified patterns
73
- files_to_index = @included_patterns.flat_map do |pattern|
74
- Dir.glob(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB)
73
+ indexables = @included_patterns.flat_map do |pattern|
74
+ Dir.glob(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
75
+ load_path_entry = $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
76
+ IndexablePath.new(load_path_entry, path)
77
+ end
75
78
  end
76
79
 
77
80
  # Remove user specified patterns
78
- files_to_index.reject! do |path|
81
+ indexables.reject! do |indexable|
79
82
  @excluded_patterns.any? do |pattern|
80
- File.fnmatch?(pattern, path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
83
+ File.fnmatch?(pattern, indexable.full_path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
81
84
  end
82
85
  end
83
86
 
84
87
  # Add default gems to the list of files to be indexed
85
- Dir.glob("#{RbConfig::CONFIG["rubylibdir"]}/*").each do |default_path|
88
+ Dir.glob(File.join(RbConfig::CONFIG["rubylibdir"], "*")).each do |default_path|
86
89
  # The default_path might be a Ruby file or a folder with the gem's name. For example:
87
90
  # bundler/
88
91
  # bundler.rb
@@ -103,10 +106,14 @@ module RubyIndexer
103
106
 
104
107
  if pathname.directory?
105
108
  # If the default_path is a directory, we index all the Ruby files in it
106
- files_to_index.concat(Dir.glob("#{default_path}/**/*.rb", File::FNM_PATHNAME | File::FNM_EXTGLOB))
109
+ indexables.concat(
110
+ Dir.glob(File.join(default_path, "**", "*.rb"), File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
111
+ IndexablePath.new(RbConfig::CONFIG["rubylibdir"], path)
112
+ end,
113
+ )
107
114
  else
108
115
  # If the default_path is a Ruby file, we index it
109
- files_to_index << default_path
116
+ indexables << IndexablePath.new(RbConfig::CONFIG["rubylibdir"], default_path)
110
117
  end
111
118
  end
112
119
 
@@ -121,15 +128,20 @@ module RubyIndexer
121
128
  # duplicates or accidentally ignoring exclude patterns
122
129
  next if spec.full_gem_path == Dir.pwd
123
130
 
124
- files_to_index.concat(Dir.glob("#{spec.full_gem_path}/{#{spec.require_paths.join(",")}}/**/*.rb"))
131
+ indexables.concat(
132
+ spec.require_paths.flat_map do |require_path|
133
+ load_path_entry = File.join(spec.full_gem_path, require_path)
134
+ Dir.glob(File.join(load_path_entry, "**", "*.rb")).map! { |path| IndexablePath.new(load_path_entry, path) }
135
+ end,
136
+ )
125
137
  rescue Gem::MissingSpecError
126
138
  # If a gem is scoped only to some specific platform, then its dependencies may not be installed either, but they
127
139
  # are still listed in locked_gems. We can't index them because they are not installed for the platform, so we
128
140
  # just ignore if they're missing
129
141
  end
130
142
 
131
- files_to_index.uniq!
132
- files_to_index
143
+ indexables.uniq!
144
+ indexables
133
145
  end
134
146
 
135
147
  sig { returns(Regexp) }
@@ -17,35 +17,55 @@ module RubyIndexer
17
17
  # }
18
18
  @entries = T.let({}, T::Hash[String, T::Array[Entry]])
19
19
 
20
+ # Holds all entries in the index using a prefix tree for searching based on prefixes to provide autocompletion
21
+ @entries_tree = T.let(PrefixTree[T::Array[Entry]].new, PrefixTree[T::Array[Entry]])
22
+
20
23
  # Holds references to where entries where discovered so that we can easily delete them
21
24
  # {
22
25
  # "/my/project/foo.rb" => [#<Entry::Class>, #<Entry::Class>],
23
26
  # "/my/project/bar.rb" => [#<Entry::Class>],
24
27
  # }
25
28
  @files_to_entries = T.let({}, T::Hash[String, T::Array[Entry]])
29
+
30
+ # Holds all require paths for every indexed item so that we can provide autocomplete for requires
31
+ @require_paths_tree = T.let(PrefixTree[IndexablePath].new, PrefixTree[IndexablePath])
26
32
  end
27
33
 
28
- sig { params(path: String).void }
29
- def delete(path)
34
+ sig { params(indexable: IndexablePath).void }
35
+ def delete(indexable)
30
36
  # For each constant discovered in `path`, delete the associated entry from the index. If there are no entries
31
37
  # left, delete the constant from the index.
32
- @files_to_entries[path]&.each do |entry|
33
- entries = @entries[entry.name]
38
+ @files_to_entries[indexable.full_path]&.each do |entry|
39
+ name = entry.name
40
+ entries = @entries[name]
34
41
  next unless entries
35
42
 
36
43
  # Delete the specific entry from the list for this name
37
44
  entries.delete(entry)
38
- # If all entries were deleted, then remove the name from the hash
39
- @entries.delete(entry.name) if entries.empty?
45
+
46
+ # If all entries were deleted, then remove the name from the hash and from the prefix tree. Otherwise, update
47
+ # the prefix tree with the current entries
48
+ if entries.empty?
49
+ @entries.delete(name)
50
+ @entries_tree.delete(name)
51
+ else
52
+ @entries_tree.insert(name, entries)
53
+ end
40
54
  end
41
55
 
42
- @files_to_entries.delete(path)
56
+ @files_to_entries.delete(indexable.full_path)
57
+
58
+ require_path = indexable.require_path
59
+ @require_paths_tree.delete(require_path) if require_path
43
60
  end
44
61
 
45
62
  sig { params(entry: Entry).void }
46
63
  def <<(entry)
47
- (@entries[entry.name] ||= []) << entry
64
+ name = entry.name
65
+
66
+ (@entries[name] ||= []) << entry
48
67
  (@files_to_entries[entry.file_path] ||= []) << entry
68
+ @entries_tree.insert(name, T.must(@entries[name]))
49
69
  end
50
70
 
51
71
  sig { params(fully_qualified_name: String).returns(T.nilable(T::Array[Entry])) }
@@ -53,6 +73,36 @@ module RubyIndexer
53
73
  @entries[fully_qualified_name.delete_prefix("::")]
54
74
  end
55
75
 
76
+ sig { params(query: String).returns(T::Array[IndexablePath]) }
77
+ def search_require_paths(query)
78
+ @require_paths_tree.search(query)
79
+ end
80
+
81
+ # Searches entries in the index based on an exact prefix, intended for providing autocomplete. All possible matches
82
+ # to the prefix are returned. The return is an array of arrays, where each entry is the array of entries for a given
83
+ # name match. For example:
84
+ # ## Example
85
+ # ```ruby
86
+ # # If the index has two entries for `Foo::Bar` and one for `Foo::Baz`, then:
87
+ # index.prefix_search("Foo::B")
88
+ # # Will return:
89
+ # [
90
+ # [#<Entry::Class name="Foo::Bar">, #<Entry::Class name="Foo::Bar">],
91
+ # [#<Entry::Class name="Foo::Baz">],
92
+ # ]
93
+ # ```
94
+ sig { params(query: String, nesting: T::Array[String]).returns(T::Array[T::Array[Entry]]) }
95
+ def prefix_search(query, nesting)
96
+ results = (nesting.length + 1).downto(0).flat_map do |i|
97
+ prefix = T.must(nesting[0...i]).join("::")
98
+ namespaced_query = prefix.empty? ? query : "#{prefix}::#{query}"
99
+ @entries_tree.search(namespaced_query)
100
+ end
101
+
102
+ results.uniq!
103
+ results
104
+ end
105
+
56
106
  # Fuzzy searches index entries based on Jaro-Winkler similarity. If no query is provided, all entries are returned
57
107
  sig { params(query: T.nilable(String)).returns(T::Array[Entry]) }
58
108
  def fuzzy_search(query)
@@ -85,16 +135,19 @@ module RubyIndexer
85
135
  nil
86
136
  end
87
137
 
88
- sig { params(paths: T::Array[String]).void }
89
- def index_all(paths: RubyIndexer.configuration.files_to_index)
90
- paths.each { |path| index_single(path) }
138
+ sig { params(indexable_paths: T::Array[IndexablePath]).void }
139
+ def index_all(indexable_paths: RubyIndexer.configuration.indexables)
140
+ indexable_paths.each { |path| index_single(path) }
91
141
  end
92
142
 
93
- sig { params(path: String, source: T.nilable(String)).void }
94
- def index_single(path, source = nil)
95
- content = source || File.read(path)
96
- visitor = IndexVisitor.new(self, YARP.parse(content), path)
143
+ sig { params(indexable_path: IndexablePath, source: T.nilable(String)).void }
144
+ def index_single(indexable_path, source = nil)
145
+ content = source || File.read(indexable_path.full_path)
146
+ visitor = IndexVisitor.new(self, YARP.parse(content), indexable_path.full_path)
97
147
  visitor.run
148
+
149
+ require_path = indexable_path.require_path
150
+ @require_paths_tree.insert(require_path, indexable_path) if require_path
98
151
  rescue Errno::EISDIR
99
152
  # If `path` is a directory, just ignore it and continue indexing
100
153
  end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ class IndexablePath
6
+ extend T::Sig
7
+
8
+ sig { returns(T.nilable(String)) }
9
+ attr_reader :require_path
10
+
11
+ sig { returns(String) }
12
+ attr_reader :full_path
13
+
14
+ # An IndexablePath is instantiated with a load_path_entry and a full_path. The load_path_entry is where the file can
15
+ # be found in the $LOAD_PATH, which we use to determine the require_path. The load_path_entry may be `nil` if the
16
+ # indexer is configured to go through files that do not belong in the $LOAD_PATH. For example,
17
+ # `sorbet/tapioca/require.rb` ends up being a part of the paths to be indexed because it's a Ruby file inside the
18
+ # project, but the `sorbet` folder is not a part of the $LOAD_PATH. That means that both its load_path_entry and
19
+ # require_path will be `nil`, since it cannot be required by the project
20
+ sig { params(load_path_entry: T.nilable(String), full_path: String).void }
21
+ def initialize(load_path_entry, full_path)
22
+ @full_path = full_path
23
+ @require_path = T.let(
24
+ load_path_entry ? Pathname.new(full_path).relative_path_from(load_path_entry).to_s.delete_suffix(".rb") : nil,
25
+ T.nilable(String),
26
+ )
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,153 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ # A PrefixTree is a data structure that allows searching for partial strings fast. The tree is similar to a nested
6
+ # hash structure, where the keys are the characters of the inserted strings.
7
+ #
8
+ # ## Example
9
+ # ```ruby
10
+ # tree = PrefixTree[String].new
11
+ # # Insert entries using the same key and value
12
+ # tree.insert("bar", "bar")
13
+ # tree.insert("baz", "baz")
14
+ # # Internally, the structure is analogous to this, but using nodes:
15
+ # # {
16
+ # # "b" => {
17
+ # # "a" => {
18
+ # # "r" => "bar",
19
+ # # "z" => "baz"
20
+ # # }
21
+ # # }
22
+ # # }
23
+ # # When we search it, it finds all possible values based on partial (or complete matches):
24
+ # tree.search("") # => ["bar", "baz"]
25
+ # tree.search("b") # => ["bar", "baz"]
26
+ # tree.search("ba") # => ["bar", "baz"]
27
+ # tree.search("bar") # => ["bar"]
28
+ # ```
29
+ #
30
+ # A PrefixTree is useful for autocomplete, since we always want to find all alternatives while the developer hasn't
31
+ # finished typing yet. This PrefixTree implementation allows for string keys and any arbitrary value using the generic
32
+ # `Value` type.
33
+ #
34
+ # See https://en.wikipedia.org/wiki/Trie for more information
35
+ class PrefixTree
36
+ extend T::Sig
37
+ extend T::Generic
38
+
39
+ Value = type_member
40
+
41
+ sig { void }
42
+ def initialize
43
+ @root = T.let(Node.new("", ""), Node[Value])
44
+ end
45
+
46
+ # Search the PrefixTree based on a given `prefix`. If `foo` is an entry in the tree, then searching for `fo` will
47
+ # return it as a result. The result is always an array of the type of value attribute to the generic `Value` type.
48
+ # Notice that if the `Value` is an array, this method will return an array of arrays, where each entry is the array
49
+ # of values for a given match
50
+ sig { params(prefix: String).returns(T::Array[Value]) }
51
+ def search(prefix)
52
+ node = find_node(prefix)
53
+ return [] unless node
54
+
55
+ node.collect
56
+ end
57
+
58
+ # Inserts a `value` using the given `key`
59
+ sig { params(key: String, value: Value).void }
60
+ def insert(key, value)
61
+ node = @root
62
+
63
+ key.each_char do |char|
64
+ node = node.children[char] ||= Node.new(char, value, node)
65
+ end
66
+
67
+ # This line is to allow a value to be overridden. When we are indexing files, we want to be able to update entries
68
+ # for a given fully qualified name if we find more occurrences of it. Without being able to override, that would
69
+ # not be possible
70
+ node.value = value
71
+ node.leaf = true
72
+ end
73
+
74
+ # Deletes the entry identified by `key` from the tree. Notice that a partial match will still delete all entries
75
+ # that match it. For example, if the tree contains `foo` and we ask to delete `fo`, then `foo` will be deleted
76
+ sig { params(key: String).void }
77
+ def delete(key)
78
+ node = find_node(key)
79
+ return unless node
80
+
81
+ # Remove the node from the tree and then go up the parents to remove any of them with empty children
82
+ parent = T.let(T.must(node.parent), T.nilable(Node[Value]))
83
+
84
+ while parent
85
+ parent.children.delete(node.key)
86
+ return if parent.children.any? || parent.leaf
87
+
88
+ node = parent
89
+ parent = parent.parent
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ # Find a node that matches the given `key`
96
+ sig { params(key: String).returns(T.nilable(Node[Value])) }
97
+ def find_node(key)
98
+ node = @root
99
+
100
+ key.each_char do |char|
101
+ snode = node.children[char]
102
+ return nil unless snode
103
+
104
+ node = snode
105
+ end
106
+
107
+ node
108
+ end
109
+
110
+ class Node
111
+ extend T::Sig
112
+ extend T::Generic
113
+
114
+ Value = type_member
115
+
116
+ sig { returns(T::Hash[String, Node[Value]]) }
117
+ attr_reader :children
118
+
119
+ sig { returns(String) }
120
+ attr_reader :key
121
+
122
+ sig { returns(Value) }
123
+ attr_accessor :value
124
+
125
+ sig { returns(T::Boolean) }
126
+ attr_accessor :leaf
127
+
128
+ sig { returns(T.nilable(Node[Value])) }
129
+ attr_reader :parent
130
+
131
+ sig { params(key: String, value: Value, parent: T.nilable(Node[Value])).void }
132
+ def initialize(key, value, parent = nil)
133
+ @key = key
134
+ @value = value
135
+ @parent = parent
136
+ @children = T.let({}, T::Hash[String, Node[Value]])
137
+ @leaf = T.let(false, T::Boolean)
138
+ end
139
+
140
+ sig { returns(T::Array[Value]) }
141
+ def collect
142
+ result = T.let([], T::Array[Value])
143
+ result << value if leaf
144
+
145
+ children.each_value do |node|
146
+ result.concat(node.collect)
147
+ end
148
+
149
+ result
150
+ end
151
+ end
152
+ end
153
+ end
@@ -4,9 +4,11 @@
4
4
  require "yaml"
5
5
  require "did_you_mean"
6
6
 
7
+ require "ruby_indexer/lib/ruby_indexer/indexable_path"
7
8
  require "ruby_indexer/lib/ruby_indexer/visitor"
8
9
  require "ruby_indexer/lib/ruby_indexer/index"
9
10
  require "ruby_indexer/lib/ruby_indexer/configuration"
11
+ require "ruby_indexer/lib/ruby_indexer/prefix_tree"
10
12
 
11
13
  module RubyIndexer
12
14
  class << self
@@ -139,7 +139,7 @@ module RubyIndexer
139
139
 
140
140
  assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-2")
141
141
 
142
- @index.delete("/fake/path/foo.rb")
142
+ @index.delete(IndexablePath.new(nil, "/fake/path/foo.rb"))
143
143
  refute_entry("Foo")
144
144
  assert_empty(@index.instance_variable_get(:@files_to_entries))
145
145
  end
@@ -11,55 +11,57 @@ module RubyIndexer
11
11
 
12
12
  def test_load_configuration_executes_configure_block
13
13
  @config.load_config
14
- files_to_index = @config.files_to_index
14
+ indexables = @config.indexables
15
15
 
16
- assert(files_to_index.none? { |path| path.include?("test/fixtures") })
17
- assert(files_to_index.none? { |path| path.include?("minitest-reporters") })
18
- assert(files_to_index.none? { |path| path == __FILE__ })
16
+ assert(indexables.none? { |indexable| indexable.full_path.include?("test/fixtures") })
17
+ assert(indexables.none? { |indexable| indexable.full_path.include?("minitest-reporters") })
18
+ assert(indexables.none? { |indexable| indexable.full_path == __FILE__ })
19
19
  end
20
20
 
21
- def test_files_to_index_only_includes_gem_require_paths
21
+ def test_indexables_only_includes_gem_require_paths
22
22
  @config.load_config
23
- files_to_index = @config.files_to_index
23
+ indexables = @config.indexables
24
24
 
25
25
  Bundler.locked_gems.specs.each do |lazy_spec|
26
26
  next if lazy_spec.name == "ruby-lsp"
27
27
 
28
28
  spec = Gem::Specification.find_by_name(lazy_spec.name)
29
- assert(files_to_index.none? { |path| path.start_with?("#{spec.full_gem_path}/test/") })
29
+ assert(indexables.none? { |indexable| indexable.full_path.start_with?("#{spec.full_gem_path}/test/") })
30
30
  rescue Gem::MissingSpecError
31
31
  # Transitive dependencies might be missing when running tests on Windows
32
32
  end
33
33
  end
34
34
 
35
- def test_files_to_index_does_not_include_default_gem_path_when_in_bundle
35
+ def test_indexables_does_not_include_default_gem_path_when_in_bundle
36
36
  @config.load_config
37
- files_to_index = @config.files_to_index
37
+ indexables = @config.indexables
38
38
 
39
- assert(files_to_index.none? { |path| path.start_with?("#{RbConfig::CONFIG["rubylibdir"]}/psych") })
39
+ assert(
40
+ indexables.none? { |indexable| indexable.full_path.start_with?("#{RbConfig::CONFIG["rubylibdir"]}/psych") },
41
+ )
40
42
  end
41
43
 
42
- def test_files_to_index_includes_default_gems
44
+ def test_indexables_includes_default_gems
43
45
  @config.load_config
44
- files_to_index = @config.files_to_index
46
+ indexables = @config.indexables.map(&:full_path)
45
47
 
46
- assert_includes(files_to_index, "#{RbConfig::CONFIG["rubylibdir"]}/pathname.rb")
47
- assert_includes(files_to_index, "#{RbConfig::CONFIG["rubylibdir"]}/ipaddr.rb")
48
- assert_includes(files_to_index, "#{RbConfig::CONFIG["rubylibdir"]}/abbrev.rb")
48
+ assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/pathname.rb")
49
+ assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/ipaddr.rb")
50
+ assert_includes(indexables, "#{RbConfig::CONFIG["rubylibdir"]}/abbrev.rb")
49
51
  end
50
52
 
51
- def test_files_to_index_includes_project_files
53
+ def test_indexables_includes_project_files
52
54
  @config.load_config
53
- files_to_index = @config.files_to_index
55
+ indexables = @config.indexables.map(&:full_path)
54
56
 
55
57
  Dir.glob("#{Dir.pwd}/lib/**/*.rb").each do |path|
56
58
  next if path.end_with?("_test.rb")
57
59
 
58
- assert_includes(files_to_index, path)
60
+ assert_includes(indexables, path)
59
61
  end
60
62
  end
61
63
 
62
- def test_files_to_index_avoids_duplicates_if_bundle_path_is_inside_project
64
+ def test_indexables_avoids_duplicates_if_bundle_path_is_inside_project
63
65
  Bundler.settings.set_global("path", "vendor/bundle")
64
66
  config = Configuration.new
65
67
  config.load_config
@@ -69,18 +71,22 @@ module RubyIndexer
69
71
  Bundler.settings.set_global("path", nil)
70
72
  end
71
73
 
72
- def test_files_to_index_does_not_include_gems_own_installed_files
74
+ def test_indexables_does_not_include_gems_own_installed_files
73
75
  @config.load_config
74
- files_to_index = @config.files_to_index
76
+ indexables = @config.indexables
75
77
 
76
- assert(files_to_index.none? { |path| path.start_with?(Bundler.bundle_path.join("gems", "ruby-lsp").to_s) })
78
+ assert(
79
+ indexables.none? do |indexable|
80
+ indexable.full_path.start_with?(Bundler.bundle_path.join("gems", "ruby-lsp").to_s)
81
+ end,
82
+ )
77
83
  end
78
84
 
79
85
  def test_paths_are_unique
80
86
  @config.load_config
81
- files_to_index = @config.files_to_index
87
+ indexables = @config.indexables
82
88
 
83
- assert_equal(files_to_index.uniq.length, files_to_index.length)
89
+ assert_equal(indexables.uniq.length, indexables.length)
84
90
  end
85
91
 
86
92
  def test_configuration_raises_for_unknown_keys
@@ -6,11 +6,11 @@ require_relative "test_case"
6
6
  module RubyIndexer
7
7
  class IndexTest < TestCase
8
8
  def test_deleting_one_entry_for_a_class
9
- @index.index_single("/fake/path/foo.rb", <<~RUBY)
9
+ @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
10
10
  class Foo
11
11
  end
12
12
  RUBY
13
- @index.index_single("/fake/path/other_foo.rb", <<~RUBY)
13
+ @index.index_single(IndexablePath.new(nil, "/fake/path/other_foo.rb"), <<~RUBY)
14
14
  class Foo
15
15
  end
16
16
  RUBY
@@ -18,13 +18,13 @@ module RubyIndexer
18
18
  entries = @index["Foo"]
19
19
  assert_equal(2, entries.length)
20
20
 
21
- @index.delete("/fake/path/other_foo.rb")
21
+ @index.delete(IndexablePath.new(nil, "/fake/path/other_foo.rb"))
22
22
  entries = @index["Foo"]
23
23
  assert_equal(1, entries.length)
24
24
  end
25
25
 
26
26
  def test_deleting_all_entries_for_a_class
27
- @index.index_single("/fake/path/foo.rb", <<~RUBY)
27
+ @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
28
28
  class Foo
29
29
  end
30
30
  RUBY
@@ -32,13 +32,13 @@ module RubyIndexer
32
32
  entries = @index["Foo"]
33
33
  assert_equal(1, entries.length)
34
34
 
35
- @index.delete("/fake/path/foo.rb")
35
+ @index.delete(IndexablePath.new(nil, "/fake/path/foo.rb"))
36
36
  entries = @index["Foo"]
37
37
  assert_nil(entries)
38
38
  end
39
39
 
40
40
  def test_index_resolve
41
- @index.index_single("/fake/path/foo.rb", <<~RUBY)
41
+ @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
42
42
  class Bar; end
43
43
 
44
44
  module Foo
@@ -72,7 +72,7 @@ module RubyIndexer
72
72
  end
73
73
 
74
74
  def test_accessing_with_colon_colon_prefix
75
- @index.index_single("/fake/path/foo.rb", <<~RUBY)
75
+ @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
76
76
  class Bar; end
77
77
 
78
78
  module Foo
@@ -92,7 +92,7 @@ module RubyIndexer
92
92
  end
93
93
 
94
94
  def test_fuzzy_search
95
- @index.index_single("/fake/path/foo.rb", <<~RUBY)
95
+ @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), <<~RUBY)
96
96
  class Bar; end
97
97
 
98
98
  module Foo
@@ -121,9 +121,42 @@ module RubyIndexer
121
121
 
122
122
  def test_index_single_ignores_directories
123
123
  FileUtils.mkdir("lib/this_is_a_dir.rb")
124
- @index.index_single("lib/this_is_a_dir.rb")
124
+ @index.index_single(IndexablePath.new(nil, "lib/this_is_a_dir.rb"))
125
125
  ensure
126
126
  FileUtils.rm_r("lib/this_is_a_dir.rb")
127
127
  end
128
+
129
+ def test_searching_for_require_paths
130
+ @index.index_single(IndexablePath.new("/fake", "/fake/path/foo.rb"), <<~RUBY)
131
+ class Foo
132
+ end
133
+ RUBY
134
+ @index.index_single(IndexablePath.new("/fake", "/fake/path/other_foo.rb"), <<~RUBY)
135
+ class Foo
136
+ end
137
+ RUBY
138
+
139
+ assert_equal(["path/foo", "path/other_foo"], @index.search_require_paths("path").map(&:require_path))
140
+ end
141
+
142
+ def test_searching_for_entries_based_on_prefix
143
+ @index.index_single(IndexablePath.new("/fake", "/fake/path/foo.rb"), <<~RUBY)
144
+ class Foo::Bar
145
+ end
146
+ RUBY
147
+ @index.index_single(IndexablePath.new("/fake", "/fake/path/other_foo.rb"), <<~RUBY)
148
+ class Foo::Bar
149
+ end
150
+
151
+ class Foo::Baz
152
+ end
153
+ RUBY
154
+
155
+ results = @index.prefix_search("Foo", []).map { |entries| entries.map(&:name) }
156
+ assert_equal([["Foo::Bar", "Foo::Bar"], ["Foo::Baz"]], results)
157
+
158
+ results = @index.prefix_search("Ba", ["Foo"]).map { |entries| entries.map(&:name) }
159
+ assert_equal([["Foo::Bar", "Foo::Bar"], ["Foo::Baz"]], results)
160
+ end
128
161
  end
129
162
  end