ruby-lsp 0.10.1 → 0.11.1
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/README.md +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +40 -5
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +141 -5
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +66 -18
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +23 -0
- data/lib/ruby_indexer/test/configuration_test.rb +2 -0
- data/lib/ruby_indexer/test/constant_test.rb +202 -0
- data/lib/ruby_indexer/test/index_test.rb +20 -0
- data/lib/ruby_lsp/{extension.rb → addon.rb} +27 -25
- data/lib/ruby_lsp/check_docs.rb +7 -8
- data/lib/ruby_lsp/document.rb +35 -38
- data/lib/ruby_lsp/event_emitter.rb +239 -77
- data/lib/ruby_lsp/executor.rb +45 -55
- data/lib/ruby_lsp/internal.rb +2 -3
- data/lib/ruby_lsp/listener.rb +8 -7
- data/lib/ruby_lsp/parameter_scope.rb +33 -0
- data/lib/ruby_lsp/requests/base_request.rb +3 -3
- data/lib/ruby_lsp/requests/code_action_resolve.rb +14 -14
- data/lib/ruby_lsp/requests/code_lens.rb +39 -63
- data/lib/ruby_lsp/requests/completion.rb +54 -32
- data/lib/ruby_lsp/requests/definition.rb +30 -27
- data/lib/ruby_lsp/requests/diagnostics.rb +26 -3
- data/lib/ruby_lsp/requests/document_highlight.rb +18 -19
- data/lib/ruby_lsp/requests/document_link.rb +50 -9
- data/lib/ruby_lsp/requests/document_symbol.rb +82 -75
- data/lib/ruby_lsp/requests/folding_ranges.rb +199 -222
- data/lib/ruby_lsp/requests/formatting.rb +5 -6
- data/lib/ruby_lsp/requests/hover.rb +33 -22
- data/lib/ruby_lsp/requests/inlay_hints.rb +2 -3
- data/lib/ruby_lsp/requests/selection_ranges.rb +65 -40
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +187 -145
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -4
- data/lib/ruby_lsp/requests/support/annotation.rb +18 -17
- data/lib/ruby_lsp/requests/support/common.rb +17 -26
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +67 -42
- data/lib/ruby_lsp/requests/support/highlight_target.rb +64 -45
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +9 -4
- data/lib/ruby_lsp/requests/support/selection_range.rb +5 -4
- data/lib/ruby_lsp/requests/support/sorbet.rb +2 -57
- data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +7 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -1
- data/lib/ruby_lsp/server.rb +6 -44
- data/lib/ruby_lsp/utils.rb +2 -12
- metadata +11 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2708af5c4c174d0fafe710fb1f7aaeed7e1aee45cb7aad49f16a53d9f32ad03
|
4
|
+
data.tar.gz: eaab5153e4c99eac231bf7376ef368fec589e1d6fe05807c9a4de45394242e42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca5c0ba2801350c6557c31b90626a07a4e358245d2177b65a9670f00d2521d0d9850fc488dd65124a58cbc841a09c656d91595cf436391f80e87a434021e822b
|
7
|
+
data.tar.gz: c8cf7c3228b62639d5b8eb3e3b61750237e85bc43837424359bb54b5d794bea37f0b7214ea6229bfb365c189445d48069dae166e8ad91a0b774bbe34fd269473
|
data/README.md
CHANGED
@@ -46,14 +46,14 @@ See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth de
|
|
46
46
|
For creating rich themes for Ruby using the semantic highlighting information, see the [semantic highlighting
|
47
47
|
documentation](SEMANTIC_HIGHLIGHTING.md).
|
48
48
|
|
49
|
-
###
|
49
|
+
### Addons
|
50
50
|
|
51
|
-
The Ruby LSP provides
|
52
|
-
|
51
|
+
The Ruby LSP provides an addon system that allows other gems to enhance the base functionality with more editor
|
52
|
+
features. This is the mechanism that powers addons like
|
53
53
|
|
54
54
|
- [Ruby LSP Rails](https://github.com/Shopify/ruby-lsp-rails)
|
55
55
|
|
56
|
-
For instructions on how to create
|
56
|
+
For instructions on how to create addons, see the [addons documentation](ADDONS.md).
|
57
57
|
|
58
58
|
## Learn More
|
59
59
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.11.1
|
data/exe/ruby-lsp-check
CHANGED
@@ -18,11 +18,7 @@ module RubyIndexer
|
|
18
18
|
|
19
19
|
sig { void }
|
20
20
|
def initialize
|
21
|
-
|
22
|
-
dependency.name if dependency.groups == [:development]
|
23
|
-
end
|
24
|
-
|
25
|
-
@excluded_gems = T.let(excluded_gem_names, T::Array[String])
|
21
|
+
@excluded_gems = T.let(initial_excluded_gems, T::Array[String])
|
26
22
|
@included_gems = T.let([], T::Array[String])
|
27
23
|
@excluded_patterns = T.let([File.join("**", "*_test.rb")], T::Array[String])
|
28
24
|
path = Bundler.settings["path"]
|
@@ -174,5 +170,44 @@ module RubyIndexer
|
|
174
170
|
@included_patterns.concat(config["included_patterns"]) if config["included_patterns"]
|
175
171
|
@excluded_magic_comments.concat(config["excluded_magic_comments"]) if config["excluded_magic_comments"]
|
176
172
|
end
|
173
|
+
|
174
|
+
sig { returns(T::Array[String]) }
|
175
|
+
def initial_excluded_gems
|
176
|
+
excluded, others = Bundler.definition.dependencies.partition do |dependency|
|
177
|
+
dependency.groups == [:development]
|
178
|
+
end
|
179
|
+
|
180
|
+
# When working on a gem, we need to make sure that its gemspec dependencies can't be excluded. This is necessary
|
181
|
+
# because Bundler doesn't assign groups to gemspec dependencies
|
182
|
+
this_gem = Bundler.definition.dependencies.find do |d|
|
183
|
+
d.to_spec.full_gem_path == Dir.pwd
|
184
|
+
rescue Gem::MissingSpecError
|
185
|
+
false
|
186
|
+
end
|
187
|
+
|
188
|
+
others.concat(this_gem.to_spec.dependencies) if this_gem
|
189
|
+
|
190
|
+
excluded.each do |dependency|
|
191
|
+
next unless dependency.runtime?
|
192
|
+
|
193
|
+
dependency.to_spec.dependencies.each do |transitive_dependency|
|
194
|
+
# If the transitive dependency is included in other groups, skip it
|
195
|
+
next if others.any? { |d| d.name == transitive_dependency.name }
|
196
|
+
|
197
|
+
# If the transitive dependency is included as a transitive dependency of a gem outside of the development
|
198
|
+
# group, skip it
|
199
|
+
next if others.any? { |d| d.to_spec.dependencies.include?(transitive_dependency) }
|
200
|
+
|
201
|
+
excluded << transitive_dependency
|
202
|
+
end
|
203
|
+
rescue Gem::MissingSpecError
|
204
|
+
# If a gem is scoped only to some specific platform, then its dependencies may not be installed either, but they
|
205
|
+
# are still listed in dependencies. We can't index them because they are not installed for the platform, so we
|
206
|
+
# just ignore if they're missing
|
207
|
+
end
|
208
|
+
|
209
|
+
excluded.uniq!
|
210
|
+
excluded.map(&:name)
|
211
|
+
end
|
177
212
|
end
|
178
213
|
end
|
@@ -5,6 +5,8 @@ module RubyIndexer
|
|
5
5
|
class Index
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
+
class UnresolvableAliasError < StandardError; end
|
9
|
+
|
8
10
|
# The minimum Jaro-Winkler similarity score for an entry to be considered a match for a given fuzzy search query
|
9
11
|
ENTRY_SIMILARITY_THRESHOLD = 0.7
|
10
12
|
|
@@ -125,13 +127,29 @@ module RubyIndexer
|
|
125
127
|
# 3. Baz
|
126
128
|
sig { params(name: String, nesting: T::Array[String]).returns(T.nilable(T::Array[Entry])) }
|
127
129
|
def resolve(name, nesting)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
return entries if entries
|
130
|
+
if name.start_with?("::")
|
131
|
+
name = name.delete_prefix("::")
|
132
|
+
results = @entries[name] || @entries[follow_aliased_namespace(name)]
|
133
|
+
return results.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e) : e } if results
|
133
134
|
end
|
134
135
|
|
136
|
+
nesting.length.downto(0).each do |i|
|
137
|
+
namespace = T.must(nesting[0...i]).join("::")
|
138
|
+
full_name = namespace.empty? ? name : "#{namespace}::#{name}"
|
139
|
+
|
140
|
+
# If we find an entry with `full_name` directly, then we can already return it, even if it contains aliases -
|
141
|
+
# because the user might be trying to jump to the alias definition.
|
142
|
+
#
|
143
|
+
# However, if we don't find it, then we need to search for possible aliases in the namespace. For example, in
|
144
|
+
# the LSP itself we alias `RubyLsp::Interface` to `LanguageServer::Protocol::Interface`, which means doing
|
145
|
+
# `RubyLsp::Interface::Location` is allowed. For these cases, we need some way to realize that the
|
146
|
+
# `RubyLsp::Interface` part is an alias, that has to be resolved
|
147
|
+
entries = @entries[full_name] || @entries[follow_aliased_namespace(full_name)]
|
148
|
+
return entries.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e) : e } if entries
|
149
|
+
end
|
150
|
+
|
151
|
+
nil
|
152
|
+
rescue UnresolvableAliasError
|
135
153
|
nil
|
136
154
|
end
|
137
155
|
|
@@ -152,6 +170,68 @@ module RubyIndexer
|
|
152
170
|
# If `path` is a directory, just ignore it and continue indexing
|
153
171
|
end
|
154
172
|
|
173
|
+
# Follows aliases in a namespace. The algorithm keeps checking if the name is an alias and then recursively follows
|
174
|
+
# it. The idea is that we test the name in parts starting from the complete name to the first namespace. For
|
175
|
+
# `Foo::Bar::Baz`, we would test:
|
176
|
+
# 1. Is `Foo::Bar::Baz` an alias? Get the target and recursively follow its target
|
177
|
+
# 2. Is `Foo::Bar` an alias? Get the target and recursively follow its target
|
178
|
+
# 3. Is `Foo` an alias? Get the target and recursively follow its target
|
179
|
+
#
|
180
|
+
# If we find an alias, then we want to follow its target. In the same example, if `Foo::Bar` is an alias to
|
181
|
+
# `Something::Else`, then we first discover `Something::Else::Baz`. But `Something::Else::Baz` might contain other
|
182
|
+
# aliases, so we have to invoke `follow_aliased_namespace` again to check until we only return a real name
|
183
|
+
sig { params(name: String).returns(String) }
|
184
|
+
def follow_aliased_namespace(name)
|
185
|
+
parts = name.split("::")
|
186
|
+
real_parts = []
|
187
|
+
|
188
|
+
(parts.length - 1).downto(0).each do |i|
|
189
|
+
current_name = T.must(parts[0..i]).join("::")
|
190
|
+
entry = @entries[current_name]&.first
|
191
|
+
|
192
|
+
case entry
|
193
|
+
when Entry::Alias
|
194
|
+
target = entry.target
|
195
|
+
return follow_aliased_namespace("#{target}::#{real_parts.join("::")}")
|
196
|
+
when Entry::UnresolvedAlias
|
197
|
+
resolved = resolve_alias(entry)
|
198
|
+
|
199
|
+
if resolved.is_a?(Entry::UnresolvedAlias)
|
200
|
+
raise UnresolvableAliasError, "The constant #{resolved.name} is an alias to a non existing constant"
|
201
|
+
end
|
202
|
+
|
203
|
+
target = resolved.target
|
204
|
+
return follow_aliased_namespace("#{target}::#{real_parts.join("::")}")
|
205
|
+
else
|
206
|
+
real_parts.unshift(T.must(parts[i]))
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
real_parts.join("::")
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
# Attempts to resolve an UnresolvedAlias into a resolved Alias. If the unresolved alias is pointing to a constant
|
216
|
+
# that doesn't exist, then we return the same UnresolvedAlias
|
217
|
+
sig { params(entry: Entry::UnresolvedAlias).returns(T.any(Entry::Alias, Entry::UnresolvedAlias)) }
|
218
|
+
def resolve_alias(entry)
|
219
|
+
target = resolve(entry.target, entry.nesting)
|
220
|
+
return entry unless target
|
221
|
+
|
222
|
+
target_name = T.must(target.first).name
|
223
|
+
resolved_alias = Entry::Alias.new(target_name, entry)
|
224
|
+
|
225
|
+
# Replace the UnresolvedAlias by a resolved one so that we don't have to do this again later
|
226
|
+
original_entries = T.must(@entries[entry.name])
|
227
|
+
original_entries.delete(entry)
|
228
|
+
original_entries << resolved_alias
|
229
|
+
|
230
|
+
@entries_tree.insert(entry.name, original_entries)
|
231
|
+
|
232
|
+
resolved_alias
|
233
|
+
end
|
234
|
+
|
155
235
|
class Entry
|
156
236
|
extend T::Sig
|
157
237
|
|
@@ -167,12 +247,16 @@ module RubyIndexer
|
|
167
247
|
sig { returns(T::Array[String]) }
|
168
248
|
attr_reader :comments
|
169
249
|
|
250
|
+
sig { returns(Symbol) }
|
251
|
+
attr_accessor :visibility
|
252
|
+
|
170
253
|
sig { params(name: String, file_path: String, location: YARP::Location, comments: T::Array[String]).void }
|
171
254
|
def initialize(name, file_path, location, comments)
|
172
255
|
@name = name
|
173
256
|
@file_path = file_path
|
174
257
|
@location = location
|
175
258
|
@comments = comments
|
259
|
+
@visibility = T.let(:public, Symbol)
|
176
260
|
end
|
177
261
|
|
178
262
|
sig { returns(String) }
|
@@ -195,6 +279,58 @@ module RubyIndexer
|
|
195
279
|
|
196
280
|
class Constant < Entry
|
197
281
|
end
|
282
|
+
|
283
|
+
# An UnresolvedAlias points to a constant alias with a right hand side that has not yet been resolved. For
|
284
|
+
# example, if we find
|
285
|
+
#
|
286
|
+
# ```ruby
|
287
|
+
# CONST = Foo
|
288
|
+
# ```
|
289
|
+
# Before we have discovered `Foo`, there's no way to eagerly resolve this alias to the correct target constant.
|
290
|
+
# All aliases are inserted as UnresolvedAlias in the index first and then we lazily resolve them to the correct
|
291
|
+
# target in [rdoc-ref:Index#resolve]. If the right hand side contains a constant that doesn't exist, then it's not
|
292
|
+
# possible to resolve the alias and it will remain an UnresolvedAlias until the right hand side constant exists
|
293
|
+
class UnresolvedAlias < Entry
|
294
|
+
extend T::Sig
|
295
|
+
|
296
|
+
sig { returns(String) }
|
297
|
+
attr_reader :target
|
298
|
+
|
299
|
+
sig { returns(T::Array[String]) }
|
300
|
+
attr_reader :nesting
|
301
|
+
|
302
|
+
sig do
|
303
|
+
params(
|
304
|
+
target: String,
|
305
|
+
nesting: T::Array[String],
|
306
|
+
name: String,
|
307
|
+
file_path: String,
|
308
|
+
location: YARP::Location,
|
309
|
+
comments: T::Array[String],
|
310
|
+
).void
|
311
|
+
end
|
312
|
+
def initialize(target, nesting, name, file_path, location, comments) # rubocop:disable Metrics/ParameterLists
|
313
|
+
super(name, file_path, location, comments)
|
314
|
+
|
315
|
+
@target = target
|
316
|
+
@nesting = nesting
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Alias represents a resolved alias, which points to an existing constant target
|
321
|
+
class Alias < Entry
|
322
|
+
extend T::Sig
|
323
|
+
|
324
|
+
sig { returns(String) }
|
325
|
+
attr_reader :target
|
326
|
+
|
327
|
+
sig { params(target: String, unresolved_alias: UnresolvedAlias).void }
|
328
|
+
def initialize(target, unresolved_alias)
|
329
|
+
super(unresolved_alias.name, unresolved_alias.file_path, unresolved_alias.location, unresolved_alias.comments)
|
330
|
+
|
331
|
+
@target = target
|
332
|
+
end
|
333
|
+
end
|
198
334
|
end
|
199
335
|
end
|
200
336
|
end
|
@@ -36,9 +36,19 @@ module RubyIndexer
|
|
36
36
|
when YARP::ModuleNode
|
37
37
|
add_index_entry(node, Index::Entry::Module)
|
38
38
|
when YARP::ConstantWriteNode, YARP::ConstantOrWriteNode
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
name = fully_qualify_name(node.name.to_s)
|
40
|
+
add_constant(node, name)
|
41
|
+
when YARP::ConstantPathWriteNode, YARP::ConstantPathOrWriteNode, YARP::ConstantPathOperatorWriteNode,
|
42
|
+
YARP::ConstantPathAndWriteNode
|
43
|
+
|
44
|
+
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
45
|
+
return unless node.target.parent.nil? || node.target.parent.is_a?(YARP::ConstantReadNode)
|
46
|
+
|
47
|
+
name = fully_qualify_name(node.target.location.slice)
|
48
|
+
add_constant(node, name)
|
49
|
+
when YARP::CallNode
|
50
|
+
message = node.message
|
51
|
+
handle_private_constant(node) if message == "private_constant"
|
42
52
|
end
|
43
53
|
end
|
44
54
|
|
@@ -50,28 +60,66 @@ module RubyIndexer
|
|
50
60
|
|
51
61
|
private
|
52
62
|
|
53
|
-
sig
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
63
|
+
sig { params(node: YARP::CallNode).void }
|
64
|
+
def handle_private_constant(node)
|
65
|
+
arguments = node.arguments&.arguments
|
66
|
+
return unless arguments
|
67
|
+
|
68
|
+
first_argument = arguments.first
|
69
|
+
|
70
|
+
name = case first_argument
|
71
|
+
when YARP::StringNode
|
72
|
+
first_argument.content
|
73
|
+
when YARP::SymbolNode
|
74
|
+
first_argument.value
|
75
|
+
end
|
76
|
+
|
77
|
+
return unless name
|
78
|
+
|
79
|
+
receiver = node.receiver
|
80
|
+
name = "#{receiver.slice}::#{name}" if receiver
|
81
|
+
|
82
|
+
# The private_constant method does not resolve the constant name. It always points to a constant that needs to
|
83
|
+
# exist in the current namespace
|
84
|
+
entries = @index[fully_qualify_name(name)]
|
85
|
+
entries&.each { |entry| entry.visibility = :private }
|
61
86
|
end
|
62
87
|
|
63
88
|
sig do
|
64
89
|
params(
|
65
|
-
node: T.any(
|
90
|
+
node: T.any(
|
91
|
+
YARP::ConstantWriteNode,
|
92
|
+
YARP::ConstantOrWriteNode,
|
93
|
+
YARP::ConstantPathWriteNode,
|
94
|
+
YARP::ConstantPathOrWriteNode,
|
95
|
+
YARP::ConstantPathOperatorWriteNode,
|
96
|
+
YARP::ConstantPathAndWriteNode,
|
97
|
+
),
|
98
|
+
name: String,
|
66
99
|
).void
|
67
100
|
end
|
68
|
-
def
|
69
|
-
|
70
|
-
return unless node.target.parent.nil? || node.target.parent.is_a?(YARP::ConstantReadNode)
|
71
|
-
|
72
|
-
name = node.target.location.slice
|
101
|
+
def add_constant(node, name)
|
102
|
+
value = node.value
|
73
103
|
comments = collect_comments(node)
|
74
|
-
|
104
|
+
|
105
|
+
@index << case value
|
106
|
+
when YARP::ConstantReadNode, YARP::ConstantPathNode
|
107
|
+
Index::Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
|
108
|
+
when YARP::ConstantWriteNode, YARP::ConstantAndWriteNode, YARP::ConstantOrWriteNode,
|
109
|
+
YARP::ConstantOperatorWriteNode
|
110
|
+
|
111
|
+
# If the right hand side is another constant assignment, we need to visit it because that constant has to be
|
112
|
+
# indexed too
|
113
|
+
visit(value)
|
114
|
+
Index::Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
|
115
|
+
when YARP::ConstantPathWriteNode, YARP::ConstantPathOrWriteNode, YARP::ConstantPathOperatorWriteNode,
|
116
|
+
YARP::ConstantPathAndWriteNode
|
117
|
+
|
118
|
+
visit(value)
|
119
|
+
Index::Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
|
120
|
+
else
|
121
|
+
Index::Entry::Constant.new(name, @file_path, node.location, comments)
|
122
|
+
end
|
75
123
|
end
|
76
124
|
|
77
125
|
sig { params(node: T.any(YARP::ClassNode, YARP::ModuleNode), klass: T.class_of(Index::Entry)).void }
|
@@ -216,5 +216,28 @@ module RubyIndexer
|
|
216
216
|
second_foo_entry = @index["Bar"][0]
|
217
217
|
assert_equal("This is a Bar comment", second_foo_entry.comments.join("\n"))
|
218
218
|
end
|
219
|
+
|
220
|
+
def test_private_class_and_module_indexing
|
221
|
+
index(<<~RUBY)
|
222
|
+
class A
|
223
|
+
class B; end
|
224
|
+
private_constant(:B)
|
225
|
+
|
226
|
+
module C; end
|
227
|
+
private_constant("C")
|
228
|
+
|
229
|
+
class D; end
|
230
|
+
end
|
231
|
+
RUBY
|
232
|
+
|
233
|
+
b_const = @index["A::B"].first
|
234
|
+
assert_equal(:private, b_const.visibility)
|
235
|
+
|
236
|
+
c_const = @index["A::C"].first
|
237
|
+
assert_equal(:private, c_const.visibility)
|
238
|
+
|
239
|
+
d_const = @index["A::D"].first
|
240
|
+
assert_equal(:public, d_const.visibility)
|
241
|
+
end
|
219
242
|
end
|
220
243
|
end
|
@@ -15,6 +15,8 @@ module RubyIndexer
|
|
15
15
|
|
16
16
|
assert(indexables.none? { |indexable| indexable.full_path.include?("test/fixtures") })
|
17
17
|
assert(indexables.none? { |indexable| indexable.full_path.include?("minitest-reporters") })
|
18
|
+
assert(indexables.none? { |indexable| indexable.full_path.include?("ansi") })
|
19
|
+
assert(indexables.any? { |indexable| indexable.full_path.include?("sorbet-runtime") })
|
18
20
|
assert(indexables.none? { |indexable| indexable.full_path == __FILE__ })
|
19
21
|
end
|
20
22
|
|
@@ -104,5 +104,207 @@ module RubyIndexer
|
|
104
104
|
|
105
105
|
assert_no_entry
|
106
106
|
end
|
107
|
+
|
108
|
+
def test_private_constant_indexing
|
109
|
+
index(<<~RUBY)
|
110
|
+
class A
|
111
|
+
B = 1
|
112
|
+
private_constant(:B)
|
113
|
+
|
114
|
+
C = 2
|
115
|
+
private_constant("C")
|
116
|
+
|
117
|
+
D = 1
|
118
|
+
end
|
119
|
+
RUBY
|
120
|
+
|
121
|
+
b_const = @index["A::B"].first
|
122
|
+
assert_equal(:private, b_const.visibility)
|
123
|
+
|
124
|
+
c_const = @index["A::C"].first
|
125
|
+
assert_equal(:private, c_const.visibility)
|
126
|
+
|
127
|
+
d_const = @index["A::D"].first
|
128
|
+
assert_equal(:public, d_const.visibility)
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_marking_constants_as_private_reopening_namespaces
|
132
|
+
index(<<~RUBY)
|
133
|
+
module A
|
134
|
+
module B
|
135
|
+
CONST_A = 1
|
136
|
+
private_constant(:CONST_A)
|
137
|
+
|
138
|
+
CONST_B = 2
|
139
|
+
CONST_C = 3
|
140
|
+
end
|
141
|
+
|
142
|
+
module B
|
143
|
+
private_constant(:CONST_B)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
module A
|
148
|
+
module B
|
149
|
+
private_constant(:CONST_C)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
RUBY
|
153
|
+
|
154
|
+
a_const = @index["A::B::CONST_A"].first
|
155
|
+
assert_equal(:private, a_const.visibility)
|
156
|
+
|
157
|
+
b_const = @index["A::B::CONST_B"].first
|
158
|
+
assert_equal(:private, b_const.visibility)
|
159
|
+
|
160
|
+
c_const = @index["A::B::CONST_C"].first
|
161
|
+
assert_equal(:private, c_const.visibility)
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_marking_constants_as_private_with_receiver
|
165
|
+
index(<<~RUBY)
|
166
|
+
module A
|
167
|
+
module B
|
168
|
+
CONST_A = 1
|
169
|
+
CONST_B = 2
|
170
|
+
end
|
171
|
+
|
172
|
+
B.private_constant(:CONST_A)
|
173
|
+
end
|
174
|
+
|
175
|
+
A::B.private_constant(:CONST_B)
|
176
|
+
RUBY
|
177
|
+
|
178
|
+
a_const = @index["A::B::CONST_A"].first
|
179
|
+
assert_equal(:private, a_const.visibility)
|
180
|
+
|
181
|
+
b_const = @index["A::B::CONST_B"].first
|
182
|
+
assert_equal(:private, b_const.visibility)
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_indexing_constant_aliases
|
186
|
+
index(<<~RUBY)
|
187
|
+
module A
|
188
|
+
module B
|
189
|
+
module C
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
FIRST = B::C
|
194
|
+
end
|
195
|
+
|
196
|
+
SECOND = A::FIRST
|
197
|
+
RUBY
|
198
|
+
|
199
|
+
unresolve_entry = @index["A::FIRST"].first
|
200
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
201
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
202
|
+
assert_equal("B::C", unresolve_entry.target)
|
203
|
+
|
204
|
+
resolved_entry = @index.resolve("A::FIRST", []).first
|
205
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
206
|
+
assert_equal("A::B::C", resolved_entry.target)
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_aliasing_namespaces
|
210
|
+
index(<<~RUBY)
|
211
|
+
module A
|
212
|
+
module B
|
213
|
+
module C
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
ALIAS = B
|
218
|
+
end
|
219
|
+
|
220
|
+
module Other
|
221
|
+
ONE_MORE = A::ALIAS
|
222
|
+
end
|
223
|
+
RUBY
|
224
|
+
|
225
|
+
unresolve_entry = @index["A::ALIAS"].first
|
226
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
227
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
228
|
+
assert_equal("B", unresolve_entry.target)
|
229
|
+
|
230
|
+
resolved_entry = @index.resolve("ALIAS", ["A"]).first
|
231
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
232
|
+
assert_equal("A::B", resolved_entry.target)
|
233
|
+
|
234
|
+
resolved_entry = @index.resolve("ALIAS::C", ["A"]).first
|
235
|
+
assert_instance_of(Index::Entry::Module, resolved_entry)
|
236
|
+
assert_equal("A::B::C", resolved_entry.name)
|
237
|
+
|
238
|
+
unresolve_entry = @index["Other::ONE_MORE"].first
|
239
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
240
|
+
assert_equal(["Other"], unresolve_entry.nesting)
|
241
|
+
assert_equal("A::ALIAS", unresolve_entry.target)
|
242
|
+
|
243
|
+
resolved_entry = @index.resolve("Other::ONE_MORE::C", []).first
|
244
|
+
assert_instance_of(Index::Entry::Module, resolved_entry)
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_indexing_same_line_constant_aliases
|
248
|
+
index(<<~RUBY)
|
249
|
+
module A
|
250
|
+
B = C = 1
|
251
|
+
D = E ||= 1
|
252
|
+
F = G::H &&= 1
|
253
|
+
I::J = K::L = M = 1
|
254
|
+
end
|
255
|
+
RUBY
|
256
|
+
|
257
|
+
# B and C
|
258
|
+
unresolve_entry = @index["A::B"].first
|
259
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
260
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
261
|
+
assert_equal("C", unresolve_entry.target)
|
262
|
+
|
263
|
+
resolved_entry = @index.resolve("A::B", []).first
|
264
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
265
|
+
assert_equal("A::C", resolved_entry.target)
|
266
|
+
|
267
|
+
constant = @index["A::C"].first
|
268
|
+
assert_instance_of(Index::Entry::Constant, constant)
|
269
|
+
|
270
|
+
# D and E
|
271
|
+
unresolve_entry = @index["A::D"].first
|
272
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
273
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
274
|
+
assert_equal("E", unresolve_entry.target)
|
275
|
+
|
276
|
+
resolved_entry = @index.resolve("A::D", []).first
|
277
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
278
|
+
assert_equal("A::E", resolved_entry.target)
|
279
|
+
|
280
|
+
# F and G::H
|
281
|
+
unresolve_entry = @index["A::F"].first
|
282
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
283
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
284
|
+
assert_equal("G::H", unresolve_entry.target)
|
285
|
+
|
286
|
+
resolved_entry = @index.resolve("A::F", []).first
|
287
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
288
|
+
assert_equal("A::G::H", resolved_entry.target)
|
289
|
+
|
290
|
+
# I::J, K::L and M
|
291
|
+
unresolve_entry = @index["A::I::J"].first
|
292
|
+
assert_instance_of(Index::Entry::UnresolvedAlias, unresolve_entry)
|
293
|
+
assert_equal(["A"], unresolve_entry.nesting)
|
294
|
+
assert_equal("K::L", unresolve_entry.target)
|
295
|
+
|
296
|
+
resolved_entry = @index.resolve("A::I::J", []).first
|
297
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
298
|
+
assert_equal("A::K::L", resolved_entry.target)
|
299
|
+
|
300
|
+
# When we are resolving A::I::J, we invoke `resolve("K::L", ["A"])`, which recursively resolves A::K::L too.
|
301
|
+
# Therefore, both A::I::J and A::K::L point to A::M by the end of the previous resolve invocation
|
302
|
+
resolved_entry = @index["A::K::L"].first
|
303
|
+
assert_instance_of(Index::Entry::Alias, resolved_entry)
|
304
|
+
assert_equal("A::M", resolved_entry.target)
|
305
|
+
|
306
|
+
constant = @index["A::M"].first
|
307
|
+
assert_instance_of(Index::Entry::Constant, constant)
|
308
|
+
end
|
107
309
|
end
|
108
310
|
end
|
@@ -158,5 +158,25 @@ module RubyIndexer
|
|
158
158
|
results = @index.prefix_search("Ba", ["Foo"]).map { |entries| entries.map(&:name) }
|
159
159
|
assert_equal([["Foo::Bar", "Foo::Bar"], ["Foo::Baz"]], results)
|
160
160
|
end
|
161
|
+
|
162
|
+
def test_resolve_normalizes_top_level_names
|
163
|
+
@index.index_single(IndexablePath.new("/fake", "/fake/path/foo.rb"), <<~RUBY)
|
164
|
+
class Bar; end
|
165
|
+
|
166
|
+
module Foo
|
167
|
+
class Bar; end
|
168
|
+
end
|
169
|
+
RUBY
|
170
|
+
|
171
|
+
entries = @index.resolve("::Foo::Bar", [])
|
172
|
+
refute_nil(entries)
|
173
|
+
|
174
|
+
assert_equal("Foo::Bar", entries.first.name)
|
175
|
+
|
176
|
+
entries = @index.resolve("::Bar", ["Foo"])
|
177
|
+
refute_nil(entries)
|
178
|
+
|
179
|
+
assert_equal("Bar", entries.first.name)
|
180
|
+
end
|
161
181
|
end
|
162
182
|
end
|