rubydex 0.1.0.beta12-aarch64-linux
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 +7 -0
- data/LICENSE.txt +23 -0
- data/README.md +125 -0
- data/THIRD_PARTY_LICENSES.html +4562 -0
- data/exe/rdx +47 -0
- data/ext/rubydex/declaration.c +453 -0
- data/ext/rubydex/declaration.h +23 -0
- data/ext/rubydex/definition.c +284 -0
- data/ext/rubydex/definition.h +28 -0
- data/ext/rubydex/diagnostic.c +6 -0
- data/ext/rubydex/diagnostic.h +11 -0
- data/ext/rubydex/document.c +97 -0
- data/ext/rubydex/document.h +10 -0
- data/ext/rubydex/extconf.rb +138 -0
- data/ext/rubydex/graph.c +681 -0
- data/ext/rubydex/graph.h +10 -0
- data/ext/rubydex/handle.h +44 -0
- data/ext/rubydex/location.c +22 -0
- data/ext/rubydex/location.h +15 -0
- data/ext/rubydex/reference.c +123 -0
- data/ext/rubydex/reference.h +15 -0
- data/ext/rubydex/rubydex.c +22 -0
- data/ext/rubydex/utils.c +108 -0
- data/ext/rubydex/utils.h +34 -0
- data/lib/rubydex/3.2/rubydex.so +0 -0
- data/lib/rubydex/3.3/rubydex.so +0 -0
- data/lib/rubydex/3.4/rubydex.so +0 -0
- data/lib/rubydex/4.0/rubydex.so +0 -0
- data/lib/rubydex/comment.rb +17 -0
- data/lib/rubydex/diagnostic.rb +21 -0
- data/lib/rubydex/failures.rb +15 -0
- data/lib/rubydex/graph.rb +98 -0
- data/lib/rubydex/keyword.rb +17 -0
- data/lib/rubydex/keyword_parameter.rb +13 -0
- data/lib/rubydex/librubydex_sys.so +0 -0
- data/lib/rubydex/location.rb +90 -0
- data/lib/rubydex/mixin.rb +22 -0
- data/lib/rubydex/version.rb +5 -0
- data/lib/rubydex.rb +23 -0
- data/rbi/rubydex.rbi +422 -0
- data/rust/Cargo.lock +1851 -0
- data/rust/Cargo.toml +29 -0
- data/rust/about.hbs +78 -0
- data/rust/about.toml +10 -0
- data/rust/rubydex/Cargo.toml +42 -0
- data/rust/rubydex/src/compile_assertions.rs +13 -0
- data/rust/rubydex/src/diagnostic.rs +110 -0
- data/rust/rubydex/src/errors.rs +28 -0
- data/rust/rubydex/src/indexing/local_graph.rs +224 -0
- data/rust/rubydex/src/indexing/rbs_indexer.rs +1551 -0
- data/rust/rubydex/src/indexing/ruby_indexer.rs +2329 -0
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +4962 -0
- data/rust/rubydex/src/indexing.rs +210 -0
- data/rust/rubydex/src/integrity.rs +279 -0
- data/rust/rubydex/src/job_queue.rs +205 -0
- data/rust/rubydex/src/lib.rs +17 -0
- data/rust/rubydex/src/listing.rs +371 -0
- data/rust/rubydex/src/main.rs +160 -0
- data/rust/rubydex/src/model/built_in.rs +83 -0
- data/rust/rubydex/src/model/comment.rs +24 -0
- data/rust/rubydex/src/model/declaration.rs +671 -0
- data/rust/rubydex/src/model/definitions.rs +1682 -0
- data/rust/rubydex/src/model/document.rs +222 -0
- data/rust/rubydex/src/model/encoding.rs +22 -0
- data/rust/rubydex/src/model/graph.rs +3754 -0
- data/rust/rubydex/src/model/id.rs +110 -0
- data/rust/rubydex/src/model/identity_maps.rs +58 -0
- data/rust/rubydex/src/model/ids.rs +60 -0
- data/rust/rubydex/src/model/keywords.rs +256 -0
- data/rust/rubydex/src/model/name.rs +298 -0
- data/rust/rubydex/src/model/references.rs +111 -0
- data/rust/rubydex/src/model/string_ref.rs +50 -0
- data/rust/rubydex/src/model/visibility.rs +41 -0
- data/rust/rubydex/src/model.rs +15 -0
- data/rust/rubydex/src/offset.rs +147 -0
- data/rust/rubydex/src/position.rs +6 -0
- data/rust/rubydex/src/query.rs +1841 -0
- data/rust/rubydex/src/resolution.rs +6517 -0
- data/rust/rubydex/src/stats/memory.rs +71 -0
- data/rust/rubydex/src/stats/orphan_report.rs +264 -0
- data/rust/rubydex/src/stats/timer.rs +127 -0
- data/rust/rubydex/src/stats.rs +11 -0
- data/rust/rubydex/src/test_utils/context.rs +226 -0
- data/rust/rubydex/src/test_utils/graph_test.rs +730 -0
- data/rust/rubydex/src/test_utils/local_graph_test.rs +602 -0
- data/rust/rubydex/src/test_utils.rs +52 -0
- data/rust/rubydex/src/visualization/dot.rs +192 -0
- data/rust/rubydex/src/visualization.rs +6 -0
- data/rust/rubydex/tests/cli.rs +185 -0
- data/rust/rubydex-mcp/Cargo.toml +28 -0
- data/rust/rubydex-mcp/src/main.rs +48 -0
- data/rust/rubydex-mcp/src/server.rs +1145 -0
- data/rust/rubydex-mcp/src/tools.rs +49 -0
- data/rust/rubydex-mcp/tests/mcp.rs +302 -0
- data/rust/rubydex-sys/Cargo.toml +20 -0
- data/rust/rubydex-sys/build.rs +14 -0
- data/rust/rubydex-sys/cbindgen.toml +12 -0
- data/rust/rubydex-sys/src/declaration_api.rs +485 -0
- data/rust/rubydex-sys/src/definition_api.rs +443 -0
- data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
- data/rust/rubydex-sys/src/document_api.rs +85 -0
- data/rust/rubydex-sys/src/graph_api.rs +948 -0
- data/rust/rubydex-sys/src/lib.rs +79 -0
- data/rust/rubydex-sys/src/location_api.rs +79 -0
- data/rust/rubydex-sys/src/name_api.rs +135 -0
- data/rust/rubydex-sys/src/reference_api.rs +267 -0
- data/rust/rubydex-sys/src/utils.rs +70 -0
- data/rust/rustfmt.toml +2 -0
- metadata +159 -0
|
@@ -0,0 +1,1841 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
use std::error::Error;
|
|
3
|
+
use std::path::PathBuf;
|
|
4
|
+
use std::thread;
|
|
5
|
+
|
|
6
|
+
use url::Url;
|
|
7
|
+
|
|
8
|
+
use crate::model::built_in::OBJECT_ID;
|
|
9
|
+
use crate::model::declaration::{Ancestor, Declaration};
|
|
10
|
+
use crate::model::definitions::{Definition, Parameter};
|
|
11
|
+
use crate::model::graph::Graph;
|
|
12
|
+
use crate::model::identity_maps::IdentityHashSet;
|
|
13
|
+
use crate::model::ids::{DeclarationId, NameId, StringId, UriId};
|
|
14
|
+
use crate::model::keywords::{self, Keyword};
|
|
15
|
+
use crate::model::name::NameRef;
|
|
16
|
+
|
|
17
|
+
/// Controls how declaration names are matched against the search query.
|
|
18
|
+
#[derive(Default)]
|
|
19
|
+
pub enum MatchMode {
|
|
20
|
+
/// Fuzzy matching: query characters must appear in order in the target (case-insensitive). Used for LSP workspace
|
|
21
|
+
/// symbol.
|
|
22
|
+
#[default]
|
|
23
|
+
Fuzzy,
|
|
24
|
+
/// Exact partial matching: query must appear as a contiguous substring of the target. Used for precise filtering
|
|
25
|
+
/// (e.g., finding all declarations containing `#is_a?()`).
|
|
26
|
+
Exact,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// # Panics
|
|
30
|
+
///
|
|
31
|
+
/// Will panic if any of the threads panic
|
|
32
|
+
pub fn declaration_search(graph: &Graph, query: &str, match_mode: &MatchMode) -> Vec<DeclarationId> {
|
|
33
|
+
let num_threads = thread::available_parallelism().map(std::num::NonZero::get).unwrap_or(4);
|
|
34
|
+
let declarations = graph.declarations();
|
|
35
|
+
let ids: Vec<DeclarationId> = declarations.keys().copied().collect();
|
|
36
|
+
let chunk_size = ids.len().div_ceil(num_threads);
|
|
37
|
+
|
|
38
|
+
if chunk_size == 0 {
|
|
39
|
+
return Vec::new();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
thread::scope(|s| {
|
|
43
|
+
let handles: Vec<_> = ids
|
|
44
|
+
.chunks(chunk_size)
|
|
45
|
+
.map(|chunk| {
|
|
46
|
+
s.spawn(|| {
|
|
47
|
+
chunk
|
|
48
|
+
.iter()
|
|
49
|
+
.filter(|id| {
|
|
50
|
+
let declaration = declarations.get(id).unwrap();
|
|
51
|
+
let name = declaration.name();
|
|
52
|
+
match match_mode {
|
|
53
|
+
MatchMode::Fuzzy => {
|
|
54
|
+
// When the query is empty, we return everything as per the LSP specification.
|
|
55
|
+
// Otherwise, we compute the match score and return anything with a score greater than zero
|
|
56
|
+
query.is_empty() || match_score(query, name) > 0
|
|
57
|
+
}
|
|
58
|
+
MatchMode::Exact => name.contains(query),
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.copied()
|
|
62
|
+
.collect::<Vec<_>>()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
.collect();
|
|
66
|
+
|
|
67
|
+
handles.into_iter().flat_map(|h| h.join().unwrap()).collect()
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[must_use]
|
|
72
|
+
fn match_score(query: &str, target: &str) -> usize {
|
|
73
|
+
let mut query_chars = query.chars().peekable();
|
|
74
|
+
let mut score = 0;
|
|
75
|
+
|
|
76
|
+
// Count the number of matches in the order of the query, so that character ordering is taken into account
|
|
77
|
+
for t_char in target.chars() {
|
|
78
|
+
if let Some(&q_char) = query_chars.peek()
|
|
79
|
+
&& q_char.eq_ignore_ascii_case(&t_char)
|
|
80
|
+
{
|
|
81
|
+
score += 1;
|
|
82
|
+
query_chars.next();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If after going through the target, there are still query characters left, then some of the query can't be found
|
|
87
|
+
// in this target and we return zero to indicate a non-match
|
|
88
|
+
if query_chars.peek().is_some() { 0 } else { score }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Resolves a require path to its URI ID. Used for go-to-definition.
|
|
92
|
+
///
|
|
93
|
+
/// Searches the `load_path` in order and returns the first match, mirroring how Ruby's `require`
|
|
94
|
+
/// walks `$LOAD_PATH`.
|
|
95
|
+
#[must_use]
|
|
96
|
+
pub fn resolve_require_path(graph: &Graph, require_path: &str, load_path: &[PathBuf]) -> Option<UriId> {
|
|
97
|
+
let normalized = require_path.trim_end_matches(".rb");
|
|
98
|
+
|
|
99
|
+
for path in load_path {
|
|
100
|
+
let file_path = path.join(format!("{normalized}.rb"));
|
|
101
|
+
let Ok(url) = Url::from_file_path(&file_path) else {
|
|
102
|
+
continue;
|
|
103
|
+
};
|
|
104
|
+
let uri_id = UriId::from(url.as_str());
|
|
105
|
+
if graph.documents().contains_key(&uri_id) {
|
|
106
|
+
return Some(uri_id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
None
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Returns all require paths. Used for completion.
|
|
114
|
+
///
|
|
115
|
+
/// When multiple files resolve to the same require path (e.g., `foo.rb` exists in multiple
|
|
116
|
+
/// load paths), the one from the earliest load path wins. This matches Ruby's `require` behavior.
|
|
117
|
+
///
|
|
118
|
+
/// # Panics
|
|
119
|
+
///
|
|
120
|
+
/// Panics if one of the search threads panics
|
|
121
|
+
#[must_use]
|
|
122
|
+
pub fn require_paths(graph: &Graph, load_paths: &[PathBuf]) -> Vec<String> {
|
|
123
|
+
let num_threads = thread::available_parallelism().map(std::num::NonZero::get).unwrap_or(4);
|
|
124
|
+
let documents = graph.documents().iter().collect::<Vec<_>>();
|
|
125
|
+
let chunk_size = documents.len().div_ceil(num_threads);
|
|
126
|
+
|
|
127
|
+
if chunk_size == 0 {
|
|
128
|
+
return Vec::new();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let mut all_results = thread::scope(|scope| {
|
|
132
|
+
let handles: Vec<_> = documents
|
|
133
|
+
.chunks(chunk_size)
|
|
134
|
+
.map(|chunk| {
|
|
135
|
+
scope.spawn(move || {
|
|
136
|
+
chunk
|
|
137
|
+
.iter()
|
|
138
|
+
.filter_map(|(_, document)| document.require_path(load_paths))
|
|
139
|
+
.collect::<Vec<_>>()
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
.collect();
|
|
143
|
+
|
|
144
|
+
handles
|
|
145
|
+
.into_iter()
|
|
146
|
+
.flat_map(|handle| handle.join().unwrap())
|
|
147
|
+
.collect::<Vec<_>>()
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Sort by load path index so earlier load paths win during deduplication
|
|
151
|
+
all_results.sort_by_key(|(_, index)| *index);
|
|
152
|
+
|
|
153
|
+
let mut seen = HashSet::new();
|
|
154
|
+
all_results
|
|
155
|
+
.into_iter()
|
|
156
|
+
.filter(|(require_path, _)| seen.insert(require_path.clone()))
|
|
157
|
+
.map(|(require_path, _)| require_path)
|
|
158
|
+
.collect()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// A completion candidate
|
|
162
|
+
pub enum CompletionCandidate {
|
|
163
|
+
Declaration(DeclarationId),
|
|
164
|
+
KeywordArgument(StringId),
|
|
165
|
+
Keyword(&'static Keyword),
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// The context in which completion is being requested
|
|
169
|
+
pub enum CompletionReceiver {
|
|
170
|
+
/// Completion requested for an expression with no previous token (e.g.: at the start of a line with nothing before)
|
|
171
|
+
/// Includes: all keywords, all global variables and reacheable instance variables, class variables, constants and methods
|
|
172
|
+
Expression(NameId),
|
|
173
|
+
/// Completion requested after a namespace access operator (e.g.: `Foo::`)
|
|
174
|
+
/// Includes: all constants and singleton methods for the namespace and its ancestors
|
|
175
|
+
NamespaceAccess(DeclarationId),
|
|
176
|
+
/// Completion requested after a method call operator (e.g.: `foo.`, `@bar.`, `@@baz.`, `Qux.`).
|
|
177
|
+
/// In the case of singleton completion (e.g.: `Foo.`), the declaration ID should be for the singleton class (i.e.: `Foo::<Foo>`)
|
|
178
|
+
/// Includes: all methods that exist on the type of the receiver and its ancestors
|
|
179
|
+
MethodCall(DeclarationId),
|
|
180
|
+
/// Completion requested inside a method call's argument list (e.g.: `foo.bar(|)`)
|
|
181
|
+
/// Includes: everything expressions do plus keyword parameter names of the method being called
|
|
182
|
+
MethodArgument {
|
|
183
|
+
self_name_id: NameId,
|
|
184
|
+
method_decl_id: DeclarationId,
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
pub struct CompletionContext<'a> {
|
|
189
|
+
seen_members: IdentityHashSet<&'a StringId>,
|
|
190
|
+
completion_receiver: CompletionReceiver,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
impl<'a> CompletionContext<'a> {
|
|
194
|
+
#[must_use]
|
|
195
|
+
pub fn new(completion_receiver: CompletionReceiver) -> Self {
|
|
196
|
+
Self {
|
|
197
|
+
seen_members: IdentityHashSet::default(),
|
|
198
|
+
completion_receiver,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
pub fn dedup(&mut self, member_str_id: &'a StringId) -> bool {
|
|
203
|
+
self.seen_members.insert(member_str_id)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Collects completion candidate members
|
|
208
|
+
macro_rules! collect_candidates {
|
|
209
|
+
// Collect all members with no filtering
|
|
210
|
+
($declaration:expr, $context:expr, $candidates:expr) => {
|
|
211
|
+
for (member_str_id, member_declaration_id) in $declaration.members() {
|
|
212
|
+
if $context.dedup(member_str_id) {
|
|
213
|
+
$candidates.push(CompletionCandidate::Declaration(*member_declaration_id));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
// Collect only members matching certain kinds
|
|
218
|
+
($graph:expr, $declaration:expr, $context:expr, $candidates:expr, $kinds:pat) => {
|
|
219
|
+
for (member_str_id, member_declaration_id) in $declaration.members() {
|
|
220
|
+
let member = $graph.declarations().get(member_declaration_id).unwrap();
|
|
221
|
+
|
|
222
|
+
if matches!(member, $kinds) && $context.dedup(member_str_id) {
|
|
223
|
+
$candidates.push(CompletionCandidate::Declaration(*member_declaration_id));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Determines all possible completion candidates based on the current context of the cursor. There are multiple cases
|
|
230
|
+
/// that change what has to be collected for completion:
|
|
231
|
+
///
|
|
232
|
+
/// - Expressions collect all keywords, constants, methods, instance variables, class variables, local variables and
|
|
233
|
+
/// global variables that are reacheable from the current lexical scope and self type
|
|
234
|
+
/// - Expression in method arguments collects everything that expressions do and all keyword parameter names that are
|
|
235
|
+
/// applicable to the method being called
|
|
236
|
+
/// everything else
|
|
237
|
+
/// - Namespace access (e.g.: `Foo::`) collects all constants and singleton methods for the namespace that `Foo`
|
|
238
|
+
/// resolves to
|
|
239
|
+
/// - Method calls on anything (e.g.: `foo.`, `@bar.`, `@@baz.`, `Qux.`) collects all methods that exist on the type
|
|
240
|
+
/// returned by the receiver
|
|
241
|
+
///
|
|
242
|
+
/// # Panics
|
|
243
|
+
///
|
|
244
|
+
/// Will panic if we incorrectly inserted non namespace declarations as ancestors
|
|
245
|
+
///
|
|
246
|
+
/// # Errors
|
|
247
|
+
///
|
|
248
|
+
/// Will error if the given `self_name_id` does not point to a namespace declaration
|
|
249
|
+
pub fn completion_candidates<'a>(
|
|
250
|
+
graph: &'a Graph,
|
|
251
|
+
context: CompletionContext<'a>,
|
|
252
|
+
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
253
|
+
match context.completion_receiver {
|
|
254
|
+
CompletionReceiver::Expression(self_name_id) => expression_completion(graph, self_name_id, context),
|
|
255
|
+
CompletionReceiver::NamespaceAccess(decl_id) => namespace_access_completion(graph, decl_id, context),
|
|
256
|
+
CompletionReceiver::MethodCall(decl_id) => method_call_completion(graph, decl_id, context),
|
|
257
|
+
CompletionReceiver::MethodArgument {
|
|
258
|
+
self_name_id,
|
|
259
|
+
method_decl_id,
|
|
260
|
+
} => method_argument_completion(graph, self_name_id, method_decl_id, context),
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// Resolves a declaration ID to a namespace, following constant aliases if necessary.
|
|
265
|
+
///
|
|
266
|
+
/// Returns:
|
|
267
|
+
/// - `Ok(Some(id))` if the declaration is a namespace (directly or via alias)
|
|
268
|
+
/// - `Ok(None)` if the declaration does not exist in the graph
|
|
269
|
+
/// - `Err(...)` if the declaration exists but is not a namespace or alias to a namespace
|
|
270
|
+
fn resolve_to_namespace(graph: &Graph, decl_id: DeclarationId) -> Result<Option<DeclarationId>, Box<dyn Error>> {
|
|
271
|
+
match graph.declarations().get(&decl_id) {
|
|
272
|
+
Some(Declaration::Namespace(_)) => Ok(Some(decl_id)),
|
|
273
|
+
None => Ok(None),
|
|
274
|
+
Some(_) => {
|
|
275
|
+
if let Some(target_id) = graph.resolve_alias(&decl_id)
|
|
276
|
+
&& let Some(Declaration::Namespace(_)) = graph.declarations().get(&target_id)
|
|
277
|
+
{
|
|
278
|
+
Ok(Some(target_id))
|
|
279
|
+
} else {
|
|
280
|
+
Err(format!("Expected declaration {decl_id:?} to be a namespace or alias to a namespace").into())
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// Collect completion for a namespace access (e.g.: `Foo::`)
|
|
287
|
+
fn namespace_access_completion<'a>(
|
|
288
|
+
graph: &'a Graph,
|
|
289
|
+
namespace_decl_id: DeclarationId,
|
|
290
|
+
mut context: CompletionContext<'a>,
|
|
291
|
+
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
292
|
+
let Some(resolved_id) = resolve_to_namespace(graph, namespace_decl_id)? else {
|
|
293
|
+
return Ok(Vec::new());
|
|
294
|
+
};
|
|
295
|
+
let namespace = graph.declarations().get(&resolved_id).unwrap().as_namespace().unwrap();
|
|
296
|
+
let mut candidates = Vec::new();
|
|
297
|
+
|
|
298
|
+
// Walk ancestors collecting inherited constants, stopping at Object to avoid surfacing top-level constants
|
|
299
|
+
// from Object, Kernel, BasicObject, etc.
|
|
300
|
+
for ancestor in namespace.ancestors() {
|
|
301
|
+
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
302
|
+
// Do not offer completion for constants inherited after `Object` (e.g.: `Object::String`). While this is
|
|
303
|
+
// valid Ruby code, it's extremely uncommon and not a super valuable completion suggestion
|
|
304
|
+
if *ancestor_id == *OBJECT_ID {
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
|
|
309
|
+
|
|
310
|
+
collect_candidates!(
|
|
311
|
+
graph,
|
|
312
|
+
&ancestor_decl,
|
|
313
|
+
context,
|
|
314
|
+
candidates,
|
|
315
|
+
Declaration::Namespace(_) | Declaration::Constant(_) | Declaration::ConstantAlias(_)
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Collect singleton methods from the singleton class and its ancestors
|
|
321
|
+
if let Some(singleton_id) = namespace.singleton_class() {
|
|
322
|
+
let singleton = graph.declarations().get(singleton_id).unwrap().as_namespace().unwrap();
|
|
323
|
+
|
|
324
|
+
for ancestor in singleton.ancestors() {
|
|
325
|
+
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
326
|
+
let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
|
|
327
|
+
collect_candidates!(graph, &ancestor_decl, context, candidates, Declaration::Method(_));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
Ok(candidates)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/// Collect completion for a method call (e.g.: `foo.`, `@bar.`, `Baz.`)
|
|
336
|
+
fn method_call_completion<'a>(
|
|
337
|
+
graph: &'a Graph,
|
|
338
|
+
receiver_decl_id: DeclarationId,
|
|
339
|
+
mut context: CompletionContext<'a>,
|
|
340
|
+
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
341
|
+
let Some(resolved_id) = resolve_to_namespace(graph, receiver_decl_id)? else {
|
|
342
|
+
return Ok(Vec::new());
|
|
343
|
+
};
|
|
344
|
+
let namespace = graph.declarations().get(&resolved_id).unwrap().as_namespace().unwrap();
|
|
345
|
+
let mut candidates = Vec::new();
|
|
346
|
+
|
|
347
|
+
for ancestor in namespace.ancestors() {
|
|
348
|
+
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
349
|
+
let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
|
|
350
|
+
collect_candidates!(graph, &ancestor_decl, context, candidates, Declaration::Method(_));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
Ok(candidates)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/// Collect completion for an expression
|
|
358
|
+
fn expression_completion<'a>(
|
|
359
|
+
graph: &'a Graph,
|
|
360
|
+
self_name_id: NameId,
|
|
361
|
+
mut context: CompletionContext<'a>,
|
|
362
|
+
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
363
|
+
let Some(name_ref) = graph.names().get(&self_name_id) else {
|
|
364
|
+
return Err(format!("Name {self_name_id} not found in graph").into());
|
|
365
|
+
};
|
|
366
|
+
let NameRef::Resolved(name_ref) = name_ref else {
|
|
367
|
+
return Err(format!("Expected name {self_name_id} to be resolved").into());
|
|
368
|
+
};
|
|
369
|
+
let Some(self_decl) = graph
|
|
370
|
+
.declarations()
|
|
371
|
+
.get(name_ref.declaration_id())
|
|
372
|
+
.and_then(|d| d.as_namespace())
|
|
373
|
+
else {
|
|
374
|
+
return Err("Expected associated declaration to be a namespace".into());
|
|
375
|
+
};
|
|
376
|
+
let mut candidates = Vec::new();
|
|
377
|
+
|
|
378
|
+
// Walk the name's lexical scopes, collecting all constant completion members
|
|
379
|
+
let mut current_name_id = Some(self_name_id);
|
|
380
|
+
|
|
381
|
+
while let Some(id) = current_name_id {
|
|
382
|
+
let NameRef::Resolved(name_ref) = graph.names().get(&id).unwrap() else {
|
|
383
|
+
break;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
let nesting_decl = graph
|
|
387
|
+
.declarations()
|
|
388
|
+
.get(name_ref.declaration_id())
|
|
389
|
+
.unwrap()
|
|
390
|
+
.as_namespace()
|
|
391
|
+
.unwrap();
|
|
392
|
+
|
|
393
|
+
collect_candidates!(
|
|
394
|
+
graph,
|
|
395
|
+
&nesting_decl,
|
|
396
|
+
context,
|
|
397
|
+
candidates,
|
|
398
|
+
Declaration::Namespace(_) | Declaration::Constant(_) | Declaration::ConstantAlias(_)
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
current_name_id = *name_ref.nesting();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Include all top level constants and globals, which are accessible everywhere
|
|
405
|
+
let object = graph.declarations().get(&OBJECT_ID).unwrap().as_namespace().unwrap();
|
|
406
|
+
collect_candidates!(
|
|
407
|
+
graph,
|
|
408
|
+
&object,
|
|
409
|
+
context,
|
|
410
|
+
candidates,
|
|
411
|
+
Declaration::Namespace(_)
|
|
412
|
+
| Declaration::Constant(_)
|
|
413
|
+
| Declaration::ConstantAlias(_)
|
|
414
|
+
| Declaration::GlobalVariable(_)
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// Walk ancestors collecting all applicable completion members
|
|
418
|
+
for ancestor in self_decl.ancestors() {
|
|
419
|
+
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
420
|
+
let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
|
|
421
|
+
collect_candidates!(&ancestor_decl, context, candidates);
|
|
422
|
+
|
|
423
|
+
// Collect class variables from the attached object, which are available at any singleton class level
|
|
424
|
+
// within self
|
|
425
|
+
let attached_object = graph.attached_object(ancestor_decl);
|
|
426
|
+
collect_candidates!(
|
|
427
|
+
graph,
|
|
428
|
+
&attached_object,
|
|
429
|
+
context,
|
|
430
|
+
candidates,
|
|
431
|
+
Declaration::ClassVariable(_)
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Keywords are always available in expression contexts
|
|
437
|
+
candidates.extend(keywords::KEYWORDS.iter().map(CompletionCandidate::Keyword));
|
|
438
|
+
Ok(candidates)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// Collect completion for a method argument (e.g.: `foo.bar(|)`)
|
|
442
|
+
fn method_argument_completion<'a>(
|
|
443
|
+
graph: &'a Graph,
|
|
444
|
+
self_name_id: NameId,
|
|
445
|
+
method_decl_id: DeclarationId,
|
|
446
|
+
context: CompletionContext<'a>,
|
|
447
|
+
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
448
|
+
let mut candidates = expression_completion(graph, self_name_id, context)?;
|
|
449
|
+
let Some(method_decl) = graph.declarations().get(&method_decl_id) else {
|
|
450
|
+
return Ok(candidates);
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// Find the first Method definition to extract keyword parameters
|
|
454
|
+
for def_id in method_decl.definitions() {
|
|
455
|
+
if let Some(Definition::Method(method_def)) = graph.definitions().get(def_id) {
|
|
456
|
+
for signature in method_def.signatures().as_slice() {
|
|
457
|
+
for param in signature {
|
|
458
|
+
match param {
|
|
459
|
+
Parameter::RequiredKeyword(p) | Parameter::OptionalKeyword(p) => {
|
|
460
|
+
candidates.push(CompletionCandidate::KeywordArgument(*p.str()));
|
|
461
|
+
}
|
|
462
|
+
_ => {}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
Ok(candidates)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
#[cfg(test)]
|
|
474
|
+
mod tests {
|
|
475
|
+
use std::str::FromStr;
|
|
476
|
+
use url::Url;
|
|
477
|
+
|
|
478
|
+
use super::*;
|
|
479
|
+
use crate::{
|
|
480
|
+
model::{
|
|
481
|
+
ids::StringId,
|
|
482
|
+
name::{Name, ParentScope},
|
|
483
|
+
},
|
|
484
|
+
test_utils::GraphTest,
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
macro_rules! assert_results_eq {
|
|
488
|
+
($context:expr, $query:expr, $expected:expr) => {
|
|
489
|
+
assert_results_eq!($context, $query, &MatchMode::default(), $expected);
|
|
490
|
+
};
|
|
491
|
+
($context:expr, $query:expr, $match_mode:expr, $expected:expr) => {
|
|
492
|
+
let actual = declaration_search(&$context.graph(), $query, $match_mode);
|
|
493
|
+
assert_eq!(
|
|
494
|
+
actual,
|
|
495
|
+
$expected
|
|
496
|
+
.into_iter()
|
|
497
|
+
.map(|s| DeclarationId::from(s))
|
|
498
|
+
.collect::<Vec<DeclarationId>>(),
|
|
499
|
+
"Unexpected search results: {:?}",
|
|
500
|
+
actual
|
|
501
|
+
.iter()
|
|
502
|
+
.map(|id| $context
|
|
503
|
+
.graph()
|
|
504
|
+
.declarations()
|
|
505
|
+
.get(id)
|
|
506
|
+
.unwrap()
|
|
507
|
+
.name()
|
|
508
|
+
.to_string())
|
|
509
|
+
.collect::<Vec<String>>()
|
|
510
|
+
);
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
fn candidate_label(context: &GraphTest, candidate: &CompletionCandidate) -> String {
|
|
515
|
+
match candidate {
|
|
516
|
+
CompletionCandidate::Declaration(id) => context.graph().declarations().get(id).unwrap().name().to_string(),
|
|
517
|
+
CompletionCandidate::KeywordArgument(str_id) => {
|
|
518
|
+
format!("{}:", context.graph().strings().get(str_id).unwrap().as_str())
|
|
519
|
+
}
|
|
520
|
+
CompletionCandidate::Keyword(kw) => kw.name().to_string(),
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
macro_rules! assert_completion_eq {
|
|
525
|
+
($context:expr, $receiver:expr, $expected:expr) => {
|
|
526
|
+
assert_eq!(
|
|
527
|
+
$expected,
|
|
528
|
+
*completion_candidates($context.graph(), CompletionContext::new($receiver))
|
|
529
|
+
.unwrap()
|
|
530
|
+
.iter()
|
|
531
|
+
.map(|candidate| candidate_label(&$context, candidate))
|
|
532
|
+
.collect::<Vec<_>>()
|
|
533
|
+
);
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/// Asserts declaration and keyword argument completion candidates, excluding language keywords.
|
|
538
|
+
/// Language keywords are always present in expression contexts and tested separately.
|
|
539
|
+
macro_rules! assert_declaration_completion_eq {
|
|
540
|
+
($context:expr, $receiver:expr, $expected:expr) => {
|
|
541
|
+
assert_eq!(
|
|
542
|
+
$expected,
|
|
543
|
+
*completion_candidates($context.graph(), CompletionContext::new($receiver))
|
|
544
|
+
.unwrap()
|
|
545
|
+
.iter()
|
|
546
|
+
.filter(|c| !matches!(c, CompletionCandidate::Keyword(_)))
|
|
547
|
+
.map(|candidate| candidate_label(&$context, candidate))
|
|
548
|
+
.collect::<Vec<_>>()
|
|
549
|
+
);
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
#[test]
|
|
554
|
+
fn fuzzy_search_returns_partial_matches() {
|
|
555
|
+
let mut context = GraphTest::new();
|
|
556
|
+
context.index_uri("file:///foo.rb", {
|
|
557
|
+
r"
|
|
558
|
+
class Foo
|
|
559
|
+
end
|
|
560
|
+
"
|
|
561
|
+
});
|
|
562
|
+
context.resolve();
|
|
563
|
+
assert_results_eq!(context, "Fo", ["Foo"]);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
#[test]
|
|
567
|
+
fn exact_partial_match_search() {
|
|
568
|
+
let mut context = GraphTest::new();
|
|
569
|
+
context.index_uri("file:///foo.rb", {
|
|
570
|
+
r"
|
|
571
|
+
class Foo
|
|
572
|
+
def is_a_foo?; end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
class Bar < Foo
|
|
576
|
+
def is_a?(other); end
|
|
577
|
+
end
|
|
578
|
+
"
|
|
579
|
+
});
|
|
580
|
+
context.resolve();
|
|
581
|
+
assert_results_eq!(context, "#is_a?()", &MatchMode::Exact, ["Bar#is_a?()"]);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
#[test]
|
|
585
|
+
fn exact_match_empty_query_returns_all() {
|
|
586
|
+
let mut context = GraphTest::new();
|
|
587
|
+
context.index_uri("file:///foo.rb", {
|
|
588
|
+
r"
|
|
589
|
+
class Foo; end
|
|
590
|
+
class Bar; end
|
|
591
|
+
"
|
|
592
|
+
});
|
|
593
|
+
context.resolve();
|
|
594
|
+
let exact_results = declaration_search(context.graph(), "", &MatchMode::Exact);
|
|
595
|
+
let fuzzy_results = declaration_search(context.graph(), "", &MatchMode::Fuzzy);
|
|
596
|
+
|
|
597
|
+
assert_eq!(exact_results.len(), fuzzy_results.len());
|
|
598
|
+
assert_eq!(context.graph().declarations().len(), exact_results.len());
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
#[test]
|
|
602
|
+
fn exact_match_is_case_sensitive() {
|
|
603
|
+
let mut context = GraphTest::new();
|
|
604
|
+
context.index_uri("file:///foo.rb", {
|
|
605
|
+
r"
|
|
606
|
+
class Foo
|
|
607
|
+
def is_a_foo?; end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
class Bar < Foo
|
|
611
|
+
def is_a?(other); end
|
|
612
|
+
end
|
|
613
|
+
"
|
|
614
|
+
});
|
|
615
|
+
context.resolve();
|
|
616
|
+
|
|
617
|
+
assert_results_eq!(context, "#Is_A?()", &MatchMode::Exact, Vec::<&str>::new());
|
|
618
|
+
assert_results_eq!(context, "#Is_A?()", ["Foo#is_a_foo?()", "Bar#is_a?()"]);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
fn test_root() -> PathBuf {
|
|
622
|
+
let root = if cfg!(windows) { "C:\\" } else { "/" };
|
|
623
|
+
PathBuf::from_str(root).unwrap()
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
#[test]
|
|
627
|
+
fn test_resolve_require_path() {
|
|
628
|
+
let root = test_root();
|
|
629
|
+
let path = root
|
|
630
|
+
.join("lib")
|
|
631
|
+
.join("foo")
|
|
632
|
+
.join("bar.rb")
|
|
633
|
+
.to_str()
|
|
634
|
+
.unwrap()
|
|
635
|
+
.to_string();
|
|
636
|
+
let uri = Url::from_file_path(path).unwrap().to_string();
|
|
637
|
+
let load_paths = [root.join("lib")];
|
|
638
|
+
|
|
639
|
+
let mut context = GraphTest::new();
|
|
640
|
+
context.index_uri(&uri, "class Bar; end");
|
|
641
|
+
|
|
642
|
+
// finds basic path
|
|
643
|
+
let uri_id = resolve_require_path(context.graph(), "foo/bar", &load_paths);
|
|
644
|
+
assert!(uri_id.is_some());
|
|
645
|
+
let doc = context.graph().documents().get(&uri_id.unwrap()).unwrap();
|
|
646
|
+
assert_eq!(uri, doc.uri());
|
|
647
|
+
|
|
648
|
+
// handles .rb suffix
|
|
649
|
+
let uri_id_with_rb = resolve_require_path(context.graph(), "foo/bar.rb", &load_paths);
|
|
650
|
+
assert_eq!(uri_id, uri_id_with_rb);
|
|
651
|
+
|
|
652
|
+
// returns None for nonexistent
|
|
653
|
+
assert!(resolve_require_path(context.graph(), "nonexistent", &load_paths).is_none());
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
#[test]
|
|
657
|
+
fn test_resolve_require_path_prefers_earliest_load_path() {
|
|
658
|
+
let root = test_root();
|
|
659
|
+
let lib_path = root.join("lib").join("foo").join("bar.rb");
|
|
660
|
+
let test_path = root.join("test").join("foo").join("bar.rb");
|
|
661
|
+
let lib_uri = Url::from_file_path(&lib_path).unwrap().to_string();
|
|
662
|
+
let test_uri = Url::from_file_path(&test_path).unwrap().to_string();
|
|
663
|
+
|
|
664
|
+
let mut context = GraphTest::new();
|
|
665
|
+
context.index_uri(&lib_uri, "class Bar; end");
|
|
666
|
+
context.index_uri(&test_uri, "class Bar; end");
|
|
667
|
+
|
|
668
|
+
// lib comes first in load paths
|
|
669
|
+
let load_paths = [root.join("lib"), root.join("test")];
|
|
670
|
+
let uri_id = resolve_require_path(context.graph(), "foo/bar", &load_paths).unwrap();
|
|
671
|
+
let doc = context.graph().documents().get(&uri_id).unwrap();
|
|
672
|
+
assert!(
|
|
673
|
+
doc.uri().contains("lib/foo/bar.rb"),
|
|
674
|
+
"Expected lib path, got {}",
|
|
675
|
+
doc.uri()
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
// test comes first in load paths
|
|
679
|
+
let load_paths = [root.join("test"), root.join("lib")];
|
|
680
|
+
let uri_id = resolve_require_path(context.graph(), "foo/bar", &load_paths).unwrap();
|
|
681
|
+
let doc = context.graph().documents().get(&uri_id).unwrap();
|
|
682
|
+
assert!(
|
|
683
|
+
doc.uri().contains("test/foo/bar.rb"),
|
|
684
|
+
"Expected test path, got {}",
|
|
685
|
+
doc.uri()
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
#[test]
|
|
690
|
+
fn test_require_paths() {
|
|
691
|
+
let root = test_root();
|
|
692
|
+
let path_bar = root.join("lib").join("foo").join("bar.rb");
|
|
693
|
+
let path_qux = root.join("lib").join("foo").join("qux.rb");
|
|
694
|
+
let path_foobar = root.join("lib").join("foobar.rb");
|
|
695
|
+
let uri_bar = Url::from_file_path(&path_bar).unwrap().to_string();
|
|
696
|
+
let uri_qux = Url::from_file_path(&path_qux).unwrap().to_string();
|
|
697
|
+
let uri_foobar = Url::from_file_path(&path_foobar).unwrap().to_string();
|
|
698
|
+
let load_paths = vec![root.join("lib")];
|
|
699
|
+
|
|
700
|
+
let mut context = GraphTest::new();
|
|
701
|
+
context.index_uri(&uri_bar, "class Bar; end");
|
|
702
|
+
context.index_uri(&uri_qux, "class Qux; end");
|
|
703
|
+
context.index_uri(&uri_foobar, "class Foobar; end");
|
|
704
|
+
|
|
705
|
+
let results = require_paths(context.graph(), &load_paths);
|
|
706
|
+
|
|
707
|
+
assert_eq!(3, results.len());
|
|
708
|
+
assert!(results.contains(&"foo/bar".to_string()));
|
|
709
|
+
assert!(results.contains(&"foo/qux".to_string()));
|
|
710
|
+
assert!(results.contains(&"foobar".to_string()));
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
#[test]
|
|
714
|
+
fn test_require_paths_deduplicates_by_load_path_order() {
|
|
715
|
+
let root = test_root();
|
|
716
|
+
let path1 = root.join("lib1").join("foo.rb");
|
|
717
|
+
let path2 = root.join("lib2").join("foo.rb");
|
|
718
|
+
let uri1 = Url::from_file_path(&path1).unwrap().to_string();
|
|
719
|
+
let uri2 = Url::from_file_path(&path2).unwrap().to_string();
|
|
720
|
+
let load_paths = [root.join("lib1"), root.join("lib2")];
|
|
721
|
+
|
|
722
|
+
let mut context = GraphTest::new();
|
|
723
|
+
context.index_uri(&uri1, "class Foo; end");
|
|
724
|
+
context.index_uri(&uri2, "class Foo; end");
|
|
725
|
+
|
|
726
|
+
let results = require_paths(context.graph(), &load_paths);
|
|
727
|
+
|
|
728
|
+
let foo_count = results.iter().filter(|p| *p == "foo").count();
|
|
729
|
+
assert_eq!(1, foo_count);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
#[test]
|
|
733
|
+
fn completion_candidates_on_self() {
|
|
734
|
+
let mut context = GraphTest::new();
|
|
735
|
+
|
|
736
|
+
context.index_uri(
|
|
737
|
+
"file:///foo.rb",
|
|
738
|
+
"
|
|
739
|
+
module Foo
|
|
740
|
+
CONST = 1
|
|
741
|
+
def bar; end
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
class Parent
|
|
745
|
+
def initialize
|
|
746
|
+
@var = 1
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
class Child < Parent
|
|
751
|
+
include Foo
|
|
752
|
+
|
|
753
|
+
def baz
|
|
754
|
+
# Completion in this `self` context
|
|
755
|
+
end
|
|
756
|
+
end
|
|
757
|
+
",
|
|
758
|
+
);
|
|
759
|
+
context.resolve();
|
|
760
|
+
|
|
761
|
+
let name_id = Name::new(StringId::from("Child"), ParentScope::None, None).id();
|
|
762
|
+
assert_declaration_completion_eq!(
|
|
763
|
+
context,
|
|
764
|
+
CompletionReceiver::Expression(name_id),
|
|
765
|
+
[
|
|
766
|
+
"Class",
|
|
767
|
+
"BasicObject",
|
|
768
|
+
"Child",
|
|
769
|
+
"Parent",
|
|
770
|
+
"Kernel",
|
|
771
|
+
"Module",
|
|
772
|
+
"Foo",
|
|
773
|
+
"Object",
|
|
774
|
+
"Child#baz()",
|
|
775
|
+
"Foo::CONST",
|
|
776
|
+
"Foo#bar()",
|
|
777
|
+
"Parent#initialize()",
|
|
778
|
+
"Parent#@var"
|
|
779
|
+
]
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
#[test]
|
|
784
|
+
fn completion_candidates_shows_first_option_in_the_ancestor_chain() {
|
|
785
|
+
let mut context = GraphTest::new();
|
|
786
|
+
|
|
787
|
+
context.index_uri(
|
|
788
|
+
"file:///foo.rb",
|
|
789
|
+
"
|
|
790
|
+
module Foo
|
|
791
|
+
def bar; end
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
class Parent
|
|
795
|
+
def bar; end
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
class Child < Parent
|
|
799
|
+
def bar
|
|
800
|
+
# Completion in this `self` context
|
|
801
|
+
end
|
|
802
|
+
end
|
|
803
|
+
",
|
|
804
|
+
);
|
|
805
|
+
context.resolve();
|
|
806
|
+
|
|
807
|
+
let name_id = Name::new(StringId::from("Child"), ParentScope::None, None).id();
|
|
808
|
+
assert_declaration_completion_eq!(
|
|
809
|
+
context,
|
|
810
|
+
CompletionReceiver::Expression(name_id),
|
|
811
|
+
[
|
|
812
|
+
"Class",
|
|
813
|
+
"BasicObject",
|
|
814
|
+
"Child",
|
|
815
|
+
"Parent",
|
|
816
|
+
"Kernel",
|
|
817
|
+
"Module",
|
|
818
|
+
"Foo",
|
|
819
|
+
"Object",
|
|
820
|
+
"Child#bar()"
|
|
821
|
+
]
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
#[test]
|
|
826
|
+
fn completion_candidates_in_a_cyclic_ancestor_chain() {
|
|
827
|
+
let mut context = GraphTest::new();
|
|
828
|
+
|
|
829
|
+
context.index_uri(
|
|
830
|
+
"file:///foo.rb",
|
|
831
|
+
"
|
|
832
|
+
module Foo
|
|
833
|
+
include Baz
|
|
834
|
+
|
|
835
|
+
def foo_m; end
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
module Bar
|
|
839
|
+
include Foo
|
|
840
|
+
|
|
841
|
+
def bar_m; end
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
module Baz
|
|
845
|
+
include Bar
|
|
846
|
+
|
|
847
|
+
def baz_m; end
|
|
848
|
+
end
|
|
849
|
+
",
|
|
850
|
+
);
|
|
851
|
+
context.resolve();
|
|
852
|
+
|
|
853
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
854
|
+
assert_declaration_completion_eq!(
|
|
855
|
+
context,
|
|
856
|
+
CompletionReceiver::Expression(name_id),
|
|
857
|
+
[
|
|
858
|
+
"Foo",
|
|
859
|
+
"Class",
|
|
860
|
+
"BasicObject",
|
|
861
|
+
"Object",
|
|
862
|
+
"Kernel",
|
|
863
|
+
"Module",
|
|
864
|
+
"Baz",
|
|
865
|
+
"Bar",
|
|
866
|
+
"Foo#foo_m()",
|
|
867
|
+
"Baz#baz_m()",
|
|
868
|
+
"Bar#bar_m()"
|
|
869
|
+
]
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
#[test]
|
|
874
|
+
fn completion_candidates_for_class_variables() {
|
|
875
|
+
let mut context = GraphTest::new();
|
|
876
|
+
|
|
877
|
+
context.index_uri(
|
|
878
|
+
"file:///foo.rb",
|
|
879
|
+
"
|
|
880
|
+
class Foo
|
|
881
|
+
@@foo_var = 1
|
|
882
|
+
|
|
883
|
+
class << self
|
|
884
|
+
def do_something
|
|
885
|
+
# Completion in this `self` context
|
|
886
|
+
end
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
class Bar < Foo
|
|
891
|
+
def baz
|
|
892
|
+
# Other completion in this `self` context
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
",
|
|
896
|
+
);
|
|
897
|
+
context.resolve();
|
|
898
|
+
|
|
899
|
+
let foo_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
900
|
+
let name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_id), None).id();
|
|
901
|
+
assert_declaration_completion_eq!(
|
|
902
|
+
context,
|
|
903
|
+
CompletionReceiver::Expression(name_id),
|
|
904
|
+
[
|
|
905
|
+
"Module",
|
|
906
|
+
"Class",
|
|
907
|
+
"Object",
|
|
908
|
+
"BasicObject",
|
|
909
|
+
"Kernel",
|
|
910
|
+
"Foo",
|
|
911
|
+
"Bar",
|
|
912
|
+
"Foo::<Foo>#do_something()",
|
|
913
|
+
"Foo#@@foo_var"
|
|
914
|
+
]
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
918
|
+
assert_declaration_completion_eq!(
|
|
919
|
+
context,
|
|
920
|
+
CompletionReceiver::Expression(name_id),
|
|
921
|
+
[
|
|
922
|
+
"Module",
|
|
923
|
+
"Class",
|
|
924
|
+
"Object",
|
|
925
|
+
"BasicObject",
|
|
926
|
+
"Kernel",
|
|
927
|
+
"Foo",
|
|
928
|
+
"Bar",
|
|
929
|
+
"Bar#baz()",
|
|
930
|
+
"Foo#@@foo_var"
|
|
931
|
+
]
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
#[test]
|
|
936
|
+
fn completion_candidates_includes_constants_accessible_within_lexical_scope() {
|
|
937
|
+
let mut context = GraphTest::new();
|
|
938
|
+
|
|
939
|
+
context.index_uri(
|
|
940
|
+
"file:///foo.rb",
|
|
941
|
+
"
|
|
942
|
+
module Foo
|
|
943
|
+
CONST_A = 1
|
|
944
|
+
|
|
945
|
+
class ::Bar
|
|
946
|
+
def bar_m
|
|
947
|
+
# Completion in this `self` context
|
|
948
|
+
end
|
|
949
|
+
end
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
class Bar
|
|
953
|
+
def bar_m2
|
|
954
|
+
# Completion in this `self` context
|
|
955
|
+
end
|
|
956
|
+
end
|
|
957
|
+
",
|
|
958
|
+
);
|
|
959
|
+
context.resolve();
|
|
960
|
+
|
|
961
|
+
let name_id = Name::new(
|
|
962
|
+
StringId::from("Bar"),
|
|
963
|
+
ParentScope::TopLevel,
|
|
964
|
+
Some(Name::new(StringId::from("Foo"), ParentScope::None, None).id()),
|
|
965
|
+
)
|
|
966
|
+
.id();
|
|
967
|
+
assert_declaration_completion_eq!(
|
|
968
|
+
context,
|
|
969
|
+
CompletionReceiver::Expression(name_id),
|
|
970
|
+
[
|
|
971
|
+
"Foo::CONST_A",
|
|
972
|
+
"Module",
|
|
973
|
+
"Class",
|
|
974
|
+
"Object",
|
|
975
|
+
"BasicObject",
|
|
976
|
+
"Kernel",
|
|
977
|
+
"Foo",
|
|
978
|
+
"Bar",
|
|
979
|
+
"Bar#bar_m()",
|
|
980
|
+
"Bar#bar_m2()"
|
|
981
|
+
]
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
985
|
+
assert_declaration_completion_eq!(
|
|
986
|
+
context,
|
|
987
|
+
CompletionReceiver::Expression(name_id),
|
|
988
|
+
[
|
|
989
|
+
"Module",
|
|
990
|
+
"Class",
|
|
991
|
+
"Object",
|
|
992
|
+
"BasicObject",
|
|
993
|
+
"Kernel",
|
|
994
|
+
"Foo",
|
|
995
|
+
"Bar",
|
|
996
|
+
"Bar#bar_m()",
|
|
997
|
+
"Bar#bar_m2()"
|
|
998
|
+
]
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
#[test]
|
|
1003
|
+
fn completion_candidates_finds_unqualified_constant_reachable_from_namespace() {
|
|
1004
|
+
let mut context = GraphTest::new();
|
|
1005
|
+
|
|
1006
|
+
context.index_uri(
|
|
1007
|
+
"file:///foo.rb",
|
|
1008
|
+
"
|
|
1009
|
+
module Foo
|
|
1010
|
+
CONST = 1
|
|
1011
|
+
|
|
1012
|
+
class Bar
|
|
1013
|
+
def baz
|
|
1014
|
+
# Typing CONST here should find Foo::CONST
|
|
1015
|
+
end
|
|
1016
|
+
end
|
|
1017
|
+
end
|
|
1018
|
+
",
|
|
1019
|
+
);
|
|
1020
|
+
context.resolve();
|
|
1021
|
+
|
|
1022
|
+
let foo_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
1023
|
+
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, Some(foo_id)).id();
|
|
1024
|
+
// Foo::CONST is reachable from Foo::Bar through lexical scoping, so it must appear as a completion candidate
|
|
1025
|
+
// when the user types the unqualified name CONST
|
|
1026
|
+
assert_declaration_completion_eq!(
|
|
1027
|
+
context,
|
|
1028
|
+
CompletionReceiver::Expression(name_id),
|
|
1029
|
+
[
|
|
1030
|
+
"Foo::CONST",
|
|
1031
|
+
"Foo::Bar",
|
|
1032
|
+
"Class",
|
|
1033
|
+
"Object",
|
|
1034
|
+
"BasicObject",
|
|
1035
|
+
"Kernel",
|
|
1036
|
+
"Foo",
|
|
1037
|
+
"Module",
|
|
1038
|
+
"Foo::Bar#baz()"
|
|
1039
|
+
]
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
#[test]
|
|
1044
|
+
fn completion_candidates_includes_globals() {
|
|
1045
|
+
let mut context = GraphTest::new();
|
|
1046
|
+
|
|
1047
|
+
context.index_uri(
|
|
1048
|
+
"file:///foo.rb",
|
|
1049
|
+
"
|
|
1050
|
+
$var = 1
|
|
1051
|
+
module Foo
|
|
1052
|
+
$var2 = 2
|
|
1053
|
+
|
|
1054
|
+
class Bar < BasicObject
|
|
1055
|
+
def bar_m
|
|
1056
|
+
# Completion in this `self` context
|
|
1057
|
+
end
|
|
1058
|
+
end
|
|
1059
|
+
end
|
|
1060
|
+
",
|
|
1061
|
+
);
|
|
1062
|
+
context.resolve();
|
|
1063
|
+
|
|
1064
|
+
let name_id = Name::new(
|
|
1065
|
+
StringId::from("Bar"),
|
|
1066
|
+
ParentScope::None,
|
|
1067
|
+
Some(Name::new(StringId::from("Foo"), ParentScope::None, None).id()),
|
|
1068
|
+
)
|
|
1069
|
+
.id();
|
|
1070
|
+
assert_declaration_completion_eq!(
|
|
1071
|
+
context,
|
|
1072
|
+
CompletionReceiver::Expression(name_id),
|
|
1073
|
+
[
|
|
1074
|
+
"Foo::Bar",
|
|
1075
|
+
"$var2",
|
|
1076
|
+
"$var",
|
|
1077
|
+
"BasicObject",
|
|
1078
|
+
"Object",
|
|
1079
|
+
"Kernel",
|
|
1080
|
+
"Module",
|
|
1081
|
+
"Foo",
|
|
1082
|
+
"Class",
|
|
1083
|
+
"Foo::Bar#bar_m()"
|
|
1084
|
+
]
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
#[test]
|
|
1089
|
+
fn namespace_access_completion_collects_constants_and_singleton_methods() {
|
|
1090
|
+
let mut context = GraphTest::new();
|
|
1091
|
+
|
|
1092
|
+
context.index_uri(
|
|
1093
|
+
"file:///foo.rb",
|
|
1094
|
+
"
|
|
1095
|
+
module Foo
|
|
1096
|
+
CONST = 1
|
|
1097
|
+
class Bar; end
|
|
1098
|
+
|
|
1099
|
+
class << self
|
|
1100
|
+
def class_method; end
|
|
1101
|
+
end
|
|
1102
|
+
|
|
1103
|
+
def instance_method; end
|
|
1104
|
+
end
|
|
1105
|
+
",
|
|
1106
|
+
);
|
|
1107
|
+
context.resolve();
|
|
1108
|
+
|
|
1109
|
+
assert_completion_eq!(
|
|
1110
|
+
context,
|
|
1111
|
+
CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
|
|
1112
|
+
["Foo::CONST", "Foo::Bar", "Foo::<Foo>#class_method()"]
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
#[test]
|
|
1117
|
+
fn namespace_access_completion_includes_inherited_members() {
|
|
1118
|
+
let mut context = GraphTest::new();
|
|
1119
|
+
|
|
1120
|
+
context.index_uri(
|
|
1121
|
+
"file:///foo.rb",
|
|
1122
|
+
"
|
|
1123
|
+
class Parent
|
|
1124
|
+
PARENT_CONST = 1
|
|
1125
|
+
|
|
1126
|
+
class << self
|
|
1127
|
+
def parent_class_method; end
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
class Child < Parent
|
|
1132
|
+
CHILD_CONST = 2
|
|
1133
|
+
|
|
1134
|
+
class << self
|
|
1135
|
+
def child_class_method; end
|
|
1136
|
+
end
|
|
1137
|
+
end
|
|
1138
|
+
",
|
|
1139
|
+
);
|
|
1140
|
+
context.resolve();
|
|
1141
|
+
|
|
1142
|
+
assert_completion_eq!(
|
|
1143
|
+
context,
|
|
1144
|
+
CompletionReceiver::NamespaceAccess(DeclarationId::from("Child")),
|
|
1145
|
+
[
|
|
1146
|
+
"Child::CHILD_CONST",
|
|
1147
|
+
"Parent::PARENT_CONST",
|
|
1148
|
+
"Child::<Child>#child_class_method()",
|
|
1149
|
+
"Parent::<Parent>#parent_class_method()",
|
|
1150
|
+
]
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
#[test]
|
|
1155
|
+
fn namespace_access_completion_deduplicates_overridden_members() {
|
|
1156
|
+
let mut context = GraphTest::new();
|
|
1157
|
+
|
|
1158
|
+
context.index_uri(
|
|
1159
|
+
"file:///foo.rb",
|
|
1160
|
+
"
|
|
1161
|
+
class Parent
|
|
1162
|
+
CONST = 1
|
|
1163
|
+
|
|
1164
|
+
class << self
|
|
1165
|
+
def shared_method; end
|
|
1166
|
+
end
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1169
|
+
class Child < Parent
|
|
1170
|
+
CONST = 2
|
|
1171
|
+
|
|
1172
|
+
class << self
|
|
1173
|
+
def shared_method; end
|
|
1174
|
+
end
|
|
1175
|
+
end
|
|
1176
|
+
",
|
|
1177
|
+
);
|
|
1178
|
+
context.resolve();
|
|
1179
|
+
|
|
1180
|
+
assert_completion_eq!(
|
|
1181
|
+
context,
|
|
1182
|
+
CompletionReceiver::NamespaceAccess(DeclarationId::from("Child")),
|
|
1183
|
+
["Child::CONST", "Child::<Child>#shared_method()"]
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
#[test]
|
|
1188
|
+
fn namespace_access_completion_excludes_object_owned_constants() {
|
|
1189
|
+
let mut context = GraphTest::new();
|
|
1190
|
+
|
|
1191
|
+
context.index_uri(
|
|
1192
|
+
"file:///foo.rb",
|
|
1193
|
+
"
|
|
1194
|
+
class Foo
|
|
1195
|
+
CONST = 1
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
class Bar; end
|
|
1199
|
+
",
|
|
1200
|
+
);
|
|
1201
|
+
context.resolve();
|
|
1202
|
+
|
|
1203
|
+
assert_completion_eq!(
|
|
1204
|
+
context,
|
|
1205
|
+
CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
|
|
1206
|
+
["Foo::CONST"]
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
#[test]
|
|
1211
|
+
fn namespace_access_completion_includes_constant_aliases() {
|
|
1212
|
+
let mut context = GraphTest::new();
|
|
1213
|
+
|
|
1214
|
+
context.index_uri(
|
|
1215
|
+
"file:///foo.rb",
|
|
1216
|
+
"
|
|
1217
|
+
module Foo
|
|
1218
|
+
Bar = String
|
|
1219
|
+
CONST = 1
|
|
1220
|
+
end
|
|
1221
|
+
",
|
|
1222
|
+
);
|
|
1223
|
+
context.resolve();
|
|
1224
|
+
|
|
1225
|
+
assert_completion_eq!(
|
|
1226
|
+
context,
|
|
1227
|
+
CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
|
|
1228
|
+
["Foo::CONST", "Foo::Bar"]
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
#[test]
|
|
1233
|
+
fn namespace_access_completion_follows_constant_alias() {
|
|
1234
|
+
let mut context = GraphTest::new();
|
|
1235
|
+
|
|
1236
|
+
context.index_uri(
|
|
1237
|
+
"file:///foo.rb",
|
|
1238
|
+
"
|
|
1239
|
+
class Original
|
|
1240
|
+
CONST = 1
|
|
1241
|
+
class Nested; end
|
|
1242
|
+
|
|
1243
|
+
class << self
|
|
1244
|
+
def class_method; end
|
|
1245
|
+
end
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
module Foo
|
|
1249
|
+
MyOriginal = Original
|
|
1250
|
+
end
|
|
1251
|
+
",
|
|
1252
|
+
);
|
|
1253
|
+
context.resolve();
|
|
1254
|
+
|
|
1255
|
+
assert_completion_eq!(
|
|
1256
|
+
context,
|
|
1257
|
+
CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo::MyOriginal")),
|
|
1258
|
+
[
|
|
1259
|
+
"Original::CONST",
|
|
1260
|
+
"Original::Nested",
|
|
1261
|
+
"Original::<Original>#class_method()"
|
|
1262
|
+
]
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
#[test]
|
|
1267
|
+
fn namespace_access_completion_follows_chained_constant_alias() {
|
|
1268
|
+
let mut context = GraphTest::new();
|
|
1269
|
+
|
|
1270
|
+
context.index_uri(
|
|
1271
|
+
"file:///foo.rb",
|
|
1272
|
+
"
|
|
1273
|
+
class Original
|
|
1274
|
+
CONST = 1
|
|
1275
|
+
|
|
1276
|
+
class << self
|
|
1277
|
+
def class_method; end
|
|
1278
|
+
end
|
|
1279
|
+
end
|
|
1280
|
+
|
|
1281
|
+
Alias1 = Original
|
|
1282
|
+
Alias2 = Alias1
|
|
1283
|
+
",
|
|
1284
|
+
);
|
|
1285
|
+
context.resolve();
|
|
1286
|
+
|
|
1287
|
+
assert_completion_eq!(
|
|
1288
|
+
context,
|
|
1289
|
+
CompletionReceiver::NamespaceAccess(DeclarationId::from("Alias2")),
|
|
1290
|
+
["Original::CONST", "Original::<Original>#class_method()"]
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
#[test]
|
|
1295
|
+
fn namespace_access_completion_on_basic_object_subclass() {
|
|
1296
|
+
let mut context = GraphTest::new();
|
|
1297
|
+
|
|
1298
|
+
context.index_uri(
|
|
1299
|
+
"file:///foo.rb",
|
|
1300
|
+
"
|
|
1301
|
+
class Foo < BasicObject
|
|
1302
|
+
CONST = 1
|
|
1303
|
+
|
|
1304
|
+
class << self
|
|
1305
|
+
def class_method; end
|
|
1306
|
+
end
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
class Bar; end
|
|
1310
|
+
",
|
|
1311
|
+
);
|
|
1312
|
+
context.resolve();
|
|
1313
|
+
|
|
1314
|
+
assert_completion_eq!(
|
|
1315
|
+
context,
|
|
1316
|
+
CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
|
|
1317
|
+
["Foo::CONST", "Foo::<Foo>#class_method()"]
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
#[test]
|
|
1322
|
+
fn namespace_access_completion_includes_module_members() {
|
|
1323
|
+
let mut context = GraphTest::new();
|
|
1324
|
+
|
|
1325
|
+
context.index_uri(
|
|
1326
|
+
"file:///foo.rb",
|
|
1327
|
+
"
|
|
1328
|
+
module Bar
|
|
1329
|
+
CONST = 1
|
|
1330
|
+
|
|
1331
|
+
class << self
|
|
1332
|
+
def bar_class_method; end
|
|
1333
|
+
end
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
class Foo
|
|
1337
|
+
FOO_CONST = 2
|
|
1338
|
+
include Bar
|
|
1339
|
+
|
|
1340
|
+
class << self
|
|
1341
|
+
def foo_class_method; end
|
|
1342
|
+
end
|
|
1343
|
+
end
|
|
1344
|
+
",
|
|
1345
|
+
);
|
|
1346
|
+
context.resolve();
|
|
1347
|
+
|
|
1348
|
+
assert_completion_eq!(
|
|
1349
|
+
context,
|
|
1350
|
+
CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
|
|
1351
|
+
["Foo::FOO_CONST", "Bar::CONST", "Foo::<Foo>#foo_class_method()"]
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
#[test]
|
|
1356
|
+
fn method_call_completion_collects_instance_methods() {
|
|
1357
|
+
let mut context = GraphTest::new();
|
|
1358
|
+
|
|
1359
|
+
context.index_uri(
|
|
1360
|
+
"file:///foo.rb",
|
|
1361
|
+
"
|
|
1362
|
+
class Foo
|
|
1363
|
+
CONST = 1
|
|
1364
|
+
|
|
1365
|
+
def bar; end
|
|
1366
|
+
def baz; end
|
|
1367
|
+
|
|
1368
|
+
class << self
|
|
1369
|
+
def class_method; end
|
|
1370
|
+
end
|
|
1371
|
+
end
|
|
1372
|
+
",
|
|
1373
|
+
);
|
|
1374
|
+
context.resolve();
|
|
1375
|
+
|
|
1376
|
+
assert_completion_eq!(
|
|
1377
|
+
context,
|
|
1378
|
+
CompletionReceiver::MethodCall(DeclarationId::from("Foo")),
|
|
1379
|
+
["Foo#baz()", "Foo#bar()"]
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
#[test]
|
|
1384
|
+
fn method_call_completion_follows_constant_alias() {
|
|
1385
|
+
let mut context = GraphTest::new();
|
|
1386
|
+
|
|
1387
|
+
context.index_uri(
|
|
1388
|
+
"file:///foo.rb",
|
|
1389
|
+
"
|
|
1390
|
+
class Original
|
|
1391
|
+
def bar; end
|
|
1392
|
+
def baz; end
|
|
1393
|
+
|
|
1394
|
+
class << self
|
|
1395
|
+
def class_method; end
|
|
1396
|
+
end
|
|
1397
|
+
end
|
|
1398
|
+
|
|
1399
|
+
module Foo
|
|
1400
|
+
MyOriginal = Original
|
|
1401
|
+
end
|
|
1402
|
+
",
|
|
1403
|
+
);
|
|
1404
|
+
context.resolve();
|
|
1405
|
+
|
|
1406
|
+
assert_completion_eq!(
|
|
1407
|
+
context,
|
|
1408
|
+
CompletionReceiver::MethodCall(DeclarationId::from("Foo::MyOriginal")),
|
|
1409
|
+
["Original#baz()", "Original#bar()"]
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
#[test]
|
|
1414
|
+
fn method_call_completion_includes_inherited_methods() {
|
|
1415
|
+
let mut context = GraphTest::new();
|
|
1416
|
+
|
|
1417
|
+
context.index_uri(
|
|
1418
|
+
"file:///foo.rb",
|
|
1419
|
+
"
|
|
1420
|
+
class Parent
|
|
1421
|
+
def parent_method; end
|
|
1422
|
+
end
|
|
1423
|
+
|
|
1424
|
+
class Child < Parent
|
|
1425
|
+
def child_method; end
|
|
1426
|
+
end
|
|
1427
|
+
",
|
|
1428
|
+
);
|
|
1429
|
+
context.resolve();
|
|
1430
|
+
|
|
1431
|
+
assert_completion_eq!(
|
|
1432
|
+
context,
|
|
1433
|
+
CompletionReceiver::MethodCall(DeclarationId::from("Child")),
|
|
1434
|
+
["Child#child_method()", "Parent#parent_method()"]
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
#[test]
|
|
1439
|
+
fn method_call_completion_includes_methods_from_included_modules() {
|
|
1440
|
+
let mut context = GraphTest::new();
|
|
1441
|
+
|
|
1442
|
+
context.index_uri(
|
|
1443
|
+
"file:///foo.rb",
|
|
1444
|
+
"
|
|
1445
|
+
module Mixin
|
|
1446
|
+
def mixin_method; end
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1449
|
+
class Foo
|
|
1450
|
+
include Mixin
|
|
1451
|
+
|
|
1452
|
+
def foo_method; end
|
|
1453
|
+
end
|
|
1454
|
+
",
|
|
1455
|
+
);
|
|
1456
|
+
context.resolve();
|
|
1457
|
+
|
|
1458
|
+
assert_completion_eq!(
|
|
1459
|
+
context,
|
|
1460
|
+
CompletionReceiver::MethodCall(DeclarationId::from("Foo")),
|
|
1461
|
+
["Foo#foo_method()", "Mixin#mixin_method()"]
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
#[test]
|
|
1466
|
+
fn method_call_completion_deduplicates_overridden_methods() {
|
|
1467
|
+
let mut context = GraphTest::new();
|
|
1468
|
+
|
|
1469
|
+
context.index_uri(
|
|
1470
|
+
"file:///foo.rb",
|
|
1471
|
+
"
|
|
1472
|
+
class Parent
|
|
1473
|
+
def shared_method; end
|
|
1474
|
+
def parent_only; end
|
|
1475
|
+
end
|
|
1476
|
+
|
|
1477
|
+
class Child < Parent
|
|
1478
|
+
def shared_method; end
|
|
1479
|
+
def child_only; end
|
|
1480
|
+
end
|
|
1481
|
+
",
|
|
1482
|
+
);
|
|
1483
|
+
context.resolve();
|
|
1484
|
+
|
|
1485
|
+
assert_completion_eq!(
|
|
1486
|
+
context,
|
|
1487
|
+
CompletionReceiver::MethodCall(DeclarationId::from("Child")),
|
|
1488
|
+
["Child#shared_method()", "Child#child_only()", "Parent#parent_only()"]
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
#[test]
|
|
1493
|
+
fn method_call_completion_excludes_non_method_members() {
|
|
1494
|
+
let mut context = GraphTest::new();
|
|
1495
|
+
|
|
1496
|
+
context.index_uri(
|
|
1497
|
+
"file:///foo.rb",
|
|
1498
|
+
"
|
|
1499
|
+
class Foo
|
|
1500
|
+
CONST = 1
|
|
1501
|
+
@@class_var = 2
|
|
1502
|
+
|
|
1503
|
+
def initialize
|
|
1504
|
+
@ivar = 3
|
|
1505
|
+
end
|
|
1506
|
+
|
|
1507
|
+
def bar; end
|
|
1508
|
+
end
|
|
1509
|
+
",
|
|
1510
|
+
);
|
|
1511
|
+
context.resolve();
|
|
1512
|
+
|
|
1513
|
+
assert_completion_eq!(
|
|
1514
|
+
context,
|
|
1515
|
+
CompletionReceiver::MethodCall(DeclarationId::from("Foo")),
|
|
1516
|
+
["Foo#initialize()", "Foo#bar()"]
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
#[test]
|
|
1521
|
+
fn method_call_completion_at_singleton_level() {
|
|
1522
|
+
let mut context = GraphTest::new();
|
|
1523
|
+
|
|
1524
|
+
context.index_uri(
|
|
1525
|
+
"file:///foo.rb",
|
|
1526
|
+
"
|
|
1527
|
+
class Foo
|
|
1528
|
+
def self.bar; end
|
|
1529
|
+
|
|
1530
|
+
class << self
|
|
1531
|
+
def baz; end
|
|
1532
|
+
end
|
|
1533
|
+
end
|
|
1534
|
+
",
|
|
1535
|
+
);
|
|
1536
|
+
context.resolve();
|
|
1537
|
+
|
|
1538
|
+
assert_completion_eq!(
|
|
1539
|
+
context,
|
|
1540
|
+
CompletionReceiver::MethodCall(DeclarationId::from("Foo::<Foo>")),
|
|
1541
|
+
["Foo::<Foo>#baz()", "Foo::<Foo>#bar()"]
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
#[test]
|
|
1546
|
+
fn method_argument_completion_includes_keyword_params() {
|
|
1547
|
+
let mut context = GraphTest::new();
|
|
1548
|
+
|
|
1549
|
+
context.index_uri(
|
|
1550
|
+
"file:///foo.rb",
|
|
1551
|
+
"
|
|
1552
|
+
class Foo
|
|
1553
|
+
def greet(name:, greeting: 'hello'); end
|
|
1554
|
+
end
|
|
1555
|
+
",
|
|
1556
|
+
);
|
|
1557
|
+
context.resolve();
|
|
1558
|
+
|
|
1559
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
1560
|
+
assert_declaration_completion_eq!(
|
|
1561
|
+
context,
|
|
1562
|
+
CompletionReceiver::MethodArgument {
|
|
1563
|
+
self_name_id: name_id,
|
|
1564
|
+
method_decl_id: DeclarationId::from("Foo#greet()"),
|
|
1565
|
+
},
|
|
1566
|
+
[
|
|
1567
|
+
"Class",
|
|
1568
|
+
"Object",
|
|
1569
|
+
"BasicObject",
|
|
1570
|
+
"Kernel",
|
|
1571
|
+
"Foo",
|
|
1572
|
+
"Module",
|
|
1573
|
+
"Foo#greet()",
|
|
1574
|
+
"name:",
|
|
1575
|
+
"greeting:"
|
|
1576
|
+
]
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
#[test]
|
|
1581
|
+
fn method_argument_completion_no_keyword_params() {
|
|
1582
|
+
let mut context = GraphTest::new();
|
|
1583
|
+
|
|
1584
|
+
context.index_uri(
|
|
1585
|
+
"file:///foo.rb",
|
|
1586
|
+
"
|
|
1587
|
+
class Foo
|
|
1588
|
+
def bar(x, y); end
|
|
1589
|
+
end
|
|
1590
|
+
",
|
|
1591
|
+
);
|
|
1592
|
+
context.resolve();
|
|
1593
|
+
|
|
1594
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
1595
|
+
assert_declaration_completion_eq!(
|
|
1596
|
+
context,
|
|
1597
|
+
CompletionReceiver::MethodArgument {
|
|
1598
|
+
self_name_id: name_id,
|
|
1599
|
+
method_decl_id: DeclarationId::from("Foo#bar()"),
|
|
1600
|
+
},
|
|
1601
|
+
["Class", "Object", "BasicObject", "Kernel", "Foo", "Module", "Foo#bar()"]
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
#[test]
|
|
1606
|
+
fn method_argument_completion_mixed_params() {
|
|
1607
|
+
let mut context = GraphTest::new();
|
|
1608
|
+
|
|
1609
|
+
context.index_uri(
|
|
1610
|
+
"file:///foo.rb",
|
|
1611
|
+
"
|
|
1612
|
+
class Foo
|
|
1613
|
+
def search(query, limit:, offset: 0, **opts); end
|
|
1614
|
+
end
|
|
1615
|
+
",
|
|
1616
|
+
);
|
|
1617
|
+
context.resolve();
|
|
1618
|
+
|
|
1619
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
1620
|
+
assert_declaration_completion_eq!(
|
|
1621
|
+
context,
|
|
1622
|
+
CompletionReceiver::MethodArgument {
|
|
1623
|
+
self_name_id: name_id,
|
|
1624
|
+
method_decl_id: DeclarationId::from("Foo#search()"),
|
|
1625
|
+
},
|
|
1626
|
+
// Only RequiredKeyword and OptionalKeyword, not RestKeyword (**opts)
|
|
1627
|
+
[
|
|
1628
|
+
"Class",
|
|
1629
|
+
"Object",
|
|
1630
|
+
"BasicObject",
|
|
1631
|
+
"Kernel",
|
|
1632
|
+
"Foo",
|
|
1633
|
+
"Module",
|
|
1634
|
+
"Foo#search()",
|
|
1635
|
+
"limit:",
|
|
1636
|
+
"offset:"
|
|
1637
|
+
]
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
#[test]
|
|
1642
|
+
fn first_entry_is_always_used_overridden_methods() {
|
|
1643
|
+
let mut context = GraphTest::new();
|
|
1644
|
+
context.index_uri(
|
|
1645
|
+
"file:///foo.rb",
|
|
1646
|
+
"
|
|
1647
|
+
class Foo
|
|
1648
|
+
def bar(first:, second:); end
|
|
1649
|
+
end
|
|
1650
|
+
",
|
|
1651
|
+
);
|
|
1652
|
+
context.index_uri(
|
|
1653
|
+
"file:///foo2.rb",
|
|
1654
|
+
"
|
|
1655
|
+
class Foo
|
|
1656
|
+
def bar(first:); end
|
|
1657
|
+
end
|
|
1658
|
+
",
|
|
1659
|
+
);
|
|
1660
|
+
context.resolve();
|
|
1661
|
+
|
|
1662
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
1663
|
+
assert_declaration_completion_eq!(
|
|
1664
|
+
context,
|
|
1665
|
+
CompletionReceiver::MethodArgument {
|
|
1666
|
+
self_name_id: name_id,
|
|
1667
|
+
method_decl_id: DeclarationId::from("Foo#bar()"),
|
|
1668
|
+
},
|
|
1669
|
+
[
|
|
1670
|
+
"Class",
|
|
1671
|
+
"Object",
|
|
1672
|
+
"BasicObject",
|
|
1673
|
+
"Kernel",
|
|
1674
|
+
"Foo",
|
|
1675
|
+
"Module",
|
|
1676
|
+
"Foo#bar()",
|
|
1677
|
+
"first:",
|
|
1678
|
+
"second:"
|
|
1679
|
+
]
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
#[test]
|
|
1684
|
+
fn expression_completion_includes_keywords() {
|
|
1685
|
+
let mut context = GraphTest::new();
|
|
1686
|
+
context.index_uri("file:///foo.rb", "class Foo; end");
|
|
1687
|
+
context.resolve();
|
|
1688
|
+
|
|
1689
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
1690
|
+
assert_completion_eq!(
|
|
1691
|
+
context,
|
|
1692
|
+
CompletionReceiver::Expression(name_id),
|
|
1693
|
+
[
|
|
1694
|
+
"Class",
|
|
1695
|
+
"Object",
|
|
1696
|
+
"BasicObject",
|
|
1697
|
+
"Kernel",
|
|
1698
|
+
"Foo",
|
|
1699
|
+
"Module",
|
|
1700
|
+
"BEGIN",
|
|
1701
|
+
"END",
|
|
1702
|
+
"__ENCODING__",
|
|
1703
|
+
"__FILE__",
|
|
1704
|
+
"__LINE__",
|
|
1705
|
+
"alias",
|
|
1706
|
+
"and",
|
|
1707
|
+
"begin",
|
|
1708
|
+
"break",
|
|
1709
|
+
"case",
|
|
1710
|
+
"class",
|
|
1711
|
+
"def",
|
|
1712
|
+
"defined?",
|
|
1713
|
+
"do",
|
|
1714
|
+
"else",
|
|
1715
|
+
"elsif",
|
|
1716
|
+
"end",
|
|
1717
|
+
"ensure",
|
|
1718
|
+
"false",
|
|
1719
|
+
"for",
|
|
1720
|
+
"if",
|
|
1721
|
+
"in",
|
|
1722
|
+
"module",
|
|
1723
|
+
"next",
|
|
1724
|
+
"nil",
|
|
1725
|
+
"not",
|
|
1726
|
+
"or",
|
|
1727
|
+
"redo",
|
|
1728
|
+
"rescue",
|
|
1729
|
+
"retry",
|
|
1730
|
+
"return",
|
|
1731
|
+
"self",
|
|
1732
|
+
"super",
|
|
1733
|
+
"then",
|
|
1734
|
+
"true",
|
|
1735
|
+
"undef",
|
|
1736
|
+
"unless",
|
|
1737
|
+
"until",
|
|
1738
|
+
"when",
|
|
1739
|
+
"while",
|
|
1740
|
+
"yield",
|
|
1741
|
+
]
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
#[test]
|
|
1746
|
+
fn method_argument_completion_includes_keywords() {
|
|
1747
|
+
let mut context = GraphTest::new();
|
|
1748
|
+
context.index_uri("file:///foo.rb", "class Foo; def bar(name:); end; end");
|
|
1749
|
+
context.resolve();
|
|
1750
|
+
|
|
1751
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
1752
|
+
assert_completion_eq!(
|
|
1753
|
+
context,
|
|
1754
|
+
CompletionReceiver::MethodArgument {
|
|
1755
|
+
self_name_id: name_id,
|
|
1756
|
+
method_decl_id: DeclarationId::from("Foo#bar()"),
|
|
1757
|
+
},
|
|
1758
|
+
[
|
|
1759
|
+
"Class",
|
|
1760
|
+
"Object",
|
|
1761
|
+
"BasicObject",
|
|
1762
|
+
"Kernel",
|
|
1763
|
+
"Foo",
|
|
1764
|
+
"Module",
|
|
1765
|
+
"Foo#bar()",
|
|
1766
|
+
"BEGIN",
|
|
1767
|
+
"END",
|
|
1768
|
+
"__ENCODING__",
|
|
1769
|
+
"__FILE__",
|
|
1770
|
+
"__LINE__",
|
|
1771
|
+
"alias",
|
|
1772
|
+
"and",
|
|
1773
|
+
"begin",
|
|
1774
|
+
"break",
|
|
1775
|
+
"case",
|
|
1776
|
+
"class",
|
|
1777
|
+
"def",
|
|
1778
|
+
"defined?",
|
|
1779
|
+
"do",
|
|
1780
|
+
"else",
|
|
1781
|
+
"elsif",
|
|
1782
|
+
"end",
|
|
1783
|
+
"ensure",
|
|
1784
|
+
"false",
|
|
1785
|
+
"for",
|
|
1786
|
+
"if",
|
|
1787
|
+
"in",
|
|
1788
|
+
"module",
|
|
1789
|
+
"next",
|
|
1790
|
+
"nil",
|
|
1791
|
+
"not",
|
|
1792
|
+
"or",
|
|
1793
|
+
"redo",
|
|
1794
|
+
"rescue",
|
|
1795
|
+
"retry",
|
|
1796
|
+
"return",
|
|
1797
|
+
"self",
|
|
1798
|
+
"super",
|
|
1799
|
+
"then",
|
|
1800
|
+
"true",
|
|
1801
|
+
"undef",
|
|
1802
|
+
"unless",
|
|
1803
|
+
"until",
|
|
1804
|
+
"when",
|
|
1805
|
+
"while",
|
|
1806
|
+
"yield",
|
|
1807
|
+
"name:",
|
|
1808
|
+
]
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
#[test]
|
|
1813
|
+
fn namespace_access_completion_excludes_keywords() {
|
|
1814
|
+
let mut context = GraphTest::new();
|
|
1815
|
+
context.index_uri("file:///foo.rb", "class Foo; CONST = 1; end");
|
|
1816
|
+
context.resolve();
|
|
1817
|
+
|
|
1818
|
+
let candidates = completion_candidates(
|
|
1819
|
+
context.graph(),
|
|
1820
|
+
CompletionContext::new(CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo"))),
|
|
1821
|
+
)
|
|
1822
|
+
.unwrap();
|
|
1823
|
+
|
|
1824
|
+
assert!(!candidates.iter().any(|c| matches!(c, CompletionCandidate::Keyword(_))));
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
#[test]
|
|
1828
|
+
fn method_call_completion_excludes_keywords() {
|
|
1829
|
+
let mut context = GraphTest::new();
|
|
1830
|
+
context.index_uri("file:///foo.rb", "class Foo; def bar; end; end");
|
|
1831
|
+
context.resolve();
|
|
1832
|
+
|
|
1833
|
+
let candidates = completion_candidates(
|
|
1834
|
+
context.graph(),
|
|
1835
|
+
CompletionContext::new(CompletionReceiver::MethodCall(DeclarationId::from("Foo"))),
|
|
1836
|
+
)
|
|
1837
|
+
.unwrap();
|
|
1838
|
+
|
|
1839
|
+
assert!(!candidates.iter().any(|c| matches!(c, CompletionCandidate::Keyword(_))));
|
|
1840
|
+
}
|
|
1841
|
+
}
|