ruby-lsp 0.9.3 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +27 -15
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +68 -15
- data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +29 -0
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +153 -0
- data/lib/ruby_indexer/ruby_indexer.rb +2 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +1 -1
- data/lib/ruby_indexer/test/configuration_test.rb +30 -24
- data/lib/ruby_indexer/test/index_test.rb +42 -9
- data/lib/ruby_indexer/test/prefix_tree_test.rb +150 -0
- data/lib/ruby_indexer/test/test_case.rb +1 -1
- data/lib/ruby_lsp/check_docs.rb +2 -1
- data/lib/ruby_lsp/event_emitter.rb +2 -0
- data/lib/ruby_lsp/executor.rb +33 -16
- data/lib/ruby_lsp/extension.rb +13 -1
- data/lib/ruby_lsp/listener.rb +43 -4
- data/lib/ruby_lsp/requests/code_lens.rb +15 -13
- data/lib/ruby_lsp/requests/completion.rb +168 -0
- data/lib/ruby_lsp/requests/definition.rb +43 -32
- data/lib/ruby_lsp/requests/document_highlight.rb +3 -3
- data/lib/ruby_lsp/requests/document_link.rb +3 -3
- data/lib/ruby_lsp/requests/document_symbol.rb +10 -9
- data/lib/ruby_lsp/requests/hover.rb +16 -33
- data/lib/ruby_lsp/requests/inlay_hints.rb +3 -3
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +4 -4
- data/lib/ruby_lsp/requests/support/common.rb +33 -0
- data/lib/ruby_lsp/requests.rb +2 -3
- metadata +9 -7
- data/lib/ruby_lsp/requests/path_completion.rb +0 -65
- data/lib/ruby_lsp/requests/support/prefix_tree.rb +0 -80
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98731986ff16a22d1854fe971bfa188d55212665128ad635aabbf519c1097d9f
|
4
|
+
data.tar.gz: fe416e79e672448b3d2477909fec47451c6f878020ec92e2178259d933c51cb4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad9a36886ffb6b1370b2530ffdb84a89244e22ec2329ee111d70bea8e90ad7ebe6c92b87dd437e283270cb09a7d564b3471d7ddcdc51df86df54d4bdfd09ada8
|
7
|
+
data.tar.gz: 9a89e7a763d1a74a320ca8dd544a52ead86a3bd573204ad86585dce7dc7797ac993f3716b98e9e6cdfd9d5b6b7a8f17e9743aa149866945d099e7d9210719a28
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
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(["
|
27
|
+
@excluded_patterns = T.let([File.join("**", "*_test.rb")], T::Array[String])
|
28
28
|
path = Bundler.settings["path"]
|
29
|
-
@excluded_patterns <<
|
29
|
+
@excluded_patterns << File.join(File.expand_path(path, Dir.pwd), "**", "*.rb") if path
|
30
30
|
|
31
|
-
@included_patterns = T.let([
|
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[
|
65
|
-
def
|
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
|
-
|
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
|
-
|
81
|
+
indexables.reject! do |indexable|
|
79
82
|
@excluded_patterns.any? do |pattern|
|
80
|
-
File.fnmatch?(pattern,
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
132
|
-
|
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(
|
29
|
-
def delete(
|
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[
|
33
|
-
|
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
|
-
|
39
|
-
|
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(
|
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
|
-
|
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(
|
89
|
-
def index_all(
|
90
|
-
|
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(
|
94
|
-
def index_single(
|
95
|
-
content = source || File.read(
|
96
|
-
visitor = IndexVisitor.new(self, YARP.parse(content),
|
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
|
-
|
14
|
+
indexables = @config.indexables
|
15
15
|
|
16
|
-
assert(
|
17
|
-
assert(
|
18
|
-
assert(
|
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
|
21
|
+
def test_indexables_only_includes_gem_require_paths
|
22
22
|
@config.load_config
|
23
|
-
|
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(
|
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
|
35
|
+
def test_indexables_does_not_include_default_gem_path_when_in_bundle
|
36
36
|
@config.load_config
|
37
|
-
|
37
|
+
indexables = @config.indexables
|
38
38
|
|
39
|
-
assert(
|
39
|
+
assert(
|
40
|
+
indexables.none? { |indexable| indexable.full_path.start_with?("#{RbConfig::CONFIG["rubylibdir"]}/psych") },
|
41
|
+
)
|
40
42
|
end
|
41
43
|
|
42
|
-
def
|
44
|
+
def test_indexables_includes_default_gems
|
43
45
|
@config.load_config
|
44
|
-
|
46
|
+
indexables = @config.indexables.map(&:full_path)
|
45
47
|
|
46
|
-
assert_includes(
|
47
|
-
assert_includes(
|
48
|
-
assert_includes(
|
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
|
53
|
+
def test_indexables_includes_project_files
|
52
54
|
@config.load_config
|
53
|
-
|
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(
|
60
|
+
assert_includes(indexables, path)
|
59
61
|
end
|
60
62
|
end
|
61
63
|
|
62
|
-
def
|
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
|
74
|
+
def test_indexables_does_not_include_gems_own_installed_files
|
73
75
|
@config.load_config
|
74
|
-
|
76
|
+
indexables = @config.indexables
|
75
77
|
|
76
|
-
assert(
|
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
|
-
|
87
|
+
indexables = @config.indexables
|
82
88
|
|
83
|
-
assert_equal(
|
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
|