rubydex 0.2.0 → 0.2.2
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/ext/rubydex/declaration.c +38 -0
- data/ext/rubydex/definition.c +27 -0
- data/ext/rubydex/graph.c +70 -17
- data/ext/rubydex/rubydex.c +2 -0
- data/ext/rubydex/signature.c +83 -0
- data/ext/rubydex/signature.h +23 -0
- data/lib/rubydex/declaration.rb +31 -0
- data/lib/rubydex/signature.rb +130 -0
- data/lib/rubydex/version.rb +1 -1
- data/lib/rubydex.rb +1 -0
- data/rbi/rubydex.rbi +41 -11
- data/rust/rubydex/src/diagnostic.rs +1 -0
- data/rust/rubydex/src/indexing/ruby_indexer.rs +75 -15
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +2832 -2663
- data/rust/rubydex/src/model/declaration.rs +48 -0
- data/rust/rubydex/src/model/definitions.rs +41 -9
- data/rust/rubydex/src/model/graph.rs +276 -46
- data/rust/rubydex/src/query.rs +2073 -160
- data/rust/rubydex/src/resolution.rs +202 -71
- data/rust/rubydex/src/resolution_tests.rs +333 -1
- data/rust/rubydex-sys/src/declaration_api.rs +29 -33
- data/rust/rubydex-sys/src/graph_api.rs +89 -5
- data/rust/rubydex-sys/src/lib.rs +1 -0
- data/rust/rubydex-sys/src/signature_api.rs +209 -0
- metadata +6 -2
data/rust/rubydex/src/query.rs
CHANGED
|
@@ -6,13 +6,14 @@ use std::thread;
|
|
|
6
6
|
use url::Url;
|
|
7
7
|
|
|
8
8
|
use crate::model::built_in::OBJECT_ID;
|
|
9
|
-
use crate::model::declaration::{Ancestor, Declaration};
|
|
9
|
+
use crate::model::declaration::{Ancestor, Declaration, Namespace};
|
|
10
10
|
use crate::model::definitions::{Definition, Parameter};
|
|
11
11
|
use crate::model::graph::Graph;
|
|
12
12
|
use crate::model::identity_maps::IdentityHashSet;
|
|
13
|
-
use crate::model::ids::{DeclarationId, NameId, StringId, UriId};
|
|
13
|
+
use crate::model::ids::{DeclarationId, DefinitionId, NameId, StringId, UriId};
|
|
14
14
|
use crate::model::keywords::{self, Keyword};
|
|
15
15
|
use crate::model::name::NameRef;
|
|
16
|
+
use crate::model::visibility::Visibility;
|
|
16
17
|
|
|
17
18
|
/// Controls how declaration names are matched against the search query.
|
|
18
19
|
#[derive(Default)]
|
|
@@ -169,18 +170,37 @@ pub enum CompletionCandidate {
|
|
|
169
170
|
pub enum CompletionReceiver {
|
|
170
171
|
/// Completion requested for an expression with no previous token (e.g.: at the start of a line with nothing before)
|
|
171
172
|
/// Includes: all keywords, all global variables and reacheable instance variables, class variables, constants and methods
|
|
172
|
-
|
|
173
|
+
///
|
|
174
|
+
/// `nesting_name_id` represents the lexical scope. It is walked for constants and drives class-variable
|
|
175
|
+
/// lookup (cvars follow lexical scope in Ruby, not self).
|
|
176
|
+
/// `self_decl_id` overrides the self-type used for methods and instance variables when the runtime `self`
|
|
177
|
+
/// diverges from the innermost lexical scope — for example `def Foo.bar` (where self is `Foo` but the
|
|
178
|
+
/// lexical scope is the outer namespace) or `def self.bar`. Callers may pass a `ConstantAlias` id; it is
|
|
179
|
+
/// unwrapped to the target namespace. When `None`, self is derived from the innermost lexical scope.
|
|
180
|
+
Expression {
|
|
181
|
+
self_decl_id: Option<DeclarationId>,
|
|
182
|
+
nesting_name_id: NameId,
|
|
183
|
+
},
|
|
173
184
|
/// 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
|
|
185
|
+
/// Includes: all constants and singleton methods for the namespace and its ancestors.
|
|
186
|
+
NamespaceAccess {
|
|
187
|
+
self_decl_id: Option<DeclarationId>,
|
|
188
|
+
namespace_decl_id: DeclarationId,
|
|
189
|
+
},
|
|
176
190
|
/// Completion requested after a method call operator (e.g.: `foo.`, `@bar.`, `@@baz.`, `Qux.`).
|
|
177
191
|
/// 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
|
|
192
|
+
/// Includes: all methods that exist on the type of the receiver and its ancestors.
|
|
193
|
+
MethodCall {
|
|
194
|
+
self_decl_id: Option<DeclarationId>,
|
|
195
|
+
receiver_decl_id: DeclarationId,
|
|
196
|
+
},
|
|
180
197
|
/// Completion requested inside a method call's argument list (e.g.: `foo.bar(|)`)
|
|
181
198
|
/// Includes: everything expressions do plus keyword parameter names of the method being called
|
|
199
|
+
///
|
|
200
|
+
/// Same `self_decl_id` / `nesting_name_id` split as `Expression`.
|
|
182
201
|
MethodArgument {
|
|
183
|
-
|
|
202
|
+
self_decl_id: Option<DeclarationId>,
|
|
203
|
+
nesting_name_id: NameId,
|
|
184
204
|
method_decl_id: DeclarationId,
|
|
185
205
|
},
|
|
186
206
|
}
|
|
@@ -204,26 +224,58 @@ impl<'a> CompletionContext<'a> {
|
|
|
204
224
|
}
|
|
205
225
|
}
|
|
206
226
|
|
|
207
|
-
///
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
227
|
+
/// Method visibility depends both on the type of self and the current lexical scope (due to protected methods)
|
|
228
|
+
fn method_visible_at_call(
|
|
229
|
+
graph: &Graph,
|
|
230
|
+
method_id: DeclarationId,
|
|
231
|
+
defined_in: DeclarationId,
|
|
232
|
+
caller_self: Option<DeclarationId>,
|
|
233
|
+
receiver: DeclarationId,
|
|
234
|
+
) -> bool {
|
|
235
|
+
let Some(visibility) = graph.visibility(&method_id) else {
|
|
236
|
+
return true;
|
|
216
237
|
};
|
|
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
238
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
239
|
+
match visibility {
|
|
240
|
+
Visibility::Public => true,
|
|
241
|
+
Visibility::Private | Visibility::ModuleFunction => caller_self == Some(receiver),
|
|
242
|
+
Visibility::Protected => caller_self.is_some_and(|cs| {
|
|
243
|
+
let defined_in = graph.declarations().get(&defined_in).unwrap().as_namespace().unwrap();
|
|
244
|
+
let descendants = defined_in.descendants();
|
|
245
|
+
descendants.contains(&cs) && descendants.contains(&receiver)
|
|
246
|
+
}),
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/// Walks one namespace's direct members. `kind_filter` selects the declaration kinds to surface; `visibility_filter`
|
|
251
|
+
/// decides whether each surviving candidate is reachable from the access site.
|
|
252
|
+
fn collect_members<'a>(
|
|
253
|
+
graph: &'a Graph,
|
|
254
|
+
namespace_id: DeclarationId,
|
|
255
|
+
kind_filter: fn(&Declaration) -> bool,
|
|
256
|
+
visibility_filter: impl Fn(DeclarationId) -> bool,
|
|
257
|
+
completion_ctx: &mut CompletionContext<'a>,
|
|
258
|
+
candidates: &mut Vec<CompletionCandidate>,
|
|
259
|
+
) {
|
|
260
|
+
let namespace = graph.declarations().get(&namespace_id).unwrap().as_namespace().unwrap();
|
|
261
|
+
|
|
262
|
+
for (member_str_id, member_decl_id) in namespace.members() {
|
|
263
|
+
let member = graph.declarations().get(member_decl_id).unwrap();
|
|
264
|
+
|
|
265
|
+
if !kind_filter(member) {
|
|
266
|
+
continue;
|
|
225
267
|
}
|
|
226
|
-
|
|
268
|
+
|
|
269
|
+
if !completion_ctx.dedup(member_str_id) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if !visibility_filter(*member_decl_id) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
candidates.push(CompletionCandidate::Declaration(*member_decl_id));
|
|
278
|
+
}
|
|
227
279
|
}
|
|
228
280
|
|
|
229
281
|
/// Determines all possible completion candidates based on the current context of the cursor. There are multiple cases
|
|
@@ -233,7 +285,6 @@ macro_rules! collect_candidates {
|
|
|
233
285
|
/// global variables that are reacheable from the current lexical scope and self type
|
|
234
286
|
/// - Expression in method arguments collects everything that expressions do and all keyword parameter names that are
|
|
235
287
|
/// applicable to the method being called
|
|
236
|
-
/// everything else
|
|
237
288
|
/// - Namespace access (e.g.: `Foo::`) collects all constants and singleton methods for the namespace that `Foo`
|
|
238
289
|
/// resolves to
|
|
239
290
|
/// - Method calls on anything (e.g.: `foo.`, `@bar.`, `@@baz.`, `Qux.`) collects all methods that exist on the type
|
|
@@ -245,19 +296,30 @@ macro_rules! collect_candidates {
|
|
|
245
296
|
///
|
|
246
297
|
/// # Errors
|
|
247
298
|
///
|
|
248
|
-
/// Will error if the given `
|
|
299
|
+
/// Will error if the given `self_decl_id` does not resolve to a namespace declaration (directly or via
|
|
300
|
+
/// a constant alias).
|
|
249
301
|
pub fn completion_candidates<'a>(
|
|
250
302
|
graph: &'a Graph,
|
|
251
303
|
context: CompletionContext<'a>,
|
|
252
304
|
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
253
305
|
match context.completion_receiver {
|
|
254
|
-
CompletionReceiver::Expression
|
|
255
|
-
|
|
256
|
-
|
|
306
|
+
CompletionReceiver::Expression {
|
|
307
|
+
self_decl_id,
|
|
308
|
+
nesting_name_id,
|
|
309
|
+
} => expression_completion(graph, self_decl_id, nesting_name_id, context),
|
|
310
|
+
CompletionReceiver::NamespaceAccess {
|
|
311
|
+
self_decl_id,
|
|
312
|
+
namespace_decl_id,
|
|
313
|
+
} => namespace_access_completion(graph, self_decl_id, namespace_decl_id, context),
|
|
314
|
+
CompletionReceiver::MethodCall {
|
|
315
|
+
self_decl_id,
|
|
316
|
+
receiver_decl_id,
|
|
317
|
+
} => method_call_completion(graph, self_decl_id, receiver_decl_id, context),
|
|
257
318
|
CompletionReceiver::MethodArgument {
|
|
258
|
-
|
|
319
|
+
self_decl_id,
|
|
320
|
+
nesting_name_id,
|
|
259
321
|
method_decl_id,
|
|
260
|
-
} => method_argument_completion(graph,
|
|
322
|
+
} => method_argument_completion(graph, self_decl_id, nesting_name_id, method_decl_id, context),
|
|
261
323
|
}
|
|
262
324
|
}
|
|
263
325
|
|
|
@@ -286,12 +348,14 @@ fn resolve_to_namespace(graph: &Graph, decl_id: DeclarationId) -> Result<Option<
|
|
|
286
348
|
/// Collect completion for a namespace access (e.g.: `Foo::`)
|
|
287
349
|
fn namespace_access_completion<'a>(
|
|
288
350
|
graph: &'a Graph,
|
|
351
|
+
self_decl_id: Option<DeclarationId>,
|
|
289
352
|
namespace_decl_id: DeclarationId,
|
|
290
353
|
mut context: CompletionContext<'a>,
|
|
291
354
|
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
292
355
|
let Some(resolved_id) = resolve_to_namespace(graph, namespace_decl_id)? else {
|
|
293
356
|
return Ok(Vec::new());
|
|
294
357
|
};
|
|
358
|
+
let resolved_caller_self_id = self_decl_id.map(|id| resolve_self_namespace(graph, id)).transpose()?;
|
|
295
359
|
let namespace = graph.declarations().get(&resolved_id).unwrap().as_namespace().unwrap();
|
|
296
360
|
let mut candidates = Vec::new();
|
|
297
361
|
|
|
@@ -305,26 +369,35 @@ fn namespace_access_completion<'a>(
|
|
|
305
369
|
break;
|
|
306
370
|
}
|
|
307
371
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
collect_candidates!(
|
|
372
|
+
collect_members(
|
|
311
373
|
graph,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
374
|
+
*ancestor_id,
|
|
375
|
+
|d| d.as_namespace().is_some() || d.as_constant().is_some() || d.as_constant_alias().is_some(),
|
|
376
|
+
|id| !matches!(graph.visibility(&id), Some(Visibility::Private)),
|
|
377
|
+
&mut context,
|
|
378
|
+
&mut candidates,
|
|
316
379
|
);
|
|
317
380
|
}
|
|
318
381
|
}
|
|
319
382
|
|
|
320
|
-
//
|
|
383
|
+
// The receiver of an explicit `Foo::` call is the singleton class, so visibility checks
|
|
384
|
+
// compare against it (not against `Foo` itself).
|
|
321
385
|
if let Some(singleton_id) = namespace.singleton_class() {
|
|
322
386
|
let singleton = graph.declarations().get(singleton_id).unwrap().as_namespace().unwrap();
|
|
387
|
+
let receiver = *singleton_id;
|
|
323
388
|
|
|
324
389
|
for ancestor in singleton.ancestors() {
|
|
325
390
|
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
326
|
-
let
|
|
327
|
-
|
|
391
|
+
let defined_in = *ancestor_id;
|
|
392
|
+
|
|
393
|
+
collect_members(
|
|
394
|
+
graph,
|
|
395
|
+
defined_in,
|
|
396
|
+
|d| d.as_method().is_some(),
|
|
397
|
+
|id| method_visible_at_call(graph, id, defined_in, resolved_caller_self_id, receiver),
|
|
398
|
+
&mut context,
|
|
399
|
+
&mut candidates,
|
|
400
|
+
);
|
|
328
401
|
}
|
|
329
402
|
}
|
|
330
403
|
}
|
|
@@ -335,117 +408,248 @@ fn namespace_access_completion<'a>(
|
|
|
335
408
|
/// Collect completion for a method call (e.g.: `foo.`, `@bar.`, `Baz.`)
|
|
336
409
|
fn method_call_completion<'a>(
|
|
337
410
|
graph: &'a Graph,
|
|
411
|
+
self_decl_id: Option<DeclarationId>,
|
|
338
412
|
receiver_decl_id: DeclarationId,
|
|
339
413
|
mut context: CompletionContext<'a>,
|
|
340
414
|
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
341
415
|
let Some(resolved_id) = resolve_to_namespace(graph, receiver_decl_id)? else {
|
|
342
416
|
return Ok(Vec::new());
|
|
343
417
|
};
|
|
418
|
+
let resolved_caller_self_id = self_decl_id.map(|id| resolve_self_namespace(graph, id)).transpose()?;
|
|
344
419
|
let namespace = graph.declarations().get(&resolved_id).unwrap().as_namespace().unwrap();
|
|
345
420
|
let mut candidates = Vec::new();
|
|
346
421
|
|
|
347
422
|
for ancestor in namespace.ancestors() {
|
|
348
423
|
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
349
|
-
let
|
|
350
|
-
|
|
424
|
+
let defined_in = *ancestor_id;
|
|
425
|
+
collect_members(
|
|
426
|
+
graph,
|
|
427
|
+
defined_in,
|
|
428
|
+
|d| d.as_method().is_some(),
|
|
429
|
+
|id| method_visible_at_call(graph, id, defined_in, resolved_caller_self_id, resolved_id),
|
|
430
|
+
&mut context,
|
|
431
|
+
&mut candidates,
|
|
432
|
+
);
|
|
351
433
|
}
|
|
352
434
|
}
|
|
353
435
|
|
|
354
436
|
Ok(candidates)
|
|
355
437
|
}
|
|
356
438
|
|
|
439
|
+
fn resolve_self_namespace(graph: &Graph, decl_id: DeclarationId) -> Result<DeclarationId, Box<dyn Error>> {
|
|
440
|
+
resolve_to_namespace(graph, decl_id)?
|
|
441
|
+
.ok_or_else(|| format!("self declaration {decl_id:?} not found in graph").into())
|
|
442
|
+
}
|
|
443
|
+
|
|
357
444
|
/// Collect completion for an expression
|
|
358
445
|
fn expression_completion<'a>(
|
|
359
446
|
graph: &'a Graph,
|
|
360
|
-
|
|
447
|
+
self_decl_id: Option<DeclarationId>,
|
|
448
|
+
nesting_name_id: NameId,
|
|
361
449
|
mut context: CompletionContext<'a>,
|
|
362
450
|
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
363
|
-
let Some(name_ref) = graph.names().get(&
|
|
364
|
-
return Err(format!("Name {
|
|
451
|
+
let Some(name_ref) = graph.names().get(&nesting_name_id) else {
|
|
452
|
+
return Err(format!("Name {nesting_name_id} not found in graph").into());
|
|
365
453
|
};
|
|
366
454
|
let NameRef::Resolved(name_ref) = name_ref else {
|
|
367
|
-
return Err(format!("Expected name {
|
|
455
|
+
return Err(format!("Expected name {nesting_name_id} to be resolved").into());
|
|
456
|
+
};
|
|
457
|
+
// When no explicit self is given, self is the innermost lexical scope (the nesting's own declaration).
|
|
458
|
+
// When explicit, follow constant aliases so callers can pass whatever the expression that set self
|
|
459
|
+
// resolves to without having to unwrap aliases themselves. Missing or non-namespace decls are graph
|
|
460
|
+
// inconsistencies and surfaced as errors.
|
|
461
|
+
let resolved_self_decl_id = match self_decl_id {
|
|
462
|
+
Some(id) => resolve_self_namespace(graph, id)?,
|
|
463
|
+
None => *name_ref.declaration_id(),
|
|
368
464
|
};
|
|
369
|
-
let
|
|
465
|
+
let self_decl = graph
|
|
466
|
+
.declarations()
|
|
467
|
+
.get(&resolved_self_decl_id)
|
|
468
|
+
.unwrap()
|
|
469
|
+
.as_namespace()
|
|
470
|
+
.ok_or("Expected associated declaration to be a namespace")?;
|
|
471
|
+
let innermost_lexical_decl = graph
|
|
370
472
|
.declarations()
|
|
371
473
|
.get(name_ref.declaration_id())
|
|
372
|
-
.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
474
|
+
.unwrap()
|
|
475
|
+
.as_namespace()
|
|
476
|
+
.unwrap();
|
|
477
|
+
|
|
376
478
|
let mut candidates = Vec::new();
|
|
377
479
|
|
|
378
|
-
//
|
|
379
|
-
|
|
480
|
+
// Collect constants. Immediate scope includes inheritance. Outer scopes only include `Object` inheritance when it's a module
|
|
481
|
+
collect_constants_from_lexical_scope(graph, innermost_lexical_decl, &mut context, &mut candidates);
|
|
482
|
+
collect_constants_from_outer_nesting(graph, name_ref, &mut context, &mut candidates);
|
|
380
483
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
484
|
+
// Collect class variables, which are based on the inheritance chain of the attached object of the immediate lexical scope
|
|
485
|
+
collect_class_variables_from_lexical_scope(graph, name_ref, &mut context, &mut candidates);
|
|
486
|
+
|
|
487
|
+
// Globals are accessible from anywhere, regardless of lexical scope or `self` type.
|
|
488
|
+
collect_members(
|
|
489
|
+
graph,
|
|
490
|
+
*OBJECT_ID,
|
|
491
|
+
|d| d.as_global_variable().is_some(),
|
|
492
|
+
|_| true,
|
|
493
|
+
&mut context,
|
|
494
|
+
&mut candidates,
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
// Collect methods and instance variables, which are based on the inheritance chain of the `self` type (which may
|
|
498
|
+
// not match the immediate lexical scope)
|
|
499
|
+
collect_methods_and_ivars_from_self(graph, self_decl, &mut context, &mut candidates);
|
|
500
|
+
|
|
501
|
+
// Keywords are always available in expression contexts
|
|
502
|
+
candidates.extend(keywords::KEYWORDS.iter().map(CompletionCandidate::Keyword));
|
|
503
|
+
Ok(candidates)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/// Collects constants reachable from the innermost lexical scope's ancestor chain. Module bodies also fall back to
|
|
507
|
+
/// `Object`'s ancestor chain to mirror Ruby's resolution rules.
|
|
508
|
+
fn collect_constants_from_lexical_scope<'a>(
|
|
509
|
+
graph: &'a Graph,
|
|
510
|
+
innermost_lexical_decl: &'a Namespace,
|
|
511
|
+
context: &mut CompletionContext<'a>,
|
|
512
|
+
candidates: &mut Vec<CompletionCandidate>,
|
|
513
|
+
) {
|
|
514
|
+
for ancestor in innermost_lexical_decl.ancestors() {
|
|
515
|
+
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
516
|
+
collect_members(
|
|
517
|
+
graph,
|
|
518
|
+
*ancestor_id,
|
|
519
|
+
|d| d.as_namespace().is_some() || d.as_constant().is_some() || d.as_constant_alias().is_some(),
|
|
520
|
+
|_| true,
|
|
521
|
+
context,
|
|
522
|
+
candidates,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if matches!(innermost_lexical_decl, Namespace::Module(_)) {
|
|
528
|
+
let object = graph.declarations().get(&OBJECT_ID).unwrap().as_namespace().unwrap();
|
|
529
|
+
|
|
530
|
+
for ancestor in object.ancestors() {
|
|
531
|
+
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
532
|
+
collect_members(
|
|
533
|
+
graph,
|
|
534
|
+
*ancestor_id,
|
|
535
|
+
|d| d.as_namespace().is_some() || d.as_constant().is_some() || d.as_constant_alias().is_some(),
|
|
536
|
+
|_| true,
|
|
537
|
+
context,
|
|
538
|
+
candidates,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/// Collects class variables visible from the current lexical scope. Class variables are resolved
|
|
546
|
+
/// lexically and singleton classes are skipped: inside `class Bar; class << Foo; @@cvar` the
|
|
547
|
+
/// cvar belongs to `Bar`, not `Foo`. We walk the lexical chain (innermost outward) until we find
|
|
548
|
+
/// a non-singleton namespace, then walk that namespace's ancestors.
|
|
549
|
+
fn collect_class_variables_from_lexical_scope<'a>(
|
|
550
|
+
graph: &'a Graph,
|
|
551
|
+
name_ref: &crate::model::name::ResolvedName,
|
|
552
|
+
context: &mut CompletionContext<'a>,
|
|
553
|
+
candidates: &mut Vec<CompletionCandidate>,
|
|
554
|
+
) {
|
|
555
|
+
let mut decl = graph
|
|
556
|
+
.declarations()
|
|
557
|
+
.get(name_ref.declaration_id())
|
|
558
|
+
.unwrap()
|
|
559
|
+
.as_namespace()
|
|
560
|
+
.unwrap();
|
|
561
|
+
let mut current_name_id = *name_ref.nesting();
|
|
385
562
|
|
|
386
|
-
|
|
563
|
+
while matches!(decl, Namespace::SingletonClass(_)) {
|
|
564
|
+
let Some(parent_name_id) = current_name_id else {
|
|
565
|
+
// No non-singleton lexical scope (invalid Ruby). Skip cvar collection.
|
|
566
|
+
return;
|
|
567
|
+
};
|
|
568
|
+
let NameRef::Resolved(parent_ref) = graph.names().get(&parent_name_id).unwrap() else {
|
|
569
|
+
return;
|
|
570
|
+
};
|
|
571
|
+
decl = graph
|
|
387
572
|
.declarations()
|
|
388
|
-
.get(
|
|
573
|
+
.get(parent_ref.declaration_id())
|
|
389
574
|
.unwrap()
|
|
390
575
|
.as_namespace()
|
|
391
576
|
.unwrap();
|
|
577
|
+
current_name_id = *parent_ref.nesting();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
for ancestor in decl.ancestors() {
|
|
581
|
+
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
582
|
+
collect_members(
|
|
583
|
+
graph,
|
|
584
|
+
*ancestor_id,
|
|
585
|
+
|d| d.as_class_variable().is_some(),
|
|
586
|
+
|_| true,
|
|
587
|
+
context,
|
|
588
|
+
candidates,
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/// Walks the outer lexical nesting chain (excluding the innermost scope) to collect constants reachable through
|
|
595
|
+
/// enclosing classes/modules.
|
|
596
|
+
fn collect_constants_from_outer_nesting<'a>(
|
|
597
|
+
graph: &'a Graph,
|
|
598
|
+
name_ref: &crate::model::name::ResolvedName,
|
|
599
|
+
context: &mut CompletionContext<'a>,
|
|
600
|
+
candidates: &mut Vec<CompletionCandidate>,
|
|
601
|
+
) {
|
|
602
|
+
let mut current_name_id = *name_ref.nesting();
|
|
603
|
+
|
|
604
|
+
while let Some(id) = current_name_id {
|
|
605
|
+
let NameRef::Resolved(parent_ref) = graph.names().get(&id).unwrap() else {
|
|
606
|
+
break;
|
|
607
|
+
};
|
|
392
608
|
|
|
393
|
-
|
|
609
|
+
collect_members(
|
|
394
610
|
graph,
|
|
395
|
-
|
|
611
|
+
*parent_ref.declaration_id(),
|
|
612
|
+
|d| d.as_namespace().is_some() || d.as_constant().is_some() || d.as_constant_alias().is_some(),
|
|
613
|
+
|_| true,
|
|
396
614
|
context,
|
|
397
615
|
candidates,
|
|
398
|
-
Declaration::Namespace(_) | Declaration::Constant(_) | Declaration::ConstantAlias(_)
|
|
399
616
|
);
|
|
400
617
|
|
|
401
|
-
current_name_id = *
|
|
618
|
+
current_name_id = *parent_ref.nesting();
|
|
402
619
|
}
|
|
620
|
+
}
|
|
403
621
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
| Declaration::Constant(_)
|
|
413
|
-
| Declaration::ConstantAlias(_)
|
|
414
|
-
| Declaration::GlobalVariable(_)
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
// Walk ancestors collecting all applicable completion members
|
|
622
|
+
/// Collects methods and instance variables along `self`'s ancestor chain. The chain may differ
|
|
623
|
+
/// from the lexical chain when `self` was rebound (e.g., `def Foo.baz` written inside `class Bar`).
|
|
624
|
+
fn collect_methods_and_ivars_from_self<'a>(
|
|
625
|
+
graph: &'a Graph,
|
|
626
|
+
self_decl: &'a Namespace,
|
|
627
|
+
context: &mut CompletionContext<'a>,
|
|
628
|
+
candidates: &mut Vec<CompletionCandidate>,
|
|
629
|
+
) {
|
|
418
630
|
for ancestor in self_decl.ancestors() {
|
|
419
631
|
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
420
|
-
|
|
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!(
|
|
632
|
+
collect_members(
|
|
427
633
|
graph,
|
|
428
|
-
|
|
634
|
+
*ancestor_id,
|
|
635
|
+
|d| d.as_method().is_some() || d.as_instance_variable().is_some(),
|
|
636
|
+
|_| true,
|
|
429
637
|
context,
|
|
430
638
|
candidates,
|
|
431
|
-
Declaration::ClassVariable(_)
|
|
432
639
|
);
|
|
433
640
|
}
|
|
434
641
|
}
|
|
435
|
-
|
|
436
|
-
// Keywords are always available in expression contexts
|
|
437
|
-
candidates.extend(keywords::KEYWORDS.iter().map(CompletionCandidate::Keyword));
|
|
438
|
-
Ok(candidates)
|
|
439
642
|
}
|
|
440
643
|
|
|
441
644
|
/// Collect completion for a method argument (e.g.: `foo.bar(|)`)
|
|
442
645
|
fn method_argument_completion<'a>(
|
|
443
646
|
graph: &'a Graph,
|
|
444
|
-
|
|
647
|
+
self_decl_id: Option<DeclarationId>,
|
|
648
|
+
nesting_name_id: NameId,
|
|
445
649
|
method_decl_id: DeclarationId,
|
|
446
650
|
context: CompletionContext<'a>,
|
|
447
651
|
) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
|
|
448
|
-
let mut candidates = expression_completion(graph,
|
|
652
|
+
let mut candidates = expression_completion(graph, self_decl_id, nesting_name_id, context)?;
|
|
449
653
|
let Some(method_decl) = graph.declarations().get(&method_decl_id) else {
|
|
450
654
|
return Ok(candidates);
|
|
451
655
|
};
|
|
@@ -470,6 +674,153 @@ fn method_argument_completion<'a>(
|
|
|
470
674
|
Ok(candidates)
|
|
471
675
|
}
|
|
472
676
|
|
|
677
|
+
/// Reasons [`find_member_in_ancestors`] could not produce a target declaration.
|
|
678
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
679
|
+
pub enum FindMemberError {
|
|
680
|
+
/// The provided declaration id does not exist in the graph.
|
|
681
|
+
DeclarationNotFound,
|
|
682
|
+
/// The declaration exists but is not a namespace, so it has no members or ancestor chain to search.
|
|
683
|
+
NotNamespace,
|
|
684
|
+
/// The declaration is a namespace, but no matching member exists on it or any of its ancestors.
|
|
685
|
+
MemberNotFound,
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/// Finds the given member on the ancestor chain of the declaration. Use `only_inherited` to skip all ancestors until
|
|
689
|
+
/// the main namespace and start from its parent.
|
|
690
|
+
///
|
|
691
|
+
/// # Errors
|
|
692
|
+
///
|
|
693
|
+
/// Returns a [`FindMemberError`] describing why no target declaration could be produced (declaration not found, not a
|
|
694
|
+
/// namespace, or member missing on the ancestor chain).
|
|
695
|
+
///
|
|
696
|
+
/// # Panics
|
|
697
|
+
///
|
|
698
|
+
/// Will panic if we incorrectly store ancestors that are not namespaces.
|
|
699
|
+
pub fn find_member_in_ancestors(
|
|
700
|
+
graph: &Graph,
|
|
701
|
+
declaration_id: DeclarationId,
|
|
702
|
+
member_str_id: StringId,
|
|
703
|
+
only_inherited: bool,
|
|
704
|
+
) -> Result<DeclarationId, FindMemberError> {
|
|
705
|
+
let declaration = graph
|
|
706
|
+
.declarations()
|
|
707
|
+
.get(&declaration_id)
|
|
708
|
+
.ok_or(FindMemberError::DeclarationNotFound)?;
|
|
709
|
+
let namespace = declaration.as_namespace().ok_or(FindMemberError::NotNamespace)?;
|
|
710
|
+
let mut found_main_namespace = false;
|
|
711
|
+
|
|
712
|
+
for ancestor in namespace.ancestors() {
|
|
713
|
+
let Ancestor::Complete(ancestor_id) = ancestor else {
|
|
714
|
+
continue;
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
if only_inherited && !found_main_namespace {
|
|
718
|
+
if *ancestor_id == declaration_id {
|
|
719
|
+
found_main_namespace = true;
|
|
720
|
+
}
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if let Some(member_id) = graph
|
|
725
|
+
.declarations()
|
|
726
|
+
.get(ancestor_id)
|
|
727
|
+
.unwrap()
|
|
728
|
+
.as_namespace()
|
|
729
|
+
.unwrap()
|
|
730
|
+
.member(&member_str_id)
|
|
731
|
+
{
|
|
732
|
+
return Ok(*member_id);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
Err(FindMemberError::MemberNotFound)
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/// Reasons [`follow_method_alias`] could not produce a target declaration.
|
|
740
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
741
|
+
pub enum AliasResolutionError {
|
|
742
|
+
/// The provided definition id is not a `MethodAlias`.
|
|
743
|
+
NotAnAlias,
|
|
744
|
+
/// The alias's owner could not be resolved (e.g., a `ConstantReceiver` whose name never resolved to a declaration,
|
|
745
|
+
/// or a singleton-class chain whose attached object isn't resolvable).
|
|
746
|
+
UnresolvedOwner,
|
|
747
|
+
/// The chain of aliases forms a cycle. The chain was abandoned at the first revisit.
|
|
748
|
+
Cycle,
|
|
749
|
+
/// The alias's `old_name` does not exist on the owner or any of its ancestors.
|
|
750
|
+
TargetNotFound,
|
|
751
|
+
/// The resolved target is not a method declaration. Indicates a graph inconsistency since method-name lookups
|
|
752
|
+
/// should only land on `Declaration::Method`.
|
|
753
|
+
TargetNotMethod,
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/// Follows `alias_id` through any chain of further `MethodAlias` definitions and returns the `DeclarationId` of the
|
|
757
|
+
/// final method declaration that has at least one non-alias definition (a regular `def`, `attr_*`, etc.).
|
|
758
|
+
///
|
|
759
|
+
/// # Errors
|
|
760
|
+
///
|
|
761
|
+
/// Returns an `AliasResolutionError` describing why the chain could not be resolved (not an alias, unresolved owner,
|
|
762
|
+
/// cyclic chain, target missing, or target not a method).
|
|
763
|
+
///
|
|
764
|
+
/// # Panics
|
|
765
|
+
///
|
|
766
|
+
/// Panics if the graph is internally inconsistent
|
|
767
|
+
pub fn follow_method_alias(graph: &Graph, alias_id: DefinitionId) -> Result<DeclarationId, AliasResolutionError> {
|
|
768
|
+
let mut seen: IdentityHashSet<DeclarationId> = IdentityHashSet::default();
|
|
769
|
+
let mut current = alias_id;
|
|
770
|
+
|
|
771
|
+
loop {
|
|
772
|
+
let Some(Definition::MethodAlias(alias)) = graph.definitions().get(¤t) else {
|
|
773
|
+
return Err(AliasResolutionError::NotAnAlias);
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
let owner_id = graph
|
|
777
|
+
.definition_id_to_declaration_id(current)
|
|
778
|
+
.and_then(|decl_id| graph.declarations().get(decl_id))
|
|
779
|
+
.map(|decl| *decl.owner_id())
|
|
780
|
+
.ok_or(AliasResolutionError::UnresolvedOwner)?;
|
|
781
|
+
|
|
782
|
+
let target_id = match find_member_in_ancestors(graph, owner_id, *alias.old_name_str_id(), false) {
|
|
783
|
+
Ok(id) => id,
|
|
784
|
+
Err(FindMemberError::MemberNotFound) => return Err(AliasResolutionError::TargetNotFound),
|
|
785
|
+
Err(err @ (FindMemberError::DeclarationNotFound | FindMemberError::NotNamespace)) => {
|
|
786
|
+
unreachable!("alias owner must be a valid namespace declaration, got {err:?}")
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
if !seen.insert(target_id) {
|
|
791
|
+
return Err(AliasResolutionError::Cycle);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
let Declaration::Method(target) = graph
|
|
795
|
+
.declarations()
|
|
796
|
+
.get(&target_id)
|
|
797
|
+
.expect("member returned by find_member_in_ancestors must exist")
|
|
798
|
+
else {
|
|
799
|
+
return Err(AliasResolutionError::TargetNotMethod);
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// Stop at the first non-alias definition; otherwise track the smallest alias `DefinitionId` so the trace stays
|
|
803
|
+
// deterministic across runs. (If two aliases target different methods, we just pick one of them.)
|
|
804
|
+
let mut maybe_next_alias: Option<DefinitionId> = None;
|
|
805
|
+
|
|
806
|
+
for &def_id in target.definitions() {
|
|
807
|
+
if !matches!(
|
|
808
|
+
graph
|
|
809
|
+
.definitions()
|
|
810
|
+
.get(&def_id)
|
|
811
|
+
.expect("declaration definition_id must exist in the graph"),
|
|
812
|
+
Definition::MethodAlias(_),
|
|
813
|
+
) {
|
|
814
|
+
return Ok(target_id);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
maybe_next_alias = Some(maybe_next_alias.map_or(def_id, |m| m.min(def_id)));
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
current = maybe_next_alias.ok_or(AliasResolutionError::TargetNotFound)?;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
473
824
|
#[cfg(test)]
|
|
474
825
|
mod tests {
|
|
475
826
|
use std::str::FromStr;
|
|
@@ -523,30 +874,37 @@ mod tests {
|
|
|
523
874
|
|
|
524
875
|
macro_rules! assert_completion_eq {
|
|
525
876
|
($context:expr, $receiver:expr, $expected:expr) => {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
);
|
|
877
|
+
let mut actual: Vec<String> = completion_candidates($context.graph(), CompletionContext::new($receiver))
|
|
878
|
+
.unwrap()
|
|
879
|
+
.iter()
|
|
880
|
+
.map(|candidate| candidate_label(&$context, candidate))
|
|
881
|
+
.collect();
|
|
882
|
+
actual.sort();
|
|
883
|
+
|
|
884
|
+
let mut expected: Vec<String> = $expected.into_iter().map(String::from).collect();
|
|
885
|
+
expected.sort();
|
|
886
|
+
|
|
887
|
+
assert_eq!(expected, actual);
|
|
534
888
|
};
|
|
535
889
|
}
|
|
536
890
|
|
|
537
891
|
/// Asserts declaration and keyword argument completion candidates, excluding language keywords.
|
|
538
892
|
/// Language keywords are always present in expression contexts and tested separately.
|
|
893
|
+
/// Both sides are sorted before comparison so tests are not coupled to candidate emission order.
|
|
539
894
|
macro_rules! assert_declaration_completion_eq {
|
|
540
895
|
($context:expr, $receiver:expr, $expected:expr) => {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
);
|
|
896
|
+
let mut actual: Vec<String> = completion_candidates($context.graph(), CompletionContext::new($receiver))
|
|
897
|
+
.unwrap()
|
|
898
|
+
.iter()
|
|
899
|
+
.filter(|c| !matches!(c, CompletionCandidate::Keyword(_)))
|
|
900
|
+
.map(|candidate| candidate_label(&$context, candidate))
|
|
901
|
+
.collect();
|
|
902
|
+
actual.sort();
|
|
903
|
+
|
|
904
|
+
let mut expected: Vec<String> = $expected.into_iter().map(String::from).collect();
|
|
905
|
+
expected.sort();
|
|
906
|
+
|
|
907
|
+
assert_eq!(expected, actual);
|
|
550
908
|
};
|
|
551
909
|
}
|
|
552
910
|
|
|
@@ -761,8 +1119,12 @@ mod tests {
|
|
|
761
1119
|
let name_id = Name::new(StringId::from("Child"), ParentScope::None, None).id();
|
|
762
1120
|
assert_declaration_completion_eq!(
|
|
763
1121
|
context,
|
|
764
|
-
CompletionReceiver::Expression
|
|
1122
|
+
CompletionReceiver::Expression {
|
|
1123
|
+
self_decl_id: None,
|
|
1124
|
+
nesting_name_id: name_id,
|
|
1125
|
+
},
|
|
765
1126
|
[
|
|
1127
|
+
"Foo::CONST",
|
|
766
1128
|
"Class",
|
|
767
1129
|
"BasicObject",
|
|
768
1130
|
"Child",
|
|
@@ -772,7 +1134,6 @@ mod tests {
|
|
|
772
1134
|
"Foo",
|
|
773
1135
|
"Object",
|
|
774
1136
|
"Child#baz()",
|
|
775
|
-
"Foo::CONST",
|
|
776
1137
|
"Foo#bar()",
|
|
777
1138
|
"Parent#initialize()",
|
|
778
1139
|
"Parent#@var"
|
|
@@ -807,7 +1168,10 @@ mod tests {
|
|
|
807
1168
|
let name_id = Name::new(StringId::from("Child"), ParentScope::None, None).id();
|
|
808
1169
|
assert_declaration_completion_eq!(
|
|
809
1170
|
context,
|
|
810
|
-
CompletionReceiver::Expression
|
|
1171
|
+
CompletionReceiver::Expression {
|
|
1172
|
+
self_decl_id: None,
|
|
1173
|
+
nesting_name_id: name_id,
|
|
1174
|
+
},
|
|
811
1175
|
[
|
|
812
1176
|
"Class",
|
|
813
1177
|
"BasicObject",
|
|
@@ -853,7 +1217,10 @@ mod tests {
|
|
|
853
1217
|
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
854
1218
|
assert_declaration_completion_eq!(
|
|
855
1219
|
context,
|
|
856
|
-
CompletionReceiver::Expression
|
|
1220
|
+
CompletionReceiver::Expression {
|
|
1221
|
+
self_decl_id: None,
|
|
1222
|
+
nesting_name_id: name_id,
|
|
1223
|
+
},
|
|
857
1224
|
[
|
|
858
1225
|
"Foo",
|
|
859
1226
|
"Class",
|
|
@@ -900,7 +1267,10 @@ mod tests {
|
|
|
900
1267
|
let name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_id), Some(foo_id)).id();
|
|
901
1268
|
assert_declaration_completion_eq!(
|
|
902
1269
|
context,
|
|
903
|
-
CompletionReceiver::Expression
|
|
1270
|
+
CompletionReceiver::Expression {
|
|
1271
|
+
self_decl_id: None,
|
|
1272
|
+
nesting_name_id: name_id,
|
|
1273
|
+
},
|
|
904
1274
|
[
|
|
905
1275
|
"Module",
|
|
906
1276
|
"Class",
|
|
@@ -917,7 +1287,10 @@ mod tests {
|
|
|
917
1287
|
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
918
1288
|
assert_declaration_completion_eq!(
|
|
919
1289
|
context,
|
|
920
|
-
CompletionReceiver::Expression
|
|
1290
|
+
CompletionReceiver::Expression {
|
|
1291
|
+
self_decl_id: None,
|
|
1292
|
+
nesting_name_id: name_id,
|
|
1293
|
+
},
|
|
921
1294
|
[
|
|
922
1295
|
"Module",
|
|
923
1296
|
"Class",
|
|
@@ -966,9 +1339,11 @@ mod tests {
|
|
|
966
1339
|
.id();
|
|
967
1340
|
assert_declaration_completion_eq!(
|
|
968
1341
|
context,
|
|
969
|
-
CompletionReceiver::Expression
|
|
1342
|
+
CompletionReceiver::Expression {
|
|
1343
|
+
self_decl_id: None,
|
|
1344
|
+
nesting_name_id: name_id,
|
|
1345
|
+
},
|
|
970
1346
|
[
|
|
971
|
-
"Foo::CONST_A",
|
|
972
1347
|
"Module",
|
|
973
1348
|
"Class",
|
|
974
1349
|
"Object",
|
|
@@ -976,6 +1351,7 @@ mod tests {
|
|
|
976
1351
|
"Kernel",
|
|
977
1352
|
"Foo",
|
|
978
1353
|
"Bar",
|
|
1354
|
+
"Foo::CONST_A",
|
|
979
1355
|
"Bar#bar_m()",
|
|
980
1356
|
"Bar#bar_m2()"
|
|
981
1357
|
]
|
|
@@ -984,7 +1360,10 @@ mod tests {
|
|
|
984
1360
|
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
985
1361
|
assert_declaration_completion_eq!(
|
|
986
1362
|
context,
|
|
987
|
-
CompletionReceiver::Expression
|
|
1363
|
+
CompletionReceiver::Expression {
|
|
1364
|
+
self_decl_id: None,
|
|
1365
|
+
nesting_name_id: name_id,
|
|
1366
|
+
},
|
|
988
1367
|
[
|
|
989
1368
|
"Module",
|
|
990
1369
|
"Class",
|
|
@@ -1025,16 +1404,19 @@ mod tests {
|
|
|
1025
1404
|
// when the user types the unqualified name CONST
|
|
1026
1405
|
assert_declaration_completion_eq!(
|
|
1027
1406
|
context,
|
|
1028
|
-
CompletionReceiver::Expression
|
|
1407
|
+
CompletionReceiver::Expression {
|
|
1408
|
+
self_decl_id: None,
|
|
1409
|
+
nesting_name_id: name_id,
|
|
1410
|
+
},
|
|
1029
1411
|
[
|
|
1030
|
-
"Foo::CONST",
|
|
1031
|
-
"Foo::Bar",
|
|
1032
1412
|
"Class",
|
|
1033
1413
|
"Object",
|
|
1034
1414
|
"BasicObject",
|
|
1035
1415
|
"Kernel",
|
|
1036
1416
|
"Foo",
|
|
1037
1417
|
"Module",
|
|
1418
|
+
"Foo::CONST",
|
|
1419
|
+
"Foo::Bar",
|
|
1038
1420
|
"Foo::Bar#baz()"
|
|
1039
1421
|
]
|
|
1040
1422
|
);
|
|
@@ -1069,19 +1451,11 @@ mod tests {
|
|
|
1069
1451
|
.id();
|
|
1070
1452
|
assert_declaration_completion_eq!(
|
|
1071
1453
|
context,
|
|
1072
|
-
CompletionReceiver::Expression
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
"BasicObject",
|
|
1078
|
-
"Object",
|
|
1079
|
-
"Kernel",
|
|
1080
|
-
"Module",
|
|
1081
|
-
"Foo",
|
|
1082
|
-
"Class",
|
|
1083
|
-
"Foo::Bar#bar_m()"
|
|
1084
|
-
]
|
|
1454
|
+
CompletionReceiver::Expression {
|
|
1455
|
+
self_decl_id: None,
|
|
1456
|
+
nesting_name_id: name_id,
|
|
1457
|
+
},
|
|
1458
|
+
["Foo::Bar", "$var2", "$var", "Foo::Bar#bar_m()"]
|
|
1085
1459
|
);
|
|
1086
1460
|
}
|
|
1087
1461
|
|
|
@@ -1108,7 +1482,10 @@ mod tests {
|
|
|
1108
1482
|
|
|
1109
1483
|
assert_completion_eq!(
|
|
1110
1484
|
context,
|
|
1111
|
-
CompletionReceiver::NamespaceAccess
|
|
1485
|
+
CompletionReceiver::NamespaceAccess {
|
|
1486
|
+
self_decl_id: None,
|
|
1487
|
+
namespace_decl_id: DeclarationId::from("Foo")
|
|
1488
|
+
},
|
|
1112
1489
|
["Foo::CONST", "Foo::Bar", "Foo::<Foo>#class_method()"]
|
|
1113
1490
|
);
|
|
1114
1491
|
}
|
|
@@ -1141,7 +1518,10 @@ mod tests {
|
|
|
1141
1518
|
|
|
1142
1519
|
assert_completion_eq!(
|
|
1143
1520
|
context,
|
|
1144
|
-
CompletionReceiver::NamespaceAccess
|
|
1521
|
+
CompletionReceiver::NamespaceAccess {
|
|
1522
|
+
self_decl_id: None,
|
|
1523
|
+
namespace_decl_id: DeclarationId::from("Child")
|
|
1524
|
+
},
|
|
1145
1525
|
[
|
|
1146
1526
|
"Child::CHILD_CONST",
|
|
1147
1527
|
"Parent::PARENT_CONST",
|
|
@@ -1179,7 +1559,10 @@ mod tests {
|
|
|
1179
1559
|
|
|
1180
1560
|
assert_completion_eq!(
|
|
1181
1561
|
context,
|
|
1182
|
-
CompletionReceiver::NamespaceAccess
|
|
1562
|
+
CompletionReceiver::NamespaceAccess {
|
|
1563
|
+
self_decl_id: None,
|
|
1564
|
+
namespace_decl_id: DeclarationId::from("Child")
|
|
1565
|
+
},
|
|
1183
1566
|
["Child::CONST", "Child::<Child>#shared_method()"]
|
|
1184
1567
|
);
|
|
1185
1568
|
}
|
|
@@ -1202,7 +1585,10 @@ mod tests {
|
|
|
1202
1585
|
|
|
1203
1586
|
assert_completion_eq!(
|
|
1204
1587
|
context,
|
|
1205
|
-
CompletionReceiver::NamespaceAccess
|
|
1588
|
+
CompletionReceiver::NamespaceAccess {
|
|
1589
|
+
self_decl_id: None,
|
|
1590
|
+
namespace_decl_id: DeclarationId::from("Foo")
|
|
1591
|
+
},
|
|
1206
1592
|
["Foo::CONST"]
|
|
1207
1593
|
);
|
|
1208
1594
|
}
|
|
@@ -1224,7 +1610,10 @@ mod tests {
|
|
|
1224
1610
|
|
|
1225
1611
|
assert_completion_eq!(
|
|
1226
1612
|
context,
|
|
1227
|
-
CompletionReceiver::NamespaceAccess
|
|
1613
|
+
CompletionReceiver::NamespaceAccess {
|
|
1614
|
+
self_decl_id: None,
|
|
1615
|
+
namespace_decl_id: DeclarationId::from("Foo")
|
|
1616
|
+
},
|
|
1228
1617
|
["Foo::CONST", "Foo::Bar"]
|
|
1229
1618
|
);
|
|
1230
1619
|
}
|
|
@@ -1254,7 +1643,10 @@ mod tests {
|
|
|
1254
1643
|
|
|
1255
1644
|
assert_completion_eq!(
|
|
1256
1645
|
context,
|
|
1257
|
-
CompletionReceiver::NamespaceAccess
|
|
1646
|
+
CompletionReceiver::NamespaceAccess {
|
|
1647
|
+
self_decl_id: None,
|
|
1648
|
+
namespace_decl_id: DeclarationId::from("Foo::MyOriginal")
|
|
1649
|
+
},
|
|
1258
1650
|
[
|
|
1259
1651
|
"Original::CONST",
|
|
1260
1652
|
"Original::Nested",
|
|
@@ -1286,7 +1678,10 @@ mod tests {
|
|
|
1286
1678
|
|
|
1287
1679
|
assert_completion_eq!(
|
|
1288
1680
|
context,
|
|
1289
|
-
CompletionReceiver::NamespaceAccess
|
|
1681
|
+
CompletionReceiver::NamespaceAccess {
|
|
1682
|
+
self_decl_id: None,
|
|
1683
|
+
namespace_decl_id: DeclarationId::from("Alias2")
|
|
1684
|
+
},
|
|
1290
1685
|
["Original::CONST", "Original::<Original>#class_method()"]
|
|
1291
1686
|
);
|
|
1292
1687
|
}
|
|
@@ -1313,7 +1708,10 @@ mod tests {
|
|
|
1313
1708
|
|
|
1314
1709
|
assert_completion_eq!(
|
|
1315
1710
|
context,
|
|
1316
|
-
CompletionReceiver::NamespaceAccess
|
|
1711
|
+
CompletionReceiver::NamespaceAccess {
|
|
1712
|
+
self_decl_id: None,
|
|
1713
|
+
namespace_decl_id: DeclarationId::from("Foo")
|
|
1714
|
+
},
|
|
1317
1715
|
["Foo::CONST", "Foo::<Foo>#class_method()"]
|
|
1318
1716
|
);
|
|
1319
1717
|
}
|
|
@@ -1347,7 +1745,10 @@ mod tests {
|
|
|
1347
1745
|
|
|
1348
1746
|
assert_completion_eq!(
|
|
1349
1747
|
context,
|
|
1350
|
-
CompletionReceiver::NamespaceAccess
|
|
1748
|
+
CompletionReceiver::NamespaceAccess {
|
|
1749
|
+
self_decl_id: None,
|
|
1750
|
+
namespace_decl_id: DeclarationId::from("Foo")
|
|
1751
|
+
},
|
|
1351
1752
|
["Foo::FOO_CONST", "Bar::CONST", "Foo::<Foo>#foo_class_method()"]
|
|
1352
1753
|
);
|
|
1353
1754
|
}
|
|
@@ -1375,7 +1776,10 @@ mod tests {
|
|
|
1375
1776
|
|
|
1376
1777
|
assert_completion_eq!(
|
|
1377
1778
|
context,
|
|
1378
|
-
CompletionReceiver::MethodCall
|
|
1779
|
+
CompletionReceiver::MethodCall {
|
|
1780
|
+
self_decl_id: None,
|
|
1781
|
+
receiver_decl_id: DeclarationId::from("Foo")
|
|
1782
|
+
},
|
|
1379
1783
|
["Foo#baz()", "Foo#bar()"]
|
|
1380
1784
|
);
|
|
1381
1785
|
}
|
|
@@ -1405,7 +1809,10 @@ mod tests {
|
|
|
1405
1809
|
|
|
1406
1810
|
assert_completion_eq!(
|
|
1407
1811
|
context,
|
|
1408
|
-
CompletionReceiver::MethodCall
|
|
1812
|
+
CompletionReceiver::MethodCall {
|
|
1813
|
+
self_decl_id: None,
|
|
1814
|
+
receiver_decl_id: DeclarationId::from("Foo::MyOriginal")
|
|
1815
|
+
},
|
|
1409
1816
|
["Original#baz()", "Original#bar()"]
|
|
1410
1817
|
);
|
|
1411
1818
|
}
|
|
@@ -1430,7 +1837,10 @@ mod tests {
|
|
|
1430
1837
|
|
|
1431
1838
|
assert_completion_eq!(
|
|
1432
1839
|
context,
|
|
1433
|
-
CompletionReceiver::MethodCall
|
|
1840
|
+
CompletionReceiver::MethodCall {
|
|
1841
|
+
self_decl_id: None,
|
|
1842
|
+
receiver_decl_id: DeclarationId::from("Child")
|
|
1843
|
+
},
|
|
1434
1844
|
["Child#child_method()", "Parent#parent_method()"]
|
|
1435
1845
|
);
|
|
1436
1846
|
}
|
|
@@ -1457,7 +1867,10 @@ mod tests {
|
|
|
1457
1867
|
|
|
1458
1868
|
assert_completion_eq!(
|
|
1459
1869
|
context,
|
|
1460
|
-
CompletionReceiver::MethodCall
|
|
1870
|
+
CompletionReceiver::MethodCall {
|
|
1871
|
+
self_decl_id: None,
|
|
1872
|
+
receiver_decl_id: DeclarationId::from("Foo")
|
|
1873
|
+
},
|
|
1461
1874
|
["Foo#foo_method()", "Mixin#mixin_method()"]
|
|
1462
1875
|
);
|
|
1463
1876
|
}
|
|
@@ -1484,7 +1897,10 @@ mod tests {
|
|
|
1484
1897
|
|
|
1485
1898
|
assert_completion_eq!(
|
|
1486
1899
|
context,
|
|
1487
|
-
CompletionReceiver::MethodCall
|
|
1900
|
+
CompletionReceiver::MethodCall {
|
|
1901
|
+
self_decl_id: None,
|
|
1902
|
+
receiver_decl_id: DeclarationId::from("Child")
|
|
1903
|
+
},
|
|
1488
1904
|
["Child#shared_method()", "Child#child_only()", "Parent#parent_only()"]
|
|
1489
1905
|
);
|
|
1490
1906
|
}
|
|
@@ -1512,7 +1928,10 @@ mod tests {
|
|
|
1512
1928
|
|
|
1513
1929
|
assert_completion_eq!(
|
|
1514
1930
|
context,
|
|
1515
|
-
CompletionReceiver::MethodCall
|
|
1931
|
+
CompletionReceiver::MethodCall {
|
|
1932
|
+
self_decl_id: None,
|
|
1933
|
+
receiver_decl_id: DeclarationId::from("Foo")
|
|
1934
|
+
},
|
|
1516
1935
|
["Foo#initialize()", "Foo#bar()"]
|
|
1517
1936
|
);
|
|
1518
1937
|
}
|
|
@@ -1537,7 +1956,10 @@ mod tests {
|
|
|
1537
1956
|
|
|
1538
1957
|
assert_completion_eq!(
|
|
1539
1958
|
context,
|
|
1540
|
-
CompletionReceiver::MethodCall
|
|
1959
|
+
CompletionReceiver::MethodCall {
|
|
1960
|
+
self_decl_id: None,
|
|
1961
|
+
receiver_decl_id: DeclarationId::from("Foo::<Foo>")
|
|
1962
|
+
},
|
|
1541
1963
|
["Foo::<Foo>#baz()", "Foo::<Foo>#bar()"]
|
|
1542
1964
|
);
|
|
1543
1965
|
}
|
|
@@ -1560,7 +1982,8 @@ mod tests {
|
|
|
1560
1982
|
assert_declaration_completion_eq!(
|
|
1561
1983
|
context,
|
|
1562
1984
|
CompletionReceiver::MethodArgument {
|
|
1563
|
-
|
|
1985
|
+
self_decl_id: None,
|
|
1986
|
+
nesting_name_id: name_id,
|
|
1564
1987
|
method_decl_id: DeclarationId::from("Foo#greet()"),
|
|
1565
1988
|
},
|
|
1566
1989
|
[
|
|
@@ -1595,7 +2018,8 @@ mod tests {
|
|
|
1595
2018
|
assert_declaration_completion_eq!(
|
|
1596
2019
|
context,
|
|
1597
2020
|
CompletionReceiver::MethodArgument {
|
|
1598
|
-
|
|
2021
|
+
self_decl_id: None,
|
|
2022
|
+
nesting_name_id: name_id,
|
|
1599
2023
|
method_decl_id: DeclarationId::from("Foo#bar()"),
|
|
1600
2024
|
},
|
|
1601
2025
|
["Class", "Object", "BasicObject", "Kernel", "Foo", "Module", "Foo#bar()"]
|
|
@@ -1620,7 +2044,8 @@ mod tests {
|
|
|
1620
2044
|
assert_declaration_completion_eq!(
|
|
1621
2045
|
context,
|
|
1622
2046
|
CompletionReceiver::MethodArgument {
|
|
1623
|
-
|
|
2047
|
+
self_decl_id: None,
|
|
2048
|
+
nesting_name_id: name_id,
|
|
1624
2049
|
method_decl_id: DeclarationId::from("Foo#search()"),
|
|
1625
2050
|
},
|
|
1626
2051
|
// Only RequiredKeyword and OptionalKeyword, not RestKeyword (**opts)
|
|
@@ -1663,7 +2088,8 @@ mod tests {
|
|
|
1663
2088
|
assert_declaration_completion_eq!(
|
|
1664
2089
|
context,
|
|
1665
2090
|
CompletionReceiver::MethodArgument {
|
|
1666
|
-
|
|
2091
|
+
self_decl_id: None,
|
|
2092
|
+
nesting_name_id: name_id,
|
|
1667
2093
|
method_decl_id: DeclarationId::from("Foo#bar()"),
|
|
1668
2094
|
},
|
|
1669
2095
|
[
|
|
@@ -1689,7 +2115,10 @@ mod tests {
|
|
|
1689
2115
|
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
1690
2116
|
assert_completion_eq!(
|
|
1691
2117
|
context,
|
|
1692
|
-
CompletionReceiver::Expression
|
|
2118
|
+
CompletionReceiver::Expression {
|
|
2119
|
+
self_decl_id: None,
|
|
2120
|
+
nesting_name_id: name_id,
|
|
2121
|
+
},
|
|
1693
2122
|
[
|
|
1694
2123
|
"Class",
|
|
1695
2124
|
"Object",
|
|
@@ -1752,7 +2181,8 @@ mod tests {
|
|
|
1752
2181
|
assert_completion_eq!(
|
|
1753
2182
|
context,
|
|
1754
2183
|
CompletionReceiver::MethodArgument {
|
|
1755
|
-
|
|
2184
|
+
self_decl_id: None,
|
|
2185
|
+
nesting_name_id: name_id,
|
|
1756
2186
|
method_decl_id: DeclarationId::from("Foo#bar()"),
|
|
1757
2187
|
},
|
|
1758
2188
|
[
|
|
@@ -1817,7 +2247,10 @@ mod tests {
|
|
|
1817
2247
|
|
|
1818
2248
|
let candidates = completion_candidates(
|
|
1819
2249
|
context.graph(),
|
|
1820
|
-
CompletionContext::new(CompletionReceiver::NamespaceAccess
|
|
2250
|
+
CompletionContext::new(CompletionReceiver::NamespaceAccess {
|
|
2251
|
+
self_decl_id: None,
|
|
2252
|
+
namespace_decl_id: DeclarationId::from("Foo"),
|
|
2253
|
+
}),
|
|
1821
2254
|
)
|
|
1822
2255
|
.unwrap();
|
|
1823
2256
|
|
|
@@ -1832,10 +2265,1490 @@ mod tests {
|
|
|
1832
2265
|
|
|
1833
2266
|
let candidates = completion_candidates(
|
|
1834
2267
|
context.graph(),
|
|
1835
|
-
CompletionContext::new(CompletionReceiver::MethodCall
|
|
2268
|
+
CompletionContext::new(CompletionReceiver::MethodCall {
|
|
2269
|
+
self_decl_id: None,
|
|
2270
|
+
receiver_decl_id: DeclarationId::from("Foo"),
|
|
2271
|
+
}),
|
|
1836
2272
|
)
|
|
1837
2273
|
.unwrap();
|
|
1838
2274
|
|
|
1839
2275
|
assert!(!candidates.iter().any(|c| matches!(c, CompletionCandidate::Keyword(_))));
|
|
1840
2276
|
}
|
|
2277
|
+
|
|
2278
|
+
#[test]
|
|
2279
|
+
fn expression_completion_class_variables_follow_lexical_scope() {
|
|
2280
|
+
// `@@cvar` in Ruby is resolved via the innermost lexical class/module's ancestor chain,
|
|
2281
|
+
// NOT `self`'s ancestor chain. Inside `def Foo.bar` written inside `Outer`, the lexical
|
|
2282
|
+
// scope is `[Outer]`, so `@@outer_cvar` is reachable and `@@foo_cvar` (which lives on
|
|
2283
|
+
// `Foo`, not on any lexical ancestor) must not be offered.
|
|
2284
|
+
let mut context = GraphTest::new();
|
|
2285
|
+
|
|
2286
|
+
context.index_uri(
|
|
2287
|
+
"file:///foo.rb",
|
|
2288
|
+
"
|
|
2289
|
+
module Outer
|
|
2290
|
+
@@outer_cvar = 1
|
|
2291
|
+
|
|
2292
|
+
class Foo
|
|
2293
|
+
@@foo_cvar = 2
|
|
2294
|
+
def self.singleton_m; end
|
|
2295
|
+
end
|
|
2296
|
+
end
|
|
2297
|
+
",
|
|
2298
|
+
);
|
|
2299
|
+
context.resolve();
|
|
2300
|
+
|
|
2301
|
+
let outer_name_id = Name::new(StringId::from("Outer"), ParentScope::None, None).id();
|
|
2302
|
+
assert_declaration_completion_eq!(
|
|
2303
|
+
context,
|
|
2304
|
+
CompletionReceiver::Expression {
|
|
2305
|
+
self_decl_id: Some(DeclarationId::from("Outer::Foo::<Foo>")),
|
|
2306
|
+
nesting_name_id: outer_name_id,
|
|
2307
|
+
},
|
|
2308
|
+
[
|
|
2309
|
+
"Module",
|
|
2310
|
+
"Class",
|
|
2311
|
+
"Object",
|
|
2312
|
+
"BasicObject",
|
|
2313
|
+
"Kernel",
|
|
2314
|
+
"Outer",
|
|
2315
|
+
"Outer::Foo",
|
|
2316
|
+
"Outer#@@outer_cvar",
|
|
2317
|
+
"Outer::Foo::<Foo>#singleton_m()"
|
|
2318
|
+
]
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
#[test]
|
|
2323
|
+
fn expression_completion_follows_self_decl_alias() {
|
|
2324
|
+
let mut context = GraphTest::new();
|
|
2325
|
+
|
|
2326
|
+
context.index_uri(
|
|
2327
|
+
"file:///foo.rb",
|
|
2328
|
+
"
|
|
2329
|
+
module Outer
|
|
2330
|
+
class Original
|
|
2331
|
+
def original_m; end
|
|
2332
|
+
end
|
|
2333
|
+
|
|
2334
|
+
MyAlias = Original
|
|
2335
|
+
end
|
|
2336
|
+
",
|
|
2337
|
+
);
|
|
2338
|
+
context.resolve();
|
|
2339
|
+
|
|
2340
|
+
let name_id = Name::new(StringId::from("Outer"), ParentScope::None, None).id();
|
|
2341
|
+
// `self_decl_id` points to the alias `Outer::MyAlias`, which is a `ConstantAlias` rather than a `Namespace`.
|
|
2342
|
+
// The completion should still collect members from the aliased namespace (`Outer::Original`) instead of
|
|
2343
|
+
// returning an error, so callers do not have to unwrap aliases themselves.
|
|
2344
|
+
assert_declaration_completion_eq!(
|
|
2345
|
+
context,
|
|
2346
|
+
CompletionReceiver::Expression {
|
|
2347
|
+
self_decl_id: Some(DeclarationId::from("Outer::MyAlias")),
|
|
2348
|
+
nesting_name_id: name_id,
|
|
2349
|
+
},
|
|
2350
|
+
[
|
|
2351
|
+
"Outer::MyAlias",
|
|
2352
|
+
"Outer::Original",
|
|
2353
|
+
"Class",
|
|
2354
|
+
"Object",
|
|
2355
|
+
"BasicObject",
|
|
2356
|
+
"Outer",
|
|
2357
|
+
"Kernel",
|
|
2358
|
+
"Module",
|
|
2359
|
+
"Outer::Original#original_m()"
|
|
2360
|
+
]
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
#[test]
|
|
2365
|
+
fn expression_completion_in_method_definition_with_receiver_uses_lexical_scope_for_class_variables() {
|
|
2366
|
+
let mut context = GraphTest::new();
|
|
2367
|
+
|
|
2368
|
+
context.index_uri(
|
|
2369
|
+
"file:///foo.rb",
|
|
2370
|
+
"
|
|
2371
|
+
class Foo
|
|
2372
|
+
@@class_var = 1
|
|
2373
|
+
end
|
|
2374
|
+
|
|
2375
|
+
class Bar
|
|
2376
|
+
@@other_class_var = 2
|
|
2377
|
+
|
|
2378
|
+
def Foo.baz
|
|
2379
|
+
# completion here
|
|
2380
|
+
end
|
|
2381
|
+
end
|
|
2382
|
+
",
|
|
2383
|
+
);
|
|
2384
|
+
context.resolve();
|
|
2385
|
+
|
|
2386
|
+
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
2387
|
+
assert_declaration_completion_eq!(
|
|
2388
|
+
context,
|
|
2389
|
+
CompletionReceiver::Expression {
|
|
2390
|
+
self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
|
|
2391
|
+
nesting_name_id: name_id,
|
|
2392
|
+
},
|
|
2393
|
+
[
|
|
2394
|
+
"Module",
|
|
2395
|
+
"Class",
|
|
2396
|
+
"Object",
|
|
2397
|
+
"BasicObject",
|
|
2398
|
+
"Kernel",
|
|
2399
|
+
"Foo",
|
|
2400
|
+
"Bar",
|
|
2401
|
+
"Foo::<Foo>#baz()",
|
|
2402
|
+
"Bar#@@other_class_var"
|
|
2403
|
+
]
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
#[test]
|
|
2408
|
+
fn expression_completion_in_method_definition_with_receiver_uses_lexical_scope_for_constants() {
|
|
2409
|
+
let mut context = GraphTest::new();
|
|
2410
|
+
|
|
2411
|
+
context.index_uri(
|
|
2412
|
+
"file:///foo.rb",
|
|
2413
|
+
"
|
|
2414
|
+
class Foo
|
|
2415
|
+
CONST = 1
|
|
2416
|
+
end
|
|
2417
|
+
|
|
2418
|
+
class Bar
|
|
2419
|
+
OTHER_CONST = 2
|
|
2420
|
+
|
|
2421
|
+
def Foo.baz
|
|
2422
|
+
# completion here
|
|
2423
|
+
end
|
|
2424
|
+
end
|
|
2425
|
+
",
|
|
2426
|
+
);
|
|
2427
|
+
context.resolve();
|
|
2428
|
+
|
|
2429
|
+
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
2430
|
+
assert_declaration_completion_eq!(
|
|
2431
|
+
context,
|
|
2432
|
+
CompletionReceiver::Expression {
|
|
2433
|
+
self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
|
|
2434
|
+
nesting_name_id: name_id,
|
|
2435
|
+
},
|
|
2436
|
+
[
|
|
2437
|
+
"Bar::OTHER_CONST",
|
|
2438
|
+
"Module",
|
|
2439
|
+
"Class",
|
|
2440
|
+
"Object",
|
|
2441
|
+
"BasicObject",
|
|
2442
|
+
"Kernel",
|
|
2443
|
+
"Foo",
|
|
2444
|
+
"Bar",
|
|
2445
|
+
"Foo::<Foo>#baz()"
|
|
2446
|
+
]
|
|
2447
|
+
);
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
#[test]
|
|
2451
|
+
fn expression_completion_does_not_leak_constants_reachable_only_through_self_ancestors() {
|
|
2452
|
+
let mut context = GraphTest::new();
|
|
2453
|
+
|
|
2454
|
+
context.index_uri(
|
|
2455
|
+
"file:///foo.rb",
|
|
2456
|
+
"
|
|
2457
|
+
module Mixin
|
|
2458
|
+
MIXIN_CONST = 1
|
|
2459
|
+
end
|
|
2460
|
+
|
|
2461
|
+
class Foo
|
|
2462
|
+
extend Mixin
|
|
2463
|
+
end
|
|
2464
|
+
|
|
2465
|
+
class Bar
|
|
2466
|
+
def Foo.baz
|
|
2467
|
+
# completion here
|
|
2468
|
+
end
|
|
2469
|
+
end
|
|
2470
|
+
",
|
|
2471
|
+
);
|
|
2472
|
+
context.resolve();
|
|
2473
|
+
|
|
2474
|
+
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
2475
|
+
assert_declaration_completion_eq!(
|
|
2476
|
+
context,
|
|
2477
|
+
CompletionReceiver::Expression {
|
|
2478
|
+
self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
|
|
2479
|
+
nesting_name_id: name_id,
|
|
2480
|
+
},
|
|
2481
|
+
[
|
|
2482
|
+
"Class",
|
|
2483
|
+
"BasicObject",
|
|
2484
|
+
"Mixin",
|
|
2485
|
+
"Object",
|
|
2486
|
+
"Kernel",
|
|
2487
|
+
"Module",
|
|
2488
|
+
"Foo",
|
|
2489
|
+
"Bar",
|
|
2490
|
+
"Foo::<Foo>#baz()"
|
|
2491
|
+
]
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
#[test]
|
|
2496
|
+
fn expression_completion_at_top_level_includes_object_constants() {
|
|
2497
|
+
let mut context = GraphTest::new();
|
|
2498
|
+
|
|
2499
|
+
context.index_uri(
|
|
2500
|
+
"file:///foo.rb",
|
|
2501
|
+
"
|
|
2502
|
+
class Foo
|
|
2503
|
+
CONST = 1
|
|
2504
|
+
end
|
|
2505
|
+
|
|
2506
|
+
TOP_CONST = 2
|
|
2507
|
+
",
|
|
2508
|
+
);
|
|
2509
|
+
context.resolve();
|
|
2510
|
+
|
|
2511
|
+
let name_id = Name::new(StringId::from("Object"), ParentScope::None, None).id();
|
|
2512
|
+
assert_declaration_completion_eq!(
|
|
2513
|
+
context,
|
|
2514
|
+
CompletionReceiver::Expression {
|
|
2515
|
+
self_decl_id: None,
|
|
2516
|
+
nesting_name_id: name_id,
|
|
2517
|
+
},
|
|
2518
|
+
["Module", "Class", "Object", "BasicObject", "Kernel", "Foo", "TOP_CONST"]
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
#[test]
|
|
2523
|
+
fn expression_completion_in_module_body_falls_back_to_object_constants() {
|
|
2524
|
+
let mut context = GraphTest::new();
|
|
2525
|
+
|
|
2526
|
+
context.index_uri(
|
|
2527
|
+
"file:///foo.rb",
|
|
2528
|
+
"
|
|
2529
|
+
TOP_CONST = 1
|
|
2530
|
+
|
|
2531
|
+
module Mod
|
|
2532
|
+
MOD_CONST = 2
|
|
2533
|
+
end
|
|
2534
|
+
",
|
|
2535
|
+
);
|
|
2536
|
+
context.resolve();
|
|
2537
|
+
|
|
2538
|
+
let name_id = Name::new(StringId::from("Mod"), ParentScope::None, None).id();
|
|
2539
|
+
assert_declaration_completion_eq!(
|
|
2540
|
+
context,
|
|
2541
|
+
CompletionReceiver::Expression {
|
|
2542
|
+
self_decl_id: None,
|
|
2543
|
+
nesting_name_id: name_id,
|
|
2544
|
+
},
|
|
2545
|
+
[
|
|
2546
|
+
"Mod::MOD_CONST",
|
|
2547
|
+
"Module",
|
|
2548
|
+
"Class",
|
|
2549
|
+
"Object",
|
|
2550
|
+
"BasicObject",
|
|
2551
|
+
"Kernel",
|
|
2552
|
+
"TOP_CONST",
|
|
2553
|
+
"Mod"
|
|
2554
|
+
]
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
#[test]
|
|
2559
|
+
fn expression_completion_in_module_body_falls_back_to_object_ancestors() {
|
|
2560
|
+
let mut context = GraphTest::new();
|
|
2561
|
+
|
|
2562
|
+
context.index_uri(
|
|
2563
|
+
"file:///foo.rb",
|
|
2564
|
+
"
|
|
2565
|
+
module Kernel
|
|
2566
|
+
CONST = 1
|
|
2567
|
+
end
|
|
2568
|
+
|
|
2569
|
+
module Mod
|
|
2570
|
+
# completion here
|
|
2571
|
+
end
|
|
2572
|
+
",
|
|
2573
|
+
);
|
|
2574
|
+
context.resolve();
|
|
2575
|
+
|
|
2576
|
+
let name_id = Name::new(StringId::from("Mod"), ParentScope::None, None).id();
|
|
2577
|
+
assert_declaration_completion_eq!(
|
|
2578
|
+
context,
|
|
2579
|
+
CompletionReceiver::Expression {
|
|
2580
|
+
self_decl_id: None,
|
|
2581
|
+
nesting_name_id: name_id,
|
|
2582
|
+
},
|
|
2583
|
+
[
|
|
2584
|
+
"Class",
|
|
2585
|
+
"Object",
|
|
2586
|
+
"Kernel",
|
|
2587
|
+
"BasicObject",
|
|
2588
|
+
"Mod",
|
|
2589
|
+
"Module",
|
|
2590
|
+
"Kernel::CONST",
|
|
2591
|
+
]
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
#[test]
|
|
2596
|
+
fn expression_completion_at_top_level_offers_methods_from_object_chain() {
|
|
2597
|
+
let mut context = GraphTest::new();
|
|
2598
|
+
|
|
2599
|
+
context.index_uri(
|
|
2600
|
+
"file:///foo.rb",
|
|
2601
|
+
"
|
|
2602
|
+
def my_top_method; end
|
|
2603
|
+
|
|
2604
|
+
module Kernel
|
|
2605
|
+
def kernel_helper; end
|
|
2606
|
+
end
|
|
2607
|
+
",
|
|
2608
|
+
);
|
|
2609
|
+
context.resolve();
|
|
2610
|
+
|
|
2611
|
+
let name_id = Name::new(StringId::from("Object"), ParentScope::None, None).id();
|
|
2612
|
+
assert_declaration_completion_eq!(
|
|
2613
|
+
context,
|
|
2614
|
+
CompletionReceiver::Expression {
|
|
2615
|
+
self_decl_id: None,
|
|
2616
|
+
nesting_name_id: name_id,
|
|
2617
|
+
},
|
|
2618
|
+
[
|
|
2619
|
+
"Object",
|
|
2620
|
+
"Kernel",
|
|
2621
|
+
"BasicObject",
|
|
2622
|
+
"Module",
|
|
2623
|
+
"Class",
|
|
2624
|
+
"Object#my_top_method()",
|
|
2625
|
+
"Kernel#kernel_helper()"
|
|
2626
|
+
]
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
#[test]
|
|
2631
|
+
fn expression_completion_in_basic_object_subclass_excludes_object_constants() {
|
|
2632
|
+
let mut context = GraphTest::new();
|
|
2633
|
+
|
|
2634
|
+
context.index_uri(
|
|
2635
|
+
"file:///foo.rb",
|
|
2636
|
+
"
|
|
2637
|
+
TOP_CONST = 1
|
|
2638
|
+
|
|
2639
|
+
class Bar < BasicObject
|
|
2640
|
+
BAR_CONST = 2
|
|
2641
|
+
end
|
|
2642
|
+
",
|
|
2643
|
+
);
|
|
2644
|
+
context.resolve();
|
|
2645
|
+
|
|
2646
|
+
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
2647
|
+
assert_declaration_completion_eq!(
|
|
2648
|
+
context,
|
|
2649
|
+
CompletionReceiver::Expression {
|
|
2650
|
+
self_decl_id: None,
|
|
2651
|
+
nesting_name_id: name_id,
|
|
2652
|
+
},
|
|
2653
|
+
["Bar::BAR_CONST"]
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
#[test]
|
|
2658
|
+
fn expression_completion_class_variables_in_singleton_class_block_use_outer_lexical_scope() {
|
|
2659
|
+
let mut context = GraphTest::new();
|
|
2660
|
+
|
|
2661
|
+
context.index_uri(
|
|
2662
|
+
"file:///foo.rb",
|
|
2663
|
+
"
|
|
2664
|
+
class Foo
|
|
2665
|
+
@@foo_cvar = 1
|
|
2666
|
+
end
|
|
2667
|
+
|
|
2668
|
+
class Bar
|
|
2669
|
+
@@bar_cvar = 2
|
|
2670
|
+
|
|
2671
|
+
class << Foo
|
|
2672
|
+
# completion here
|
|
2673
|
+
end
|
|
2674
|
+
end
|
|
2675
|
+
",
|
|
2676
|
+
);
|
|
2677
|
+
context.resolve();
|
|
2678
|
+
|
|
2679
|
+
let bar_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
2680
|
+
let foo_ref_id = Name::new(StringId::from("Foo"), ParentScope::None, Some(bar_id)).id();
|
|
2681
|
+
let nesting_name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_ref_id), Some(bar_id)).id();
|
|
2682
|
+
|
|
2683
|
+
assert_declaration_completion_eq!(
|
|
2684
|
+
context,
|
|
2685
|
+
CompletionReceiver::Expression {
|
|
2686
|
+
self_decl_id: None,
|
|
2687
|
+
nesting_name_id,
|
|
2688
|
+
},
|
|
2689
|
+
["Bar#@@bar_cvar"]
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
#[test]
|
|
2694
|
+
fn expression_completion_in_def_self_method_inside_module_body() {
|
|
2695
|
+
let mut context = GraphTest::new();
|
|
2696
|
+
|
|
2697
|
+
context.index_uri(
|
|
2698
|
+
"file:///foo.rb",
|
|
2699
|
+
"
|
|
2700
|
+
module Mod
|
|
2701
|
+
MOD_CONST = 1
|
|
2702
|
+
|
|
2703
|
+
def self.helper
|
|
2704
|
+
# completion here
|
|
2705
|
+
end
|
|
2706
|
+
end
|
|
2707
|
+
",
|
|
2708
|
+
);
|
|
2709
|
+
context.resolve();
|
|
2710
|
+
|
|
2711
|
+
let name_id = Name::new(StringId::from("Mod"), ParentScope::None, None).id();
|
|
2712
|
+
assert_declaration_completion_eq!(
|
|
2713
|
+
context,
|
|
2714
|
+
CompletionReceiver::Expression {
|
|
2715
|
+
self_decl_id: Some(DeclarationId::from("Mod::<Mod>")),
|
|
2716
|
+
nesting_name_id: name_id,
|
|
2717
|
+
},
|
|
2718
|
+
[
|
|
2719
|
+
"Mod::MOD_CONST",
|
|
2720
|
+
"Module",
|
|
2721
|
+
"Class",
|
|
2722
|
+
"Object",
|
|
2723
|
+
"BasicObject",
|
|
2724
|
+
"Kernel",
|
|
2725
|
+
"Mod",
|
|
2726
|
+
"Mod::<Mod>#helper()"
|
|
2727
|
+
]
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
#[test]
|
|
2732
|
+
fn expression_completion_in_singleton_class_block_at_top_level() {
|
|
2733
|
+
let mut context = GraphTest::new();
|
|
2734
|
+
|
|
2735
|
+
context.index_uri(
|
|
2736
|
+
"file:///foo.rb",
|
|
2737
|
+
"
|
|
2738
|
+
class Foo
|
|
2739
|
+
@@foo_cvar = 1
|
|
2740
|
+
end
|
|
2741
|
+
|
|
2742
|
+
class << Foo
|
|
2743
|
+
# completion here
|
|
2744
|
+
end
|
|
2745
|
+
",
|
|
2746
|
+
);
|
|
2747
|
+
context.resolve();
|
|
2748
|
+
|
|
2749
|
+
let foo_ref_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
2750
|
+
let nesting_name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_ref_id), None).id();
|
|
2751
|
+
|
|
2752
|
+
assert_declaration_completion_eq!(
|
|
2753
|
+
context,
|
|
2754
|
+
CompletionReceiver::Expression {
|
|
2755
|
+
self_decl_id: None,
|
|
2756
|
+
nesting_name_id,
|
|
2757
|
+
},
|
|
2758
|
+
["Module", "Class", "Object", "BasicObject", "Kernel", "Foo"]
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
#[test]
|
|
2763
|
+
fn expression_completion_errors_when_self_decl_id_does_not_exist() {
|
|
2764
|
+
let mut context = GraphTest::new();
|
|
2765
|
+
|
|
2766
|
+
context.index_uri(
|
|
2767
|
+
"file:///foo.rb",
|
|
2768
|
+
"
|
|
2769
|
+
class Foo
|
|
2770
|
+
end
|
|
2771
|
+
",
|
|
2772
|
+
);
|
|
2773
|
+
context.resolve();
|
|
2774
|
+
|
|
2775
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
2776
|
+
let result = completion_candidates(
|
|
2777
|
+
context.graph(),
|
|
2778
|
+
CompletionContext::new(CompletionReceiver::Expression {
|
|
2779
|
+
self_decl_id: Some(DeclarationId::from("Nonexistent")),
|
|
2780
|
+
nesting_name_id: name_id,
|
|
2781
|
+
}),
|
|
2782
|
+
);
|
|
2783
|
+
|
|
2784
|
+
assert!(result.is_err(), "missing self_decl_id should surface as an error");
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
#[test]
|
|
2788
|
+
fn expression_completion_errors_when_self_decl_id_is_not_a_namespace() {
|
|
2789
|
+
let mut context = GraphTest::new();
|
|
2790
|
+
|
|
2791
|
+
context.index_uri(
|
|
2792
|
+
"file:///foo.rb",
|
|
2793
|
+
"
|
|
2794
|
+
class Foo
|
|
2795
|
+
CONST = 1
|
|
2796
|
+
end
|
|
2797
|
+
",
|
|
2798
|
+
);
|
|
2799
|
+
context.resolve();
|
|
2800
|
+
|
|
2801
|
+
let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
2802
|
+
let result = completion_candidates(
|
|
2803
|
+
context.graph(),
|
|
2804
|
+
CompletionContext::new(CompletionReceiver::Expression {
|
|
2805
|
+
// CONST resolves to a `Constant`, not a `Namespace` and not an alias.
|
|
2806
|
+
self_decl_id: Some(DeclarationId::from("Foo::CONST")),
|
|
2807
|
+
nesting_name_id: name_id,
|
|
2808
|
+
}),
|
|
2809
|
+
);
|
|
2810
|
+
|
|
2811
|
+
assert!(result.is_err(), "non-namespace self_decl_id should surface as an error");
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
#[test]
|
|
2815
|
+
fn expression_completion_follows_chained_self_decl_alias() {
|
|
2816
|
+
let mut context = GraphTest::new();
|
|
2817
|
+
|
|
2818
|
+
context.index_uri(
|
|
2819
|
+
"file:///foo.rb",
|
|
2820
|
+
"
|
|
2821
|
+
module Outer
|
|
2822
|
+
class Original
|
|
2823
|
+
def original_m; end
|
|
2824
|
+
end
|
|
2825
|
+
|
|
2826
|
+
FirstAlias = Original
|
|
2827
|
+
SecondAlias = FirstAlias
|
|
2828
|
+
end
|
|
2829
|
+
",
|
|
2830
|
+
);
|
|
2831
|
+
context.resolve();
|
|
2832
|
+
|
|
2833
|
+
let name_id = Name::new(StringId::from("Outer"), ParentScope::None, None).id();
|
|
2834
|
+
assert_declaration_completion_eq!(
|
|
2835
|
+
context,
|
|
2836
|
+
CompletionReceiver::Expression {
|
|
2837
|
+
self_decl_id: Some(DeclarationId::from("Outer::SecondAlias")),
|
|
2838
|
+
nesting_name_id: name_id,
|
|
2839
|
+
},
|
|
2840
|
+
[
|
|
2841
|
+
"Outer::FirstAlias",
|
|
2842
|
+
"Outer::SecondAlias",
|
|
2843
|
+
"Outer::Original",
|
|
2844
|
+
"Module",
|
|
2845
|
+
"Class",
|
|
2846
|
+
"Object",
|
|
2847
|
+
"BasicObject",
|
|
2848
|
+
"Outer",
|
|
2849
|
+
"Kernel",
|
|
2850
|
+
"Outer::Original#original_m()"
|
|
2851
|
+
]
|
|
2852
|
+
);
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
#[test]
|
|
2856
|
+
fn expression_completion_includes_private_singleton_method_when_self_matches_owner() {
|
|
2857
|
+
let mut context = GraphTest::new();
|
|
2858
|
+
|
|
2859
|
+
context.index_uri(
|
|
2860
|
+
"file:///foo.rb",
|
|
2861
|
+
"
|
|
2862
|
+
class Foo
|
|
2863
|
+
def self.bar; end
|
|
2864
|
+
private_class_method :bar
|
|
2865
|
+
end
|
|
2866
|
+
|
|
2867
|
+
class Bar
|
|
2868
|
+
def Foo.baz
|
|
2869
|
+
# completion here
|
|
2870
|
+
end
|
|
2871
|
+
end
|
|
2872
|
+
",
|
|
2873
|
+
);
|
|
2874
|
+
context.resolve();
|
|
2875
|
+
|
|
2876
|
+
let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
2877
|
+
assert_declaration_completion_eq!(
|
|
2878
|
+
context,
|
|
2879
|
+
CompletionReceiver::Expression {
|
|
2880
|
+
self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
|
|
2881
|
+
nesting_name_id: name_id,
|
|
2882
|
+
},
|
|
2883
|
+
[
|
|
2884
|
+
"Module",
|
|
2885
|
+
"Class",
|
|
2886
|
+
"Object",
|
|
2887
|
+
"BasicObject",
|
|
2888
|
+
"Kernel",
|
|
2889
|
+
"Foo",
|
|
2890
|
+
"Bar",
|
|
2891
|
+
"Foo::<Foo>#bar()",
|
|
2892
|
+
"Foo::<Foo>#baz()"
|
|
2893
|
+
]
|
|
2894
|
+
);
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
#[test]
|
|
2898
|
+
fn method_call_completion_excludes_private_method_for_external_call() {
|
|
2899
|
+
let mut context = GraphTest::new();
|
|
2900
|
+
|
|
2901
|
+
context.index_uri(
|
|
2902
|
+
"file:///foo.rb",
|
|
2903
|
+
"
|
|
2904
|
+
class Foo
|
|
2905
|
+
private
|
|
2906
|
+
|
|
2907
|
+
def bar; end
|
|
2908
|
+
end
|
|
2909
|
+
",
|
|
2910
|
+
);
|
|
2911
|
+
context.resolve();
|
|
2912
|
+
|
|
2913
|
+
assert_completion_eq!(
|
|
2914
|
+
context,
|
|
2915
|
+
CompletionReceiver::MethodCall {
|
|
2916
|
+
self_decl_id: None,
|
|
2917
|
+
receiver_decl_id: DeclarationId::from("Foo")
|
|
2918
|
+
},
|
|
2919
|
+
[] as [&str; 0]
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
#[test]
|
|
2924
|
+
fn expression_completion_includes_private_instance_method_inside_self_ancestor_chain() {
|
|
2925
|
+
let mut context = GraphTest::new();
|
|
2926
|
+
|
|
2927
|
+
context.index_uri(
|
|
2928
|
+
"file:///foo.rb",
|
|
2929
|
+
"
|
|
2930
|
+
class Foo
|
|
2931
|
+
def baz
|
|
2932
|
+
# completion here
|
|
2933
|
+
end
|
|
2934
|
+
|
|
2935
|
+
private
|
|
2936
|
+
|
|
2937
|
+
def bar; end
|
|
2938
|
+
end
|
|
2939
|
+
|
|
2940
|
+
class Bar < Foo
|
|
2941
|
+
def qux
|
|
2942
|
+
# completion here
|
|
2943
|
+
end
|
|
2944
|
+
end
|
|
2945
|
+
",
|
|
2946
|
+
);
|
|
2947
|
+
context.resolve();
|
|
2948
|
+
|
|
2949
|
+
let foo_name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
2950
|
+
assert_declaration_completion_eq!(
|
|
2951
|
+
context,
|
|
2952
|
+
CompletionReceiver::Expression {
|
|
2953
|
+
self_decl_id: None,
|
|
2954
|
+
nesting_name_id: foo_name_id,
|
|
2955
|
+
},
|
|
2956
|
+
[
|
|
2957
|
+
"Module",
|
|
2958
|
+
"Class",
|
|
2959
|
+
"Object",
|
|
2960
|
+
"BasicObject",
|
|
2961
|
+
"Kernel",
|
|
2962
|
+
"Foo",
|
|
2963
|
+
"Bar",
|
|
2964
|
+
"Foo#baz()",
|
|
2965
|
+
"Foo#bar()"
|
|
2966
|
+
]
|
|
2967
|
+
);
|
|
2968
|
+
|
|
2969
|
+
let bar_name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
2970
|
+
assert_declaration_completion_eq!(
|
|
2971
|
+
context,
|
|
2972
|
+
CompletionReceiver::Expression {
|
|
2973
|
+
self_decl_id: None,
|
|
2974
|
+
nesting_name_id: bar_name_id,
|
|
2975
|
+
},
|
|
2976
|
+
[
|
|
2977
|
+
"Module",
|
|
2978
|
+
"Class",
|
|
2979
|
+
"Object",
|
|
2980
|
+
"BasicObject",
|
|
2981
|
+
"Kernel",
|
|
2982
|
+
"Foo",
|
|
2983
|
+
"Bar",
|
|
2984
|
+
"Foo#baz()",
|
|
2985
|
+
"Foo#bar()",
|
|
2986
|
+
"Bar#qux()"
|
|
2987
|
+
]
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
#[test]
|
|
2992
|
+
fn method_call_completion_includes_protected_method_when_caller_shares_ancestor_with_receiver() {
|
|
2993
|
+
let mut context = GraphTest::new();
|
|
2994
|
+
|
|
2995
|
+
context.index_uri(
|
|
2996
|
+
"file:///foo.rb",
|
|
2997
|
+
"
|
|
2998
|
+
class Account
|
|
2999
|
+
protected
|
|
3000
|
+
|
|
3001
|
+
def balance; end
|
|
3002
|
+
end
|
|
3003
|
+
|
|
3004
|
+
class Savings < Account
|
|
3005
|
+
end
|
|
3006
|
+
|
|
3007
|
+
class Checking < Account
|
|
3008
|
+
end
|
|
3009
|
+
",
|
|
3010
|
+
);
|
|
3011
|
+
context.resolve();
|
|
3012
|
+
|
|
3013
|
+
// Caller's self is `Account` (or any descendant). Both caller and receiver descend from
|
|
3014
|
+
// the defining class, satisfying MRI's `caller.class <= defined_class && recv.class <= defined_class`.
|
|
3015
|
+
assert_completion_eq!(
|
|
3016
|
+
context,
|
|
3017
|
+
CompletionReceiver::MethodCall {
|
|
3018
|
+
self_decl_id: Some(DeclarationId::from("Account")),
|
|
3019
|
+
receiver_decl_id: DeclarationId::from("Savings"),
|
|
3020
|
+
},
|
|
3021
|
+
["Account#balance()"]
|
|
3022
|
+
);
|
|
3023
|
+
|
|
3024
|
+
assert_completion_eq!(
|
|
3025
|
+
context,
|
|
3026
|
+
CompletionReceiver::MethodCall {
|
|
3027
|
+
self_decl_id: Some(DeclarationId::from("Savings")),
|
|
3028
|
+
receiver_decl_id: DeclarationId::from("Checking"),
|
|
3029
|
+
},
|
|
3030
|
+
["Account#balance()"]
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
#[test]
|
|
3035
|
+
fn method_call_completion_excludes_protected_method_when_caller_does_not_share_ancestor() {
|
|
3036
|
+
let mut context = GraphTest::new();
|
|
3037
|
+
context.index_uri(
|
|
3038
|
+
"file:///foo.rb",
|
|
3039
|
+
"
|
|
3040
|
+
class Account
|
|
3041
|
+
protected
|
|
3042
|
+
|
|
3043
|
+
def balance; end
|
|
3044
|
+
end
|
|
3045
|
+
|
|
3046
|
+
class Unrelated
|
|
3047
|
+
end
|
|
3048
|
+
",
|
|
3049
|
+
);
|
|
3050
|
+
context.resolve();
|
|
3051
|
+
|
|
3052
|
+
// Receiver is `Account`; caller's self is `Unrelated`. Caller is not a descendant of the
|
|
3053
|
+
// defining class, so the protected check fails and `balance` is hidden.
|
|
3054
|
+
assert_completion_eq!(
|
|
3055
|
+
context,
|
|
3056
|
+
CompletionReceiver::MethodCall {
|
|
3057
|
+
self_decl_id: Some(DeclarationId::from("Unrelated")),
|
|
3058
|
+
receiver_decl_id: DeclarationId::from("Account"),
|
|
3059
|
+
},
|
|
3060
|
+
[] as [&str; 0]
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
#[test]
|
|
3065
|
+
fn method_call_completion_includes_private_method_when_receiver_is_self() {
|
|
3066
|
+
let mut context = GraphTest::new();
|
|
3067
|
+
context.index_uri(
|
|
3068
|
+
"file:///foo.rb",
|
|
3069
|
+
"
|
|
3070
|
+
class Foo
|
|
3071
|
+
def public_method; end
|
|
3072
|
+
|
|
3073
|
+
private
|
|
3074
|
+
|
|
3075
|
+
def private_method; end
|
|
3076
|
+
end
|
|
3077
|
+
",
|
|
3078
|
+
);
|
|
3079
|
+
context.resolve();
|
|
3080
|
+
|
|
3081
|
+
// Caller's self is `Foo`, matching the receiver — `private_method` becomes visible
|
|
3082
|
+
// (Ruby 3.0+ allows `self.foo` for private methods, and our completion treats receiver-equals-self as the
|
|
3083
|
+
// implicit-receiver case).
|
|
3084
|
+
assert_completion_eq!(
|
|
3085
|
+
context,
|
|
3086
|
+
CompletionReceiver::MethodCall {
|
|
3087
|
+
self_decl_id: Some(DeclarationId::from("Foo")),
|
|
3088
|
+
receiver_decl_id: DeclarationId::from("Foo"),
|
|
3089
|
+
},
|
|
3090
|
+
["Foo#public_method()", "Foo#private_method()"]
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
#[test]
|
|
3095
|
+
fn method_call_completion_includes_protected_method_through_included_module() {
|
|
3096
|
+
let mut context = GraphTest::new();
|
|
3097
|
+
context.index_uri(
|
|
3098
|
+
"file:///foo.rb",
|
|
3099
|
+
"
|
|
3100
|
+
module Sharable
|
|
3101
|
+
protected
|
|
3102
|
+
|
|
3103
|
+
def shared_secret; end
|
|
3104
|
+
end
|
|
3105
|
+
|
|
3106
|
+
class A
|
|
3107
|
+
include Sharable
|
|
3108
|
+
end
|
|
3109
|
+
|
|
3110
|
+
class B
|
|
3111
|
+
include Sharable
|
|
3112
|
+
end
|
|
3113
|
+
",
|
|
3114
|
+
);
|
|
3115
|
+
context.resolve();
|
|
3116
|
+
|
|
3117
|
+
assert_completion_eq!(
|
|
3118
|
+
context,
|
|
3119
|
+
CompletionReceiver::MethodCall {
|
|
3120
|
+
self_decl_id: Some(DeclarationId::from("A")),
|
|
3121
|
+
receiver_decl_id: DeclarationId::from("B"),
|
|
3122
|
+
},
|
|
3123
|
+
["Sharable#shared_secret()"]
|
|
3124
|
+
);
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
#[test]
|
|
3128
|
+
fn method_call_completion_excludes_protected_when_caller_class_is_not_descendant_of_defined_class() {
|
|
3129
|
+
let mut context = GraphTest::new();
|
|
3130
|
+
context.index_uri(
|
|
3131
|
+
"file:///foo.rb",
|
|
3132
|
+
"
|
|
3133
|
+
class Animal
|
|
3134
|
+
end
|
|
3135
|
+
|
|
3136
|
+
class Dog < Animal
|
|
3137
|
+
protected
|
|
3138
|
+
|
|
3139
|
+
def secret_trick; end
|
|
3140
|
+
end
|
|
3141
|
+
",
|
|
3142
|
+
);
|
|
3143
|
+
context.resolve();
|
|
3144
|
+
|
|
3145
|
+
assert_completion_eq!(
|
|
3146
|
+
context,
|
|
3147
|
+
CompletionReceiver::MethodCall {
|
|
3148
|
+
self_decl_id: Some(DeclarationId::from("Animal")),
|
|
3149
|
+
receiver_decl_id: DeclarationId::from("Dog"),
|
|
3150
|
+
},
|
|
3151
|
+
[] as [&str; 0]
|
|
3152
|
+
);
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
#[test]
|
|
3156
|
+
fn method_call_completion_excludes_visibility_restricted_methods_at_top_level() {
|
|
3157
|
+
let mut context = GraphTest::new();
|
|
3158
|
+
context.index_uri(
|
|
3159
|
+
"file:///foo.rb",
|
|
3160
|
+
"
|
|
3161
|
+
class Foo
|
|
3162
|
+
def pub_inst; end
|
|
3163
|
+
|
|
3164
|
+
protected
|
|
3165
|
+
|
|
3166
|
+
def prot_inst; end
|
|
3167
|
+
|
|
3168
|
+
private
|
|
3169
|
+
|
|
3170
|
+
def priv_inst; end
|
|
3171
|
+
end
|
|
3172
|
+
",
|
|
3173
|
+
);
|
|
3174
|
+
context.resolve();
|
|
3175
|
+
|
|
3176
|
+
assert_completion_eq!(
|
|
3177
|
+
context,
|
|
3178
|
+
CompletionReceiver::MethodCall {
|
|
3179
|
+
self_decl_id: None,
|
|
3180
|
+
receiver_decl_id: DeclarationId::from("Foo"),
|
|
3181
|
+
},
|
|
3182
|
+
["Foo#pub_inst()"]
|
|
3183
|
+
);
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
#[test]
|
|
3187
|
+
fn method_call_completion_hides_method_when_subclass_overrides_with_stricter_visibility() {
|
|
3188
|
+
let mut context = GraphTest::new();
|
|
3189
|
+
context.index_uri(
|
|
3190
|
+
"file:///foo.rb",
|
|
3191
|
+
"
|
|
3192
|
+
class Parent
|
|
3193
|
+
def foo; end
|
|
3194
|
+
end
|
|
3195
|
+
|
|
3196
|
+
class Child < Parent
|
|
3197
|
+
private
|
|
3198
|
+
|
|
3199
|
+
def foo; end
|
|
3200
|
+
end
|
|
3201
|
+
",
|
|
3202
|
+
);
|
|
3203
|
+
context.resolve();
|
|
3204
|
+
|
|
3205
|
+
assert_completion_eq!(
|
|
3206
|
+
context,
|
|
3207
|
+
CompletionReceiver::MethodCall {
|
|
3208
|
+
self_decl_id: None,
|
|
3209
|
+
receiver_decl_id: DeclarationId::from("Child"),
|
|
3210
|
+
},
|
|
3211
|
+
[] as [&str; 0]
|
|
3212
|
+
);
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
#[test]
|
|
3216
|
+
fn namespace_access_completion_excludes_private_constant() {
|
|
3217
|
+
let mut context = GraphTest::new();
|
|
3218
|
+
context.index_uri(
|
|
3219
|
+
"file:///foo.rb",
|
|
3220
|
+
"
|
|
3221
|
+
class Foo
|
|
3222
|
+
PUB = 1
|
|
3223
|
+
PRIV = 2
|
|
3224
|
+
private_constant :PRIV
|
|
3225
|
+
end
|
|
3226
|
+
",
|
|
3227
|
+
);
|
|
3228
|
+
context.resolve();
|
|
3229
|
+
|
|
3230
|
+
assert_completion_eq!(
|
|
3231
|
+
context,
|
|
3232
|
+
CompletionReceiver::NamespaceAccess {
|
|
3233
|
+
self_decl_id: None,
|
|
3234
|
+
namespace_decl_id: DeclarationId::from("Foo"),
|
|
3235
|
+
},
|
|
3236
|
+
["Foo::PUB"]
|
|
3237
|
+
);
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
#[test]
|
|
3241
|
+
fn namespace_access_completion_excludes_inherited_private_constant() {
|
|
3242
|
+
let mut context = GraphTest::new();
|
|
3243
|
+
context.index_uri(
|
|
3244
|
+
"file:///foo.rb",
|
|
3245
|
+
"
|
|
3246
|
+
class Parent
|
|
3247
|
+
SECRET = 1
|
|
3248
|
+
private_constant :SECRET
|
|
3249
|
+
end
|
|
3250
|
+
|
|
3251
|
+
class Child < Parent
|
|
3252
|
+
end
|
|
3253
|
+
",
|
|
3254
|
+
);
|
|
3255
|
+
context.resolve();
|
|
3256
|
+
|
|
3257
|
+
assert_completion_eq!(
|
|
3258
|
+
context,
|
|
3259
|
+
CompletionReceiver::NamespaceAccess {
|
|
3260
|
+
self_decl_id: None,
|
|
3261
|
+
namespace_decl_id: DeclarationId::from("Child"),
|
|
3262
|
+
},
|
|
3263
|
+
[] as [&str; 0]
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
#[test]
|
|
3268
|
+
fn expression_completion_includes_private_constant_within_lexical_scope() {
|
|
3269
|
+
let mut context = GraphTest::new();
|
|
3270
|
+
context.index_uri(
|
|
3271
|
+
"file:///foo.rb",
|
|
3272
|
+
"
|
|
3273
|
+
class Foo
|
|
3274
|
+
SECRET = 1
|
|
3275
|
+
private_constant :SECRET
|
|
3276
|
+
|
|
3277
|
+
def use_it
|
|
3278
|
+
# completion here
|
|
3279
|
+
end
|
|
3280
|
+
end
|
|
3281
|
+
",
|
|
3282
|
+
);
|
|
3283
|
+
context.resolve();
|
|
3284
|
+
|
|
3285
|
+
let foo_name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
|
|
3286
|
+
assert_declaration_completion_eq!(
|
|
3287
|
+
context,
|
|
3288
|
+
CompletionReceiver::Expression {
|
|
3289
|
+
self_decl_id: None,
|
|
3290
|
+
nesting_name_id: foo_name_id,
|
|
3291
|
+
},
|
|
3292
|
+
[
|
|
3293
|
+
"Module",
|
|
3294
|
+
"Class",
|
|
3295
|
+
"Object",
|
|
3296
|
+
"BasicObject",
|
|
3297
|
+
"Kernel",
|
|
3298
|
+
"Foo",
|
|
3299
|
+
"Foo::SECRET",
|
|
3300
|
+
"Foo#use_it()"
|
|
3301
|
+
]
|
|
3302
|
+
);
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
/// Returns the smallest `MethodAlias` `DefinitionId` for the declaration named `alias_decl_fqn`
|
|
3306
|
+
/// (e.g., `"Foo#aliased()"`). Picking the smallest mirrors `follow_method_alias`'s own
|
|
3307
|
+
/// determinism rule for tests where multiple aliases share a declaration (e.g. cross-file fixtures).
|
|
3308
|
+
fn alias_def_id(context: &GraphTest, alias_decl_fqn: &str) -> DefinitionId {
|
|
3309
|
+
let decl = context
|
|
3310
|
+
.graph()
|
|
3311
|
+
.declarations()
|
|
3312
|
+
.get(&DeclarationId::from(alias_decl_fqn))
|
|
3313
|
+
.unwrap_or_else(|| panic!("expected declaration {alias_decl_fqn}"));
|
|
3314
|
+
|
|
3315
|
+
decl.definitions()
|
|
3316
|
+
.iter()
|
|
3317
|
+
.copied()
|
|
3318
|
+
.filter(|def_id| {
|
|
3319
|
+
matches!(
|
|
3320
|
+
context.graph().definitions().get(def_id),
|
|
3321
|
+
Some(Definition::MethodAlias(_)),
|
|
3322
|
+
)
|
|
3323
|
+
})
|
|
3324
|
+
.min()
|
|
3325
|
+
.unwrap_or_else(|| panic!("declaration {alias_decl_fqn} has no MethodAlias definition"))
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
/// Asserts that the alias declared as `$alias_fqn` follows to the declaration `$target_fqn`.
|
|
3329
|
+
macro_rules! assert_alias_target {
|
|
3330
|
+
($context:expr, $alias_fqn:expr, $target_fqn:expr $(,)?) => {{
|
|
3331
|
+
let context = $context;
|
|
3332
|
+
assert_eq!(
|
|
3333
|
+
follow_method_alias(context.graph(), alias_def_id(context, $alias_fqn)),
|
|
3334
|
+
Ok(DeclarationId::from($target_fqn)),
|
|
3335
|
+
);
|
|
3336
|
+
}};
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
#[test]
|
|
3340
|
+
fn follow_method_alias_to_local_method() {
|
|
3341
|
+
let mut context = GraphTest::new();
|
|
3342
|
+
context.index_uri(
|
|
3343
|
+
"file:///foo.rb",
|
|
3344
|
+
r"
|
|
3345
|
+
class Foo
|
|
3346
|
+
def original; end
|
|
3347
|
+
alias aliased original
|
|
3348
|
+
end
|
|
3349
|
+
",
|
|
3350
|
+
);
|
|
3351
|
+
context.resolve();
|
|
3352
|
+
|
|
3353
|
+
assert_alias_target!(&context, "Foo#aliased()", "Foo#original()");
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
#[test]
|
|
3357
|
+
fn follow_method_alias_through_chain_of_aliases() {
|
|
3358
|
+
let mut context = GraphTest::new();
|
|
3359
|
+
context.index_uri(
|
|
3360
|
+
"file:///foo.rb",
|
|
3361
|
+
r"
|
|
3362
|
+
class Foo
|
|
3363
|
+
def real; end
|
|
3364
|
+
alias mid real
|
|
3365
|
+
alias outer mid
|
|
3366
|
+
end
|
|
3367
|
+
",
|
|
3368
|
+
);
|
|
3369
|
+
context.resolve();
|
|
3370
|
+
|
|
3371
|
+
assert_alias_target!(&context, "Foo#outer()", "Foo#real()");
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
#[test]
|
|
3375
|
+
fn follow_method_alias_detects_two_step_cycle() {
|
|
3376
|
+
let mut context = GraphTest::new();
|
|
3377
|
+
context.index_uri(
|
|
3378
|
+
"file:///foo.rb",
|
|
3379
|
+
r"
|
|
3380
|
+
class Foo
|
|
3381
|
+
alias a b
|
|
3382
|
+
alias b a
|
|
3383
|
+
end
|
|
3384
|
+
",
|
|
3385
|
+
);
|
|
3386
|
+
context.resolve();
|
|
3387
|
+
|
|
3388
|
+
assert_eq!(
|
|
3389
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo#a()")),
|
|
3390
|
+
Err(AliasResolutionError::Cycle),
|
|
3391
|
+
);
|
|
3392
|
+
assert_eq!(
|
|
3393
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo#b()")),
|
|
3394
|
+
Err(AliasResolutionError::Cycle),
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
#[test]
|
|
3399
|
+
fn follow_method_alias_detects_multi_step_cycle() {
|
|
3400
|
+
let mut context = GraphTest::new();
|
|
3401
|
+
context.index_uri(
|
|
3402
|
+
"file:///foo.rb",
|
|
3403
|
+
r"
|
|
3404
|
+
class Foo
|
|
3405
|
+
alias a b
|
|
3406
|
+
alias b c
|
|
3407
|
+
alias c a
|
|
3408
|
+
end
|
|
3409
|
+
",
|
|
3410
|
+
);
|
|
3411
|
+
context.resolve();
|
|
3412
|
+
|
|
3413
|
+
for alias_fqn in ["Foo#a()", "Foo#b()", "Foo#c()"] {
|
|
3414
|
+
assert_eq!(
|
|
3415
|
+
follow_method_alias(context.graph(), alias_def_id(&context, alias_fqn)),
|
|
3416
|
+
Err(AliasResolutionError::Cycle),
|
|
3417
|
+
"expected {alias_fqn} to be detected as part of the cycle",
|
|
3418
|
+
);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
#[test]
|
|
3423
|
+
fn follow_method_alias_detects_self_cycle() {
|
|
3424
|
+
let mut context = GraphTest::new();
|
|
3425
|
+
context.index_uri(
|
|
3426
|
+
"file:///foo.rb",
|
|
3427
|
+
r"
|
|
3428
|
+
class Foo
|
|
3429
|
+
alias foo foo
|
|
3430
|
+
end
|
|
3431
|
+
",
|
|
3432
|
+
);
|
|
3433
|
+
context.resolve();
|
|
3434
|
+
|
|
3435
|
+
assert_eq!(
|
|
3436
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo#foo()")),
|
|
3437
|
+
Err(AliasResolutionError::Cycle),
|
|
3438
|
+
);
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
#[test]
|
|
3442
|
+
fn follow_method_alias_to_inherited_method() {
|
|
3443
|
+
let mut context = GraphTest::new();
|
|
3444
|
+
context.index_uri(
|
|
3445
|
+
"file:///foo.rb",
|
|
3446
|
+
r"
|
|
3447
|
+
class Parent
|
|
3448
|
+
def inherited_m; end
|
|
3449
|
+
end
|
|
3450
|
+
|
|
3451
|
+
class Child < Parent
|
|
3452
|
+
alias aliased inherited_m
|
|
3453
|
+
end
|
|
3454
|
+
",
|
|
3455
|
+
);
|
|
3456
|
+
context.resolve();
|
|
3457
|
+
|
|
3458
|
+
assert_alias_target!(&context, "Child#aliased()", "Parent#inherited_m()");
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
#[test]
|
|
3462
|
+
fn follow_method_alias_with_constant_receiver() {
|
|
3463
|
+
let mut context = GraphTest::new();
|
|
3464
|
+
context.index_uri(
|
|
3465
|
+
"file:///foo.rb",
|
|
3466
|
+
r"
|
|
3467
|
+
class Bar
|
|
3468
|
+
def to_s; end
|
|
3469
|
+
end
|
|
3470
|
+
|
|
3471
|
+
class Foo
|
|
3472
|
+
Bar.alias_method(:new_to_s, :to_s)
|
|
3473
|
+
end
|
|
3474
|
+
",
|
|
3475
|
+
);
|
|
3476
|
+
context.resolve();
|
|
3477
|
+
|
|
3478
|
+
assert_alias_target!(&context, "Bar#new_to_s()", "Bar#to_s()");
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
#[test]
|
|
3482
|
+
fn follow_method_alias_in_singleton_class_body() {
|
|
3483
|
+
let mut context = GraphTest::new();
|
|
3484
|
+
context.index_uri(
|
|
3485
|
+
"file:///foo.rb",
|
|
3486
|
+
r"
|
|
3487
|
+
class Foo
|
|
3488
|
+
def self.find; end
|
|
3489
|
+
|
|
3490
|
+
class << self
|
|
3491
|
+
alias_method :find_old, :find
|
|
3492
|
+
end
|
|
3493
|
+
end
|
|
3494
|
+
",
|
|
3495
|
+
);
|
|
3496
|
+
context.resolve();
|
|
3497
|
+
|
|
3498
|
+
assert_alias_target!(&context, "Foo::<Foo>#find_old()", "Foo::<Foo>#find()");
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
#[test]
|
|
3502
|
+
fn follow_method_alias_in_singleton_class_body_misses_instance_method() {
|
|
3503
|
+
let mut context = GraphTest::new();
|
|
3504
|
+
context.index_uri(
|
|
3505
|
+
"file:///foo.rb",
|
|
3506
|
+
r"
|
|
3507
|
+
class Foo
|
|
3508
|
+
def regular; end
|
|
3509
|
+
|
|
3510
|
+
class << self
|
|
3511
|
+
alias_method :other, :regular
|
|
3512
|
+
end
|
|
3513
|
+
end
|
|
3514
|
+
",
|
|
3515
|
+
);
|
|
3516
|
+
context.resolve();
|
|
3517
|
+
|
|
3518
|
+
assert_eq!(
|
|
3519
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo::<Foo>#other()")),
|
|
3520
|
+
Err(AliasResolutionError::TargetNotFound),
|
|
3521
|
+
);
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
#[test]
|
|
3525
|
+
fn follow_method_alias_to_attr_reader() {
|
|
3526
|
+
let mut context = GraphTest::new();
|
|
3527
|
+
context.index_uri(
|
|
3528
|
+
"file:///foo.rb",
|
|
3529
|
+
r"
|
|
3530
|
+
class Foo
|
|
3531
|
+
attr_reader :name
|
|
3532
|
+
alias display name
|
|
3533
|
+
end
|
|
3534
|
+
",
|
|
3535
|
+
);
|
|
3536
|
+
context.resolve();
|
|
3537
|
+
|
|
3538
|
+
assert_alias_target!(&context, "Foo#display()", "Foo#name()");
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
#[test]
|
|
3542
|
+
fn follow_method_alias_to_attr_accessor() {
|
|
3543
|
+
let mut context = GraphTest::new();
|
|
3544
|
+
context.index_uri(
|
|
3545
|
+
"file:///foo.rb",
|
|
3546
|
+
r"
|
|
3547
|
+
class Foo
|
|
3548
|
+
attr_accessor :age
|
|
3549
|
+
alias years age
|
|
3550
|
+
end
|
|
3551
|
+
",
|
|
3552
|
+
);
|
|
3553
|
+
context.resolve();
|
|
3554
|
+
|
|
3555
|
+
assert_alias_target!(&context, "Foo#years()", "Foo#age()");
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
#[test]
|
|
3559
|
+
fn follow_method_alias_to_method_in_prepended_module() {
|
|
3560
|
+
let mut context = GraphTest::new();
|
|
3561
|
+
context.index_uri(
|
|
3562
|
+
"file:///foo.rb",
|
|
3563
|
+
r"
|
|
3564
|
+
module M
|
|
3565
|
+
def original; end
|
|
3566
|
+
end
|
|
3567
|
+
|
|
3568
|
+
class Foo
|
|
3569
|
+
prepend M
|
|
3570
|
+
alias aliased original
|
|
3571
|
+
end
|
|
3572
|
+
",
|
|
3573
|
+
);
|
|
3574
|
+
context.resolve();
|
|
3575
|
+
|
|
3576
|
+
assert_alias_target!(&context, "Foo#aliased()", "M#original()");
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
#[test]
|
|
3580
|
+
fn follow_method_alias_ignores_visibility_of_target() {
|
|
3581
|
+
let mut context = GraphTest::new();
|
|
3582
|
+
context.index_uri(
|
|
3583
|
+
"file:///foo.rb",
|
|
3584
|
+
r"
|
|
3585
|
+
class Foo
|
|
3586
|
+
private def secret; end
|
|
3587
|
+
alias revealed secret
|
|
3588
|
+
end
|
|
3589
|
+
",
|
|
3590
|
+
);
|
|
3591
|
+
context.resolve();
|
|
3592
|
+
|
|
3593
|
+
assert_alias_target!(&context, "Foo#revealed()", "Foo#secret()");
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
#[test]
|
|
3597
|
+
fn follow_method_alias_picks_last_when_multiple_targets() {
|
|
3598
|
+
let mut context = GraphTest::new();
|
|
3599
|
+
context.index_uri(
|
|
3600
|
+
"file:///foo.rb",
|
|
3601
|
+
r"
|
|
3602
|
+
class Foo
|
|
3603
|
+
def bar; end
|
|
3604
|
+
def qux; end
|
|
3605
|
+
alias double bar
|
|
3606
|
+
alias double qux
|
|
3607
|
+
end
|
|
3608
|
+
",
|
|
3609
|
+
);
|
|
3610
|
+
context.resolve();
|
|
3611
|
+
|
|
3612
|
+
assert_alias_target!(&context, "Foo#double()", "Foo#qux()");
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
#[test]
|
|
3616
|
+
fn follow_method_alias_returns_target_not_found_when_target_missing() {
|
|
3617
|
+
let mut context = GraphTest::new();
|
|
3618
|
+
context.index_uri(
|
|
3619
|
+
"file:///foo.rb",
|
|
3620
|
+
r"
|
|
3621
|
+
class Foo
|
|
3622
|
+
alias aliased nonexistent
|
|
3623
|
+
end
|
|
3624
|
+
",
|
|
3625
|
+
);
|
|
3626
|
+
context.resolve();
|
|
3627
|
+
|
|
3628
|
+
assert_eq!(
|
|
3629
|
+
follow_method_alias(context.graph(), alias_def_id(&context, "Foo#aliased()")),
|
|
3630
|
+
Err(AliasResolutionError::TargetNotFound),
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
#[test]
|
|
3635
|
+
fn find_member_in_ancestors_returns_member_in_main_namespace() {
|
|
3636
|
+
let mut context = GraphTest::new();
|
|
3637
|
+
context.index_uri(
|
|
3638
|
+
"file:///foo.rb",
|
|
3639
|
+
r"
|
|
3640
|
+
class Foo
|
|
3641
|
+
def bar; end
|
|
3642
|
+
end
|
|
3643
|
+
",
|
|
3644
|
+
);
|
|
3645
|
+
context.resolve();
|
|
3646
|
+
|
|
3647
|
+
assert_eq!(
|
|
3648
|
+
find_member_in_ancestors(
|
|
3649
|
+
context.graph(),
|
|
3650
|
+
DeclarationId::from("Foo"),
|
|
3651
|
+
StringId::from("bar()"),
|
|
3652
|
+
false,
|
|
3653
|
+
),
|
|
3654
|
+
Ok(DeclarationId::from("Foo#bar()")),
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
#[test]
|
|
3659
|
+
fn find_member_in_ancestors_returns_inherited_member() {
|
|
3660
|
+
let mut context = GraphTest::new();
|
|
3661
|
+
context.index_uri(
|
|
3662
|
+
"file:///foo.rb",
|
|
3663
|
+
r"
|
|
3664
|
+
class Parent
|
|
3665
|
+
def inherited_method; end
|
|
3666
|
+
end
|
|
3667
|
+
|
|
3668
|
+
class Child < Parent
|
|
3669
|
+
end
|
|
3670
|
+
",
|
|
3671
|
+
);
|
|
3672
|
+
context.resolve();
|
|
3673
|
+
|
|
3674
|
+
assert_eq!(
|
|
3675
|
+
find_member_in_ancestors(
|
|
3676
|
+
context.graph(),
|
|
3677
|
+
DeclarationId::from("Child"),
|
|
3678
|
+
StringId::from("inherited_method()"),
|
|
3679
|
+
false,
|
|
3680
|
+
),
|
|
3681
|
+
Ok(DeclarationId::from("Parent#inherited_method()")),
|
|
3682
|
+
);
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
#[test]
|
|
3686
|
+
fn find_member_in_ancestors_returns_member_not_found_when_member_missing() {
|
|
3687
|
+
let mut context = GraphTest::new();
|
|
3688
|
+
context.index_uri(
|
|
3689
|
+
"file:///foo.rb",
|
|
3690
|
+
r"
|
|
3691
|
+
class Foo
|
|
3692
|
+
end
|
|
3693
|
+
",
|
|
3694
|
+
);
|
|
3695
|
+
context.resolve();
|
|
3696
|
+
|
|
3697
|
+
assert_eq!(
|
|
3698
|
+
find_member_in_ancestors(
|
|
3699
|
+
context.graph(),
|
|
3700
|
+
DeclarationId::from("Foo"),
|
|
3701
|
+
StringId::from("missing()"),
|
|
3702
|
+
false,
|
|
3703
|
+
),
|
|
3704
|
+
Err(FindMemberError::MemberNotFound),
|
|
3705
|
+
);
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
#[test]
|
|
3709
|
+
fn find_member_in_ancestors_returns_not_a_namespace_for_method_declaration() {
|
|
3710
|
+
let mut context = GraphTest::new();
|
|
3711
|
+
context.index_uri(
|
|
3712
|
+
"file:///foo.rb",
|
|
3713
|
+
r"
|
|
3714
|
+
class Foo
|
|
3715
|
+
def bar; end
|
|
3716
|
+
end
|
|
3717
|
+
",
|
|
3718
|
+
);
|
|
3719
|
+
context.resolve();
|
|
3720
|
+
|
|
3721
|
+
assert_eq!(
|
|
3722
|
+
find_member_in_ancestors(
|
|
3723
|
+
context.graph(),
|
|
3724
|
+
DeclarationId::from("Foo#bar()"),
|
|
3725
|
+
StringId::from("anything"),
|
|
3726
|
+
false,
|
|
3727
|
+
),
|
|
3728
|
+
Err(FindMemberError::NotNamespace),
|
|
3729
|
+
);
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3732
|
+
#[test]
|
|
3733
|
+
fn find_member_in_ancestors_returns_declaration_not_found_for_unknown_id() {
|
|
3734
|
+
let mut context = GraphTest::new();
|
|
3735
|
+
context.index_uri(
|
|
3736
|
+
"file:///foo.rb",
|
|
3737
|
+
r"
|
|
3738
|
+
class Foo
|
|
3739
|
+
end
|
|
3740
|
+
",
|
|
3741
|
+
);
|
|
3742
|
+
context.resolve();
|
|
3743
|
+
|
|
3744
|
+
assert_eq!(
|
|
3745
|
+
find_member_in_ancestors(
|
|
3746
|
+
context.graph(),
|
|
3747
|
+
DeclarationId::from("DoesNotExist"),
|
|
3748
|
+
StringId::from("anything"),
|
|
3749
|
+
false,
|
|
3750
|
+
),
|
|
3751
|
+
Err(FindMemberError::DeclarationNotFound),
|
|
3752
|
+
);
|
|
3753
|
+
}
|
|
1841
3754
|
}
|