rubydex 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
13
  use crate::model::ids::{DeclarationId, 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
- Expression(NameId),
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(DeclarationId),
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(DeclarationId),
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
- self_name_id: NameId,
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
- /// Collects completion candidate members
208
- macro_rules! collect_candidates {
209
- // Collect all members with no filtering
210
- ($declaration:expr, $context:expr, $candidates:expr) => {
211
- for (member_str_id, member_declaration_id) in $declaration.members() {
212
- if $context.dedup(member_str_id) {
213
- $candidates.push(CompletionCandidate::Declaration(*member_declaration_id));
214
- }
215
- }
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
- if matches!(member, $kinds) && $context.dedup(member_str_id) {
223
- $candidates.push(CompletionCandidate::Declaration(*member_declaration_id));
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 `self_name_id` does not point to a namespace declaration
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(self_name_id) => expression_completion(graph, self_name_id, context),
255
- CompletionReceiver::NamespaceAccess(decl_id) => namespace_access_completion(graph, decl_id, context),
256
- CompletionReceiver::MethodCall(decl_id) => method_call_completion(graph, decl_id, context),
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
- self_name_id,
319
+ self_decl_id,
320
+ nesting_name_id,
259
321
  method_decl_id,
260
- } => method_argument_completion(graph, self_name_id, method_decl_id, context),
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
- let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
309
-
310
- collect_candidates!(
372
+ collect_members(
311
373
  graph,
312
- &ancestor_decl,
313
- context,
314
- candidates,
315
- Declaration::Namespace(_) | Declaration::Constant(_) | Declaration::ConstantAlias(_)
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
- // Collect singleton methods from the singleton class and its ancestors
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 ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
327
- collect_candidates!(graph, &ancestor_decl, context, candidates, Declaration::Method(_));
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 ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
350
- collect_candidates!(graph, &ancestor_decl, context, candidates, Declaration::Method(_));
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
- self_name_id: NameId,
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(&self_name_id) else {
364
- return Err(format!("Name {self_name_id} not found in graph").into());
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 {self_name_id} to be resolved").into());
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 Some(self_decl) = graph
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
- .and_then(|d| d.as_namespace())
373
- else {
374
- return Err("Expected associated declaration to be a namespace".into());
375
- };
474
+ .unwrap()
475
+ .as_namespace()
476
+ .unwrap();
477
+
376
478
  let mut candidates = Vec::new();
377
479
 
378
- // Walk the name's lexical scopes, collecting all constant completion members
379
- let mut current_name_id = Some(self_name_id);
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
- while let Some(id) = current_name_id {
382
- let NameRef::Resolved(name_ref) = graph.names().get(&id).unwrap() else {
383
- break;
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
- let nesting_decl = graph
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(name_ref.declaration_id())
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
- collect_candidates!(
609
+ collect_members(
394
610
  graph,
395
- &nesting_decl,
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 = *name_ref.nesting();
618
+ current_name_id = *parent_ref.nesting();
402
619
  }
620
+ }
403
621
 
404
- // Include all top level constants and globals, which are accessible everywhere
405
- let object = graph.declarations().get(&OBJECT_ID).unwrap().as_namespace().unwrap();
406
- collect_candidates!(
407
- graph,
408
- &object,
409
- context,
410
- candidates,
411
- Declaration::Namespace(_)
412
- | Declaration::Constant(_)
413
- | Declaration::ConstantAlias(_)
414
- | Declaration::GlobalVariable(_)
415
- );
416
-
417
- // Walk ancestors collecting all applicable completion members
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
- let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
421
- collect_candidates!(&ancestor_decl, context, candidates);
422
-
423
- // Collect class variables from the attached object, which are available at any singleton class level
424
- // within self
425
- let attached_object = graph.attached_object(ancestor_decl);
426
- collect_candidates!(
632
+ collect_members(
427
633
  graph,
428
- &attached_object,
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
- self_name_id: NameId,
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, self_name_id, context)?;
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
  };
@@ -523,30 +727,37 @@ mod tests {
523
727
 
524
728
  macro_rules! assert_completion_eq {
525
729
  ($context:expr, $receiver:expr, $expected:expr) => {
526
- assert_eq!(
527
- $expected,
528
- *completion_candidates($context.graph(), CompletionContext::new($receiver))
529
- .unwrap()
530
- .iter()
531
- .map(|candidate| candidate_label(&$context, candidate))
532
- .collect::<Vec<_>>()
533
- );
730
+ let mut actual: Vec<String> = completion_candidates($context.graph(), CompletionContext::new($receiver))
731
+ .unwrap()
732
+ .iter()
733
+ .map(|candidate| candidate_label(&$context, candidate))
734
+ .collect();
735
+ actual.sort();
736
+
737
+ let mut expected: Vec<String> = $expected.into_iter().map(String::from).collect();
738
+ expected.sort();
739
+
740
+ assert_eq!(expected, actual);
534
741
  };
535
742
  }
536
743
 
537
744
  /// Asserts declaration and keyword argument completion candidates, excluding language keywords.
538
745
  /// Language keywords are always present in expression contexts and tested separately.
746
+ /// Both sides are sorted before comparison so tests are not coupled to candidate emission order.
539
747
  macro_rules! assert_declaration_completion_eq {
540
748
  ($context:expr, $receiver:expr, $expected:expr) => {
541
- assert_eq!(
542
- $expected,
543
- *completion_candidates($context.graph(), CompletionContext::new($receiver))
544
- .unwrap()
545
- .iter()
546
- .filter(|c| !matches!(c, CompletionCandidate::Keyword(_)))
547
- .map(|candidate| candidate_label(&$context, candidate))
548
- .collect::<Vec<_>>()
549
- );
749
+ let mut actual: Vec<String> = completion_candidates($context.graph(), CompletionContext::new($receiver))
750
+ .unwrap()
751
+ .iter()
752
+ .filter(|c| !matches!(c, CompletionCandidate::Keyword(_)))
753
+ .map(|candidate| candidate_label(&$context, candidate))
754
+ .collect();
755
+ actual.sort();
756
+
757
+ let mut expected: Vec<String> = $expected.into_iter().map(String::from).collect();
758
+ expected.sort();
759
+
760
+ assert_eq!(expected, actual);
550
761
  };
551
762
  }
552
763
 
@@ -761,8 +972,12 @@ mod tests {
761
972
  let name_id = Name::new(StringId::from("Child"), ParentScope::None, None).id();
762
973
  assert_declaration_completion_eq!(
763
974
  context,
764
- CompletionReceiver::Expression(name_id),
975
+ CompletionReceiver::Expression {
976
+ self_decl_id: None,
977
+ nesting_name_id: name_id,
978
+ },
765
979
  [
980
+ "Foo::CONST",
766
981
  "Class",
767
982
  "BasicObject",
768
983
  "Child",
@@ -772,7 +987,6 @@ mod tests {
772
987
  "Foo",
773
988
  "Object",
774
989
  "Child#baz()",
775
- "Foo::CONST",
776
990
  "Foo#bar()",
777
991
  "Parent#initialize()",
778
992
  "Parent#@var"
@@ -807,7 +1021,10 @@ mod tests {
807
1021
  let name_id = Name::new(StringId::from("Child"), ParentScope::None, None).id();
808
1022
  assert_declaration_completion_eq!(
809
1023
  context,
810
- CompletionReceiver::Expression(name_id),
1024
+ CompletionReceiver::Expression {
1025
+ self_decl_id: None,
1026
+ nesting_name_id: name_id,
1027
+ },
811
1028
  [
812
1029
  "Class",
813
1030
  "BasicObject",
@@ -853,7 +1070,10 @@ mod tests {
853
1070
  let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
854
1071
  assert_declaration_completion_eq!(
855
1072
  context,
856
- CompletionReceiver::Expression(name_id),
1073
+ CompletionReceiver::Expression {
1074
+ self_decl_id: None,
1075
+ nesting_name_id: name_id,
1076
+ },
857
1077
  [
858
1078
  "Foo",
859
1079
  "Class",
@@ -900,7 +1120,10 @@ mod tests {
900
1120
  let name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_id), Some(foo_id)).id();
901
1121
  assert_declaration_completion_eq!(
902
1122
  context,
903
- CompletionReceiver::Expression(name_id),
1123
+ CompletionReceiver::Expression {
1124
+ self_decl_id: None,
1125
+ nesting_name_id: name_id,
1126
+ },
904
1127
  [
905
1128
  "Module",
906
1129
  "Class",
@@ -917,7 +1140,10 @@ mod tests {
917
1140
  let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
918
1141
  assert_declaration_completion_eq!(
919
1142
  context,
920
- CompletionReceiver::Expression(name_id),
1143
+ CompletionReceiver::Expression {
1144
+ self_decl_id: None,
1145
+ nesting_name_id: name_id,
1146
+ },
921
1147
  [
922
1148
  "Module",
923
1149
  "Class",
@@ -966,9 +1192,11 @@ mod tests {
966
1192
  .id();
967
1193
  assert_declaration_completion_eq!(
968
1194
  context,
969
- CompletionReceiver::Expression(name_id),
1195
+ CompletionReceiver::Expression {
1196
+ self_decl_id: None,
1197
+ nesting_name_id: name_id,
1198
+ },
970
1199
  [
971
- "Foo::CONST_A",
972
1200
  "Module",
973
1201
  "Class",
974
1202
  "Object",
@@ -976,6 +1204,7 @@ mod tests {
976
1204
  "Kernel",
977
1205
  "Foo",
978
1206
  "Bar",
1207
+ "Foo::CONST_A",
979
1208
  "Bar#bar_m()",
980
1209
  "Bar#bar_m2()"
981
1210
  ]
@@ -984,7 +1213,10 @@ mod tests {
984
1213
  let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
985
1214
  assert_declaration_completion_eq!(
986
1215
  context,
987
- CompletionReceiver::Expression(name_id),
1216
+ CompletionReceiver::Expression {
1217
+ self_decl_id: None,
1218
+ nesting_name_id: name_id,
1219
+ },
988
1220
  [
989
1221
  "Module",
990
1222
  "Class",
@@ -1025,16 +1257,19 @@ mod tests {
1025
1257
  // when the user types the unqualified name CONST
1026
1258
  assert_declaration_completion_eq!(
1027
1259
  context,
1028
- CompletionReceiver::Expression(name_id),
1260
+ CompletionReceiver::Expression {
1261
+ self_decl_id: None,
1262
+ nesting_name_id: name_id,
1263
+ },
1029
1264
  [
1030
- "Foo::CONST",
1031
- "Foo::Bar",
1032
1265
  "Class",
1033
1266
  "Object",
1034
1267
  "BasicObject",
1035
1268
  "Kernel",
1036
1269
  "Foo",
1037
1270
  "Module",
1271
+ "Foo::CONST",
1272
+ "Foo::Bar",
1038
1273
  "Foo::Bar#baz()"
1039
1274
  ]
1040
1275
  );
@@ -1069,19 +1304,11 @@ mod tests {
1069
1304
  .id();
1070
1305
  assert_declaration_completion_eq!(
1071
1306
  context,
1072
- CompletionReceiver::Expression(name_id),
1073
- [
1074
- "Foo::Bar",
1075
- "$var2",
1076
- "$var",
1077
- "BasicObject",
1078
- "Object",
1079
- "Kernel",
1080
- "Module",
1081
- "Foo",
1082
- "Class",
1083
- "Foo::Bar#bar_m()"
1084
- ]
1307
+ CompletionReceiver::Expression {
1308
+ self_decl_id: None,
1309
+ nesting_name_id: name_id,
1310
+ },
1311
+ ["Foo::Bar", "$var2", "$var", "Foo::Bar#bar_m()"]
1085
1312
  );
1086
1313
  }
1087
1314
 
@@ -1108,7 +1335,10 @@ mod tests {
1108
1335
 
1109
1336
  assert_completion_eq!(
1110
1337
  context,
1111
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1338
+ CompletionReceiver::NamespaceAccess {
1339
+ self_decl_id: None,
1340
+ namespace_decl_id: DeclarationId::from("Foo")
1341
+ },
1112
1342
  ["Foo::CONST", "Foo::Bar", "Foo::<Foo>#class_method()"]
1113
1343
  );
1114
1344
  }
@@ -1141,7 +1371,10 @@ mod tests {
1141
1371
 
1142
1372
  assert_completion_eq!(
1143
1373
  context,
1144
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Child")),
1374
+ CompletionReceiver::NamespaceAccess {
1375
+ self_decl_id: None,
1376
+ namespace_decl_id: DeclarationId::from("Child")
1377
+ },
1145
1378
  [
1146
1379
  "Child::CHILD_CONST",
1147
1380
  "Parent::PARENT_CONST",
@@ -1179,7 +1412,10 @@ mod tests {
1179
1412
 
1180
1413
  assert_completion_eq!(
1181
1414
  context,
1182
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Child")),
1415
+ CompletionReceiver::NamespaceAccess {
1416
+ self_decl_id: None,
1417
+ namespace_decl_id: DeclarationId::from("Child")
1418
+ },
1183
1419
  ["Child::CONST", "Child::<Child>#shared_method()"]
1184
1420
  );
1185
1421
  }
@@ -1202,7 +1438,10 @@ mod tests {
1202
1438
 
1203
1439
  assert_completion_eq!(
1204
1440
  context,
1205
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1441
+ CompletionReceiver::NamespaceAccess {
1442
+ self_decl_id: None,
1443
+ namespace_decl_id: DeclarationId::from("Foo")
1444
+ },
1206
1445
  ["Foo::CONST"]
1207
1446
  );
1208
1447
  }
@@ -1224,7 +1463,10 @@ mod tests {
1224
1463
 
1225
1464
  assert_completion_eq!(
1226
1465
  context,
1227
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1466
+ CompletionReceiver::NamespaceAccess {
1467
+ self_decl_id: None,
1468
+ namespace_decl_id: DeclarationId::from("Foo")
1469
+ },
1228
1470
  ["Foo::CONST", "Foo::Bar"]
1229
1471
  );
1230
1472
  }
@@ -1254,7 +1496,10 @@ mod tests {
1254
1496
 
1255
1497
  assert_completion_eq!(
1256
1498
  context,
1257
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo::MyOriginal")),
1499
+ CompletionReceiver::NamespaceAccess {
1500
+ self_decl_id: None,
1501
+ namespace_decl_id: DeclarationId::from("Foo::MyOriginal")
1502
+ },
1258
1503
  [
1259
1504
  "Original::CONST",
1260
1505
  "Original::Nested",
@@ -1286,7 +1531,10 @@ mod tests {
1286
1531
 
1287
1532
  assert_completion_eq!(
1288
1533
  context,
1289
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Alias2")),
1534
+ CompletionReceiver::NamespaceAccess {
1535
+ self_decl_id: None,
1536
+ namespace_decl_id: DeclarationId::from("Alias2")
1537
+ },
1290
1538
  ["Original::CONST", "Original::<Original>#class_method()"]
1291
1539
  );
1292
1540
  }
@@ -1313,7 +1561,10 @@ mod tests {
1313
1561
 
1314
1562
  assert_completion_eq!(
1315
1563
  context,
1316
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1564
+ CompletionReceiver::NamespaceAccess {
1565
+ self_decl_id: None,
1566
+ namespace_decl_id: DeclarationId::from("Foo")
1567
+ },
1317
1568
  ["Foo::CONST", "Foo::<Foo>#class_method()"]
1318
1569
  );
1319
1570
  }
@@ -1347,7 +1598,10 @@ mod tests {
1347
1598
 
1348
1599
  assert_completion_eq!(
1349
1600
  context,
1350
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1601
+ CompletionReceiver::NamespaceAccess {
1602
+ self_decl_id: None,
1603
+ namespace_decl_id: DeclarationId::from("Foo")
1604
+ },
1351
1605
  ["Foo::FOO_CONST", "Bar::CONST", "Foo::<Foo>#foo_class_method()"]
1352
1606
  );
1353
1607
  }
@@ -1375,7 +1629,10 @@ mod tests {
1375
1629
 
1376
1630
  assert_completion_eq!(
1377
1631
  context,
1378
- CompletionReceiver::MethodCall(DeclarationId::from("Foo")),
1632
+ CompletionReceiver::MethodCall {
1633
+ self_decl_id: None,
1634
+ receiver_decl_id: DeclarationId::from("Foo")
1635
+ },
1379
1636
  ["Foo#baz()", "Foo#bar()"]
1380
1637
  );
1381
1638
  }
@@ -1405,7 +1662,10 @@ mod tests {
1405
1662
 
1406
1663
  assert_completion_eq!(
1407
1664
  context,
1408
- CompletionReceiver::MethodCall(DeclarationId::from("Foo::MyOriginal")),
1665
+ CompletionReceiver::MethodCall {
1666
+ self_decl_id: None,
1667
+ receiver_decl_id: DeclarationId::from("Foo::MyOriginal")
1668
+ },
1409
1669
  ["Original#baz()", "Original#bar()"]
1410
1670
  );
1411
1671
  }
@@ -1430,7 +1690,10 @@ mod tests {
1430
1690
 
1431
1691
  assert_completion_eq!(
1432
1692
  context,
1433
- CompletionReceiver::MethodCall(DeclarationId::from("Child")),
1693
+ CompletionReceiver::MethodCall {
1694
+ self_decl_id: None,
1695
+ receiver_decl_id: DeclarationId::from("Child")
1696
+ },
1434
1697
  ["Child#child_method()", "Parent#parent_method()"]
1435
1698
  );
1436
1699
  }
@@ -1457,7 +1720,10 @@ mod tests {
1457
1720
 
1458
1721
  assert_completion_eq!(
1459
1722
  context,
1460
- CompletionReceiver::MethodCall(DeclarationId::from("Foo")),
1723
+ CompletionReceiver::MethodCall {
1724
+ self_decl_id: None,
1725
+ receiver_decl_id: DeclarationId::from("Foo")
1726
+ },
1461
1727
  ["Foo#foo_method()", "Mixin#mixin_method()"]
1462
1728
  );
1463
1729
  }
@@ -1484,7 +1750,10 @@ mod tests {
1484
1750
 
1485
1751
  assert_completion_eq!(
1486
1752
  context,
1487
- CompletionReceiver::MethodCall(DeclarationId::from("Child")),
1753
+ CompletionReceiver::MethodCall {
1754
+ self_decl_id: None,
1755
+ receiver_decl_id: DeclarationId::from("Child")
1756
+ },
1488
1757
  ["Child#shared_method()", "Child#child_only()", "Parent#parent_only()"]
1489
1758
  );
1490
1759
  }
@@ -1512,7 +1781,10 @@ mod tests {
1512
1781
 
1513
1782
  assert_completion_eq!(
1514
1783
  context,
1515
- CompletionReceiver::MethodCall(DeclarationId::from("Foo")),
1784
+ CompletionReceiver::MethodCall {
1785
+ self_decl_id: None,
1786
+ receiver_decl_id: DeclarationId::from("Foo")
1787
+ },
1516
1788
  ["Foo#initialize()", "Foo#bar()"]
1517
1789
  );
1518
1790
  }
@@ -1537,7 +1809,10 @@ mod tests {
1537
1809
 
1538
1810
  assert_completion_eq!(
1539
1811
  context,
1540
- CompletionReceiver::MethodCall(DeclarationId::from("Foo::<Foo>")),
1812
+ CompletionReceiver::MethodCall {
1813
+ self_decl_id: None,
1814
+ receiver_decl_id: DeclarationId::from("Foo::<Foo>")
1815
+ },
1541
1816
  ["Foo::<Foo>#baz()", "Foo::<Foo>#bar()"]
1542
1817
  );
1543
1818
  }
@@ -1560,7 +1835,8 @@ mod tests {
1560
1835
  assert_declaration_completion_eq!(
1561
1836
  context,
1562
1837
  CompletionReceiver::MethodArgument {
1563
- self_name_id: name_id,
1838
+ self_decl_id: None,
1839
+ nesting_name_id: name_id,
1564
1840
  method_decl_id: DeclarationId::from("Foo#greet()"),
1565
1841
  },
1566
1842
  [
@@ -1595,7 +1871,8 @@ mod tests {
1595
1871
  assert_declaration_completion_eq!(
1596
1872
  context,
1597
1873
  CompletionReceiver::MethodArgument {
1598
- self_name_id: name_id,
1874
+ self_decl_id: None,
1875
+ nesting_name_id: name_id,
1599
1876
  method_decl_id: DeclarationId::from("Foo#bar()"),
1600
1877
  },
1601
1878
  ["Class", "Object", "BasicObject", "Kernel", "Foo", "Module", "Foo#bar()"]
@@ -1620,7 +1897,8 @@ mod tests {
1620
1897
  assert_declaration_completion_eq!(
1621
1898
  context,
1622
1899
  CompletionReceiver::MethodArgument {
1623
- self_name_id: name_id,
1900
+ self_decl_id: None,
1901
+ nesting_name_id: name_id,
1624
1902
  method_decl_id: DeclarationId::from("Foo#search()"),
1625
1903
  },
1626
1904
  // Only RequiredKeyword and OptionalKeyword, not RestKeyword (**opts)
@@ -1663,7 +1941,8 @@ mod tests {
1663
1941
  assert_declaration_completion_eq!(
1664
1942
  context,
1665
1943
  CompletionReceiver::MethodArgument {
1666
- self_name_id: name_id,
1944
+ self_decl_id: None,
1945
+ nesting_name_id: name_id,
1667
1946
  method_decl_id: DeclarationId::from("Foo#bar()"),
1668
1947
  },
1669
1948
  [
@@ -1689,7 +1968,10 @@ mod tests {
1689
1968
  let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
1690
1969
  assert_completion_eq!(
1691
1970
  context,
1692
- CompletionReceiver::Expression(name_id),
1971
+ CompletionReceiver::Expression {
1972
+ self_decl_id: None,
1973
+ nesting_name_id: name_id,
1974
+ },
1693
1975
  [
1694
1976
  "Class",
1695
1977
  "Object",
@@ -1752,7 +2034,8 @@ mod tests {
1752
2034
  assert_completion_eq!(
1753
2035
  context,
1754
2036
  CompletionReceiver::MethodArgument {
1755
- self_name_id: name_id,
2037
+ self_decl_id: None,
2038
+ nesting_name_id: name_id,
1756
2039
  method_decl_id: DeclarationId::from("Foo#bar()"),
1757
2040
  },
1758
2041
  [
@@ -1817,7 +2100,10 @@ mod tests {
1817
2100
 
1818
2101
  let candidates = completion_candidates(
1819
2102
  context.graph(),
1820
- CompletionContext::new(CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo"))),
2103
+ CompletionContext::new(CompletionReceiver::NamespaceAccess {
2104
+ self_decl_id: None,
2105
+ namespace_decl_id: DeclarationId::from("Foo"),
2106
+ }),
1821
2107
  )
1822
2108
  .unwrap();
1823
2109
 
@@ -1832,10 +2118,1040 @@ mod tests {
1832
2118
 
1833
2119
  let candidates = completion_candidates(
1834
2120
  context.graph(),
1835
- CompletionContext::new(CompletionReceiver::MethodCall(DeclarationId::from("Foo"))),
2121
+ CompletionContext::new(CompletionReceiver::MethodCall {
2122
+ self_decl_id: None,
2123
+ receiver_decl_id: DeclarationId::from("Foo"),
2124
+ }),
1836
2125
  )
1837
2126
  .unwrap();
1838
2127
 
1839
2128
  assert!(!candidates.iter().any(|c| matches!(c, CompletionCandidate::Keyword(_))));
1840
2129
  }
2130
+
2131
+ #[test]
2132
+ fn expression_completion_class_variables_follow_lexical_scope() {
2133
+ // `@@cvar` in Ruby is resolved via the innermost lexical class/module's ancestor chain,
2134
+ // NOT `self`'s ancestor chain. Inside `def Foo.bar` written inside `Outer`, the lexical
2135
+ // scope is `[Outer]`, so `@@outer_cvar` is reachable and `@@foo_cvar` (which lives on
2136
+ // `Foo`, not on any lexical ancestor) must not be offered.
2137
+ let mut context = GraphTest::new();
2138
+
2139
+ context.index_uri(
2140
+ "file:///foo.rb",
2141
+ "
2142
+ module Outer
2143
+ @@outer_cvar = 1
2144
+
2145
+ class Foo
2146
+ @@foo_cvar = 2
2147
+ def self.singleton_m; end
2148
+ end
2149
+ end
2150
+ ",
2151
+ );
2152
+ context.resolve();
2153
+
2154
+ let outer_name_id = Name::new(StringId::from("Outer"), ParentScope::None, None).id();
2155
+ assert_declaration_completion_eq!(
2156
+ context,
2157
+ CompletionReceiver::Expression {
2158
+ self_decl_id: Some(DeclarationId::from("Outer::Foo::<Foo>")),
2159
+ nesting_name_id: outer_name_id,
2160
+ },
2161
+ [
2162
+ "Module",
2163
+ "Class",
2164
+ "Object",
2165
+ "BasicObject",
2166
+ "Kernel",
2167
+ "Outer",
2168
+ "Outer::Foo",
2169
+ "Outer#@@outer_cvar",
2170
+ "Outer::Foo::<Foo>#singleton_m()"
2171
+ ]
2172
+ );
2173
+ }
2174
+
2175
+ #[test]
2176
+ fn expression_completion_follows_self_decl_alias() {
2177
+ let mut context = GraphTest::new();
2178
+
2179
+ context.index_uri(
2180
+ "file:///foo.rb",
2181
+ "
2182
+ module Outer
2183
+ class Original
2184
+ def original_m; end
2185
+ end
2186
+
2187
+ MyAlias = Original
2188
+ end
2189
+ ",
2190
+ );
2191
+ context.resolve();
2192
+
2193
+ let name_id = Name::new(StringId::from("Outer"), ParentScope::None, None).id();
2194
+ // `self_decl_id` points to the alias `Outer::MyAlias`, which is a `ConstantAlias` rather than a `Namespace`.
2195
+ // The completion should still collect members from the aliased namespace (`Outer::Original`) instead of
2196
+ // returning an error, so callers do not have to unwrap aliases themselves.
2197
+ assert_declaration_completion_eq!(
2198
+ context,
2199
+ CompletionReceiver::Expression {
2200
+ self_decl_id: Some(DeclarationId::from("Outer::MyAlias")),
2201
+ nesting_name_id: name_id,
2202
+ },
2203
+ [
2204
+ "Outer::MyAlias",
2205
+ "Outer::Original",
2206
+ "Class",
2207
+ "Object",
2208
+ "BasicObject",
2209
+ "Outer",
2210
+ "Kernel",
2211
+ "Module",
2212
+ "Outer::Original#original_m()"
2213
+ ]
2214
+ );
2215
+ }
2216
+
2217
+ #[test]
2218
+ fn expression_completion_in_method_definition_with_receiver_uses_lexical_scope_for_class_variables() {
2219
+ let mut context = GraphTest::new();
2220
+
2221
+ context.index_uri(
2222
+ "file:///foo.rb",
2223
+ "
2224
+ class Foo
2225
+ @@class_var = 1
2226
+ end
2227
+
2228
+ class Bar
2229
+ @@other_class_var = 2
2230
+
2231
+ def Foo.baz
2232
+ # completion here
2233
+ end
2234
+ end
2235
+ ",
2236
+ );
2237
+ context.resolve();
2238
+
2239
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2240
+ assert_declaration_completion_eq!(
2241
+ context,
2242
+ CompletionReceiver::Expression {
2243
+ self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
2244
+ nesting_name_id: name_id,
2245
+ },
2246
+ [
2247
+ "Module",
2248
+ "Class",
2249
+ "Object",
2250
+ "BasicObject",
2251
+ "Kernel",
2252
+ "Foo",
2253
+ "Bar",
2254
+ "Foo::<Foo>#baz()",
2255
+ "Bar#@@other_class_var"
2256
+ ]
2257
+ );
2258
+ }
2259
+
2260
+ #[test]
2261
+ fn expression_completion_in_method_definition_with_receiver_uses_lexical_scope_for_constants() {
2262
+ let mut context = GraphTest::new();
2263
+
2264
+ context.index_uri(
2265
+ "file:///foo.rb",
2266
+ "
2267
+ class Foo
2268
+ CONST = 1
2269
+ end
2270
+
2271
+ class Bar
2272
+ OTHER_CONST = 2
2273
+
2274
+ def Foo.baz
2275
+ # completion here
2276
+ end
2277
+ end
2278
+ ",
2279
+ );
2280
+ context.resolve();
2281
+
2282
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2283
+ assert_declaration_completion_eq!(
2284
+ context,
2285
+ CompletionReceiver::Expression {
2286
+ self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
2287
+ nesting_name_id: name_id,
2288
+ },
2289
+ [
2290
+ "Bar::OTHER_CONST",
2291
+ "Module",
2292
+ "Class",
2293
+ "Object",
2294
+ "BasicObject",
2295
+ "Kernel",
2296
+ "Foo",
2297
+ "Bar",
2298
+ "Foo::<Foo>#baz()"
2299
+ ]
2300
+ );
2301
+ }
2302
+
2303
+ #[test]
2304
+ fn expression_completion_does_not_leak_constants_reachable_only_through_self_ancestors() {
2305
+ let mut context = GraphTest::new();
2306
+
2307
+ context.index_uri(
2308
+ "file:///foo.rb",
2309
+ "
2310
+ module Mixin
2311
+ MIXIN_CONST = 1
2312
+ end
2313
+
2314
+ class Foo
2315
+ extend Mixin
2316
+ end
2317
+
2318
+ class Bar
2319
+ def Foo.baz
2320
+ # completion here
2321
+ end
2322
+ end
2323
+ ",
2324
+ );
2325
+ context.resolve();
2326
+
2327
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2328
+ assert_declaration_completion_eq!(
2329
+ context,
2330
+ CompletionReceiver::Expression {
2331
+ self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
2332
+ nesting_name_id: name_id,
2333
+ },
2334
+ [
2335
+ "Class",
2336
+ "BasicObject",
2337
+ "Mixin",
2338
+ "Object",
2339
+ "Kernel",
2340
+ "Module",
2341
+ "Foo",
2342
+ "Bar",
2343
+ "Foo::<Foo>#baz()"
2344
+ ]
2345
+ );
2346
+ }
2347
+
2348
+ #[test]
2349
+ fn expression_completion_at_top_level_includes_object_constants() {
2350
+ let mut context = GraphTest::new();
2351
+
2352
+ context.index_uri(
2353
+ "file:///foo.rb",
2354
+ "
2355
+ class Foo
2356
+ CONST = 1
2357
+ end
2358
+
2359
+ TOP_CONST = 2
2360
+ ",
2361
+ );
2362
+ context.resolve();
2363
+
2364
+ let name_id = Name::new(StringId::from("Object"), ParentScope::None, None).id();
2365
+ assert_declaration_completion_eq!(
2366
+ context,
2367
+ CompletionReceiver::Expression {
2368
+ self_decl_id: None,
2369
+ nesting_name_id: name_id,
2370
+ },
2371
+ ["Module", "Class", "Object", "BasicObject", "Kernel", "Foo", "TOP_CONST"]
2372
+ );
2373
+ }
2374
+
2375
+ #[test]
2376
+ fn expression_completion_in_module_body_falls_back_to_object_constants() {
2377
+ let mut context = GraphTest::new();
2378
+
2379
+ context.index_uri(
2380
+ "file:///foo.rb",
2381
+ "
2382
+ TOP_CONST = 1
2383
+
2384
+ module Mod
2385
+ MOD_CONST = 2
2386
+ end
2387
+ ",
2388
+ );
2389
+ context.resolve();
2390
+
2391
+ let name_id = Name::new(StringId::from("Mod"), ParentScope::None, None).id();
2392
+ assert_declaration_completion_eq!(
2393
+ context,
2394
+ CompletionReceiver::Expression {
2395
+ self_decl_id: None,
2396
+ nesting_name_id: name_id,
2397
+ },
2398
+ [
2399
+ "Mod::MOD_CONST",
2400
+ "Module",
2401
+ "Class",
2402
+ "Object",
2403
+ "BasicObject",
2404
+ "Kernel",
2405
+ "TOP_CONST",
2406
+ "Mod"
2407
+ ]
2408
+ );
2409
+ }
2410
+
2411
+ #[test]
2412
+ fn expression_completion_in_module_body_falls_back_to_object_ancestors() {
2413
+ let mut context = GraphTest::new();
2414
+
2415
+ context.index_uri(
2416
+ "file:///foo.rb",
2417
+ "
2418
+ module Kernel
2419
+ CONST = 1
2420
+ end
2421
+
2422
+ module Mod
2423
+ # completion here
2424
+ end
2425
+ ",
2426
+ );
2427
+ context.resolve();
2428
+
2429
+ let name_id = Name::new(StringId::from("Mod"), ParentScope::None, None).id();
2430
+ assert_declaration_completion_eq!(
2431
+ context,
2432
+ CompletionReceiver::Expression {
2433
+ self_decl_id: None,
2434
+ nesting_name_id: name_id,
2435
+ },
2436
+ [
2437
+ "Class",
2438
+ "Object",
2439
+ "Kernel",
2440
+ "BasicObject",
2441
+ "Mod",
2442
+ "Module",
2443
+ "Kernel::CONST",
2444
+ ]
2445
+ );
2446
+ }
2447
+
2448
+ #[test]
2449
+ fn expression_completion_at_top_level_offers_methods_from_object_chain() {
2450
+ let mut context = GraphTest::new();
2451
+
2452
+ context.index_uri(
2453
+ "file:///foo.rb",
2454
+ "
2455
+ def my_top_method; end
2456
+
2457
+ module Kernel
2458
+ def kernel_helper; end
2459
+ end
2460
+ ",
2461
+ );
2462
+ context.resolve();
2463
+
2464
+ let name_id = Name::new(StringId::from("Object"), ParentScope::None, None).id();
2465
+ assert_declaration_completion_eq!(
2466
+ context,
2467
+ CompletionReceiver::Expression {
2468
+ self_decl_id: None,
2469
+ nesting_name_id: name_id,
2470
+ },
2471
+ [
2472
+ "Object",
2473
+ "Kernel",
2474
+ "BasicObject",
2475
+ "Module",
2476
+ "Class",
2477
+ "Object#my_top_method()",
2478
+ "Kernel#kernel_helper()"
2479
+ ]
2480
+ );
2481
+ }
2482
+
2483
+ #[test]
2484
+ fn expression_completion_in_basic_object_subclass_excludes_object_constants() {
2485
+ let mut context = GraphTest::new();
2486
+
2487
+ context.index_uri(
2488
+ "file:///foo.rb",
2489
+ "
2490
+ TOP_CONST = 1
2491
+
2492
+ class Bar < BasicObject
2493
+ BAR_CONST = 2
2494
+ end
2495
+ ",
2496
+ );
2497
+ context.resolve();
2498
+
2499
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2500
+ assert_declaration_completion_eq!(
2501
+ context,
2502
+ CompletionReceiver::Expression {
2503
+ self_decl_id: None,
2504
+ nesting_name_id: name_id,
2505
+ },
2506
+ ["Bar::BAR_CONST"]
2507
+ );
2508
+ }
2509
+
2510
+ #[test]
2511
+ fn expression_completion_class_variables_in_singleton_class_block_use_outer_lexical_scope() {
2512
+ let mut context = GraphTest::new();
2513
+
2514
+ context.index_uri(
2515
+ "file:///foo.rb",
2516
+ "
2517
+ class Foo
2518
+ @@foo_cvar = 1
2519
+ end
2520
+
2521
+ class Bar
2522
+ @@bar_cvar = 2
2523
+
2524
+ class << Foo
2525
+ # completion here
2526
+ end
2527
+ end
2528
+ ",
2529
+ );
2530
+ context.resolve();
2531
+
2532
+ let bar_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2533
+ let foo_ref_id = Name::new(StringId::from("Foo"), ParentScope::None, Some(bar_id)).id();
2534
+ let nesting_name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_ref_id), Some(bar_id)).id();
2535
+
2536
+ assert_declaration_completion_eq!(
2537
+ context,
2538
+ CompletionReceiver::Expression {
2539
+ self_decl_id: None,
2540
+ nesting_name_id,
2541
+ },
2542
+ ["Bar#@@bar_cvar"]
2543
+ );
2544
+ }
2545
+
2546
+ #[test]
2547
+ fn expression_completion_in_def_self_method_inside_module_body() {
2548
+ let mut context = GraphTest::new();
2549
+
2550
+ context.index_uri(
2551
+ "file:///foo.rb",
2552
+ "
2553
+ module Mod
2554
+ MOD_CONST = 1
2555
+
2556
+ def self.helper
2557
+ # completion here
2558
+ end
2559
+ end
2560
+ ",
2561
+ );
2562
+ context.resolve();
2563
+
2564
+ let name_id = Name::new(StringId::from("Mod"), ParentScope::None, None).id();
2565
+ assert_declaration_completion_eq!(
2566
+ context,
2567
+ CompletionReceiver::Expression {
2568
+ self_decl_id: Some(DeclarationId::from("Mod::<Mod>")),
2569
+ nesting_name_id: name_id,
2570
+ },
2571
+ [
2572
+ "Mod::MOD_CONST",
2573
+ "Module",
2574
+ "Class",
2575
+ "Object",
2576
+ "BasicObject",
2577
+ "Kernel",
2578
+ "Mod",
2579
+ "Mod::<Mod>#helper()"
2580
+ ]
2581
+ );
2582
+ }
2583
+
2584
+ #[test]
2585
+ fn expression_completion_in_singleton_class_block_at_top_level() {
2586
+ let mut context = GraphTest::new();
2587
+
2588
+ context.index_uri(
2589
+ "file:///foo.rb",
2590
+ "
2591
+ class Foo
2592
+ @@foo_cvar = 1
2593
+ end
2594
+
2595
+ class << Foo
2596
+ # completion here
2597
+ end
2598
+ ",
2599
+ );
2600
+ context.resolve();
2601
+
2602
+ let foo_ref_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
2603
+ let nesting_name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_ref_id), None).id();
2604
+
2605
+ assert_declaration_completion_eq!(
2606
+ context,
2607
+ CompletionReceiver::Expression {
2608
+ self_decl_id: None,
2609
+ nesting_name_id,
2610
+ },
2611
+ ["Module", "Class", "Object", "BasicObject", "Kernel", "Foo"]
2612
+ );
2613
+ }
2614
+
2615
+ #[test]
2616
+ fn expression_completion_errors_when_self_decl_id_does_not_exist() {
2617
+ let mut context = GraphTest::new();
2618
+
2619
+ context.index_uri(
2620
+ "file:///foo.rb",
2621
+ "
2622
+ class Foo
2623
+ end
2624
+ ",
2625
+ );
2626
+ context.resolve();
2627
+
2628
+ let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
2629
+ let result = completion_candidates(
2630
+ context.graph(),
2631
+ CompletionContext::new(CompletionReceiver::Expression {
2632
+ self_decl_id: Some(DeclarationId::from("Nonexistent")),
2633
+ nesting_name_id: name_id,
2634
+ }),
2635
+ );
2636
+
2637
+ assert!(result.is_err(), "missing self_decl_id should surface as an error");
2638
+ }
2639
+
2640
+ #[test]
2641
+ fn expression_completion_errors_when_self_decl_id_is_not_a_namespace() {
2642
+ let mut context = GraphTest::new();
2643
+
2644
+ context.index_uri(
2645
+ "file:///foo.rb",
2646
+ "
2647
+ class Foo
2648
+ CONST = 1
2649
+ end
2650
+ ",
2651
+ );
2652
+ context.resolve();
2653
+
2654
+ let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
2655
+ let result = completion_candidates(
2656
+ context.graph(),
2657
+ CompletionContext::new(CompletionReceiver::Expression {
2658
+ // CONST resolves to a `Constant`, not a `Namespace` and not an alias.
2659
+ self_decl_id: Some(DeclarationId::from("Foo::CONST")),
2660
+ nesting_name_id: name_id,
2661
+ }),
2662
+ );
2663
+
2664
+ assert!(result.is_err(), "non-namespace self_decl_id should surface as an error");
2665
+ }
2666
+
2667
+ #[test]
2668
+ fn expression_completion_follows_chained_self_decl_alias() {
2669
+ let mut context = GraphTest::new();
2670
+
2671
+ context.index_uri(
2672
+ "file:///foo.rb",
2673
+ "
2674
+ module Outer
2675
+ class Original
2676
+ def original_m; end
2677
+ end
2678
+
2679
+ FirstAlias = Original
2680
+ SecondAlias = FirstAlias
2681
+ end
2682
+ ",
2683
+ );
2684
+ context.resolve();
2685
+
2686
+ let name_id = Name::new(StringId::from("Outer"), ParentScope::None, None).id();
2687
+ assert_declaration_completion_eq!(
2688
+ context,
2689
+ CompletionReceiver::Expression {
2690
+ self_decl_id: Some(DeclarationId::from("Outer::SecondAlias")),
2691
+ nesting_name_id: name_id,
2692
+ },
2693
+ [
2694
+ "Outer::FirstAlias",
2695
+ "Outer::SecondAlias",
2696
+ "Outer::Original",
2697
+ "Module",
2698
+ "Class",
2699
+ "Object",
2700
+ "BasicObject",
2701
+ "Outer",
2702
+ "Kernel",
2703
+ "Outer::Original#original_m()"
2704
+ ]
2705
+ );
2706
+ }
2707
+
2708
+ #[test]
2709
+ fn expression_completion_includes_private_singleton_method_when_self_matches_owner() {
2710
+ let mut context = GraphTest::new();
2711
+
2712
+ context.index_uri(
2713
+ "file:///foo.rb",
2714
+ "
2715
+ class Foo
2716
+ def self.bar; end
2717
+ private_class_method :bar
2718
+ end
2719
+
2720
+ class Bar
2721
+ def Foo.baz
2722
+ # completion here
2723
+ end
2724
+ end
2725
+ ",
2726
+ );
2727
+ context.resolve();
2728
+
2729
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2730
+ assert_declaration_completion_eq!(
2731
+ context,
2732
+ CompletionReceiver::Expression {
2733
+ self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
2734
+ nesting_name_id: name_id,
2735
+ },
2736
+ [
2737
+ "Module",
2738
+ "Class",
2739
+ "Object",
2740
+ "BasicObject",
2741
+ "Kernel",
2742
+ "Foo",
2743
+ "Bar",
2744
+ "Foo::<Foo>#bar()",
2745
+ "Foo::<Foo>#baz()"
2746
+ ]
2747
+ );
2748
+ }
2749
+
2750
+ #[test]
2751
+ fn method_call_completion_excludes_private_method_for_external_call() {
2752
+ let mut context = GraphTest::new();
2753
+
2754
+ context.index_uri(
2755
+ "file:///foo.rb",
2756
+ "
2757
+ class Foo
2758
+ private
2759
+
2760
+ def bar; end
2761
+ end
2762
+ ",
2763
+ );
2764
+ context.resolve();
2765
+
2766
+ assert_completion_eq!(
2767
+ context,
2768
+ CompletionReceiver::MethodCall {
2769
+ self_decl_id: None,
2770
+ receiver_decl_id: DeclarationId::from("Foo")
2771
+ },
2772
+ [] as [&str; 0]
2773
+ );
2774
+ }
2775
+
2776
+ #[test]
2777
+ fn expression_completion_includes_private_instance_method_inside_self_ancestor_chain() {
2778
+ let mut context = GraphTest::new();
2779
+
2780
+ context.index_uri(
2781
+ "file:///foo.rb",
2782
+ "
2783
+ class Foo
2784
+ def baz
2785
+ # completion here
2786
+ end
2787
+
2788
+ private
2789
+
2790
+ def bar; end
2791
+ end
2792
+
2793
+ class Bar < Foo
2794
+ def qux
2795
+ # completion here
2796
+ end
2797
+ end
2798
+ ",
2799
+ );
2800
+ context.resolve();
2801
+
2802
+ let foo_name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
2803
+ assert_declaration_completion_eq!(
2804
+ context,
2805
+ CompletionReceiver::Expression {
2806
+ self_decl_id: None,
2807
+ nesting_name_id: foo_name_id,
2808
+ },
2809
+ [
2810
+ "Module",
2811
+ "Class",
2812
+ "Object",
2813
+ "BasicObject",
2814
+ "Kernel",
2815
+ "Foo",
2816
+ "Bar",
2817
+ "Foo#baz()",
2818
+ "Foo#bar()"
2819
+ ]
2820
+ );
2821
+
2822
+ let bar_name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2823
+ assert_declaration_completion_eq!(
2824
+ context,
2825
+ CompletionReceiver::Expression {
2826
+ self_decl_id: None,
2827
+ nesting_name_id: bar_name_id,
2828
+ },
2829
+ [
2830
+ "Module",
2831
+ "Class",
2832
+ "Object",
2833
+ "BasicObject",
2834
+ "Kernel",
2835
+ "Foo",
2836
+ "Bar",
2837
+ "Foo#baz()",
2838
+ "Foo#bar()",
2839
+ "Bar#qux()"
2840
+ ]
2841
+ );
2842
+ }
2843
+
2844
+ #[test]
2845
+ fn method_call_completion_includes_protected_method_when_caller_shares_ancestor_with_receiver() {
2846
+ let mut context = GraphTest::new();
2847
+
2848
+ context.index_uri(
2849
+ "file:///foo.rb",
2850
+ "
2851
+ class Account
2852
+ protected
2853
+
2854
+ def balance; end
2855
+ end
2856
+
2857
+ class Savings < Account
2858
+ end
2859
+
2860
+ class Checking < Account
2861
+ end
2862
+ ",
2863
+ );
2864
+ context.resolve();
2865
+
2866
+ // Caller's self is `Account` (or any descendant). Both caller and receiver descend from
2867
+ // the defining class, satisfying MRI's `caller.class <= defined_class && recv.class <= defined_class`.
2868
+ assert_completion_eq!(
2869
+ context,
2870
+ CompletionReceiver::MethodCall {
2871
+ self_decl_id: Some(DeclarationId::from("Account")),
2872
+ receiver_decl_id: DeclarationId::from("Savings"),
2873
+ },
2874
+ ["Account#balance()"]
2875
+ );
2876
+
2877
+ assert_completion_eq!(
2878
+ context,
2879
+ CompletionReceiver::MethodCall {
2880
+ self_decl_id: Some(DeclarationId::from("Savings")),
2881
+ receiver_decl_id: DeclarationId::from("Checking"),
2882
+ },
2883
+ ["Account#balance()"]
2884
+ );
2885
+ }
2886
+
2887
+ #[test]
2888
+ fn method_call_completion_excludes_protected_method_when_caller_does_not_share_ancestor() {
2889
+ let mut context = GraphTest::new();
2890
+ context.index_uri(
2891
+ "file:///foo.rb",
2892
+ "
2893
+ class Account
2894
+ protected
2895
+
2896
+ def balance; end
2897
+ end
2898
+
2899
+ class Unrelated
2900
+ end
2901
+ ",
2902
+ );
2903
+ context.resolve();
2904
+
2905
+ // Receiver is `Account`; caller's self is `Unrelated`. Caller is not a descendant of the
2906
+ // defining class, so the protected check fails and `balance` is hidden.
2907
+ assert_completion_eq!(
2908
+ context,
2909
+ CompletionReceiver::MethodCall {
2910
+ self_decl_id: Some(DeclarationId::from("Unrelated")),
2911
+ receiver_decl_id: DeclarationId::from("Account"),
2912
+ },
2913
+ [] as [&str; 0]
2914
+ );
2915
+ }
2916
+
2917
+ #[test]
2918
+ fn method_call_completion_includes_private_method_when_receiver_is_self() {
2919
+ let mut context = GraphTest::new();
2920
+ context.index_uri(
2921
+ "file:///foo.rb",
2922
+ "
2923
+ class Foo
2924
+ def public_method; end
2925
+
2926
+ private
2927
+
2928
+ def private_method; end
2929
+ end
2930
+ ",
2931
+ );
2932
+ context.resolve();
2933
+
2934
+ // Caller's self is `Foo`, matching the receiver — `private_method` becomes visible
2935
+ // (Ruby 3.0+ allows `self.foo` for private methods, and our completion treats receiver-equals-self as the
2936
+ // implicit-receiver case).
2937
+ assert_completion_eq!(
2938
+ context,
2939
+ CompletionReceiver::MethodCall {
2940
+ self_decl_id: Some(DeclarationId::from("Foo")),
2941
+ receiver_decl_id: DeclarationId::from("Foo"),
2942
+ },
2943
+ ["Foo#public_method()", "Foo#private_method()"]
2944
+ );
2945
+ }
2946
+
2947
+ #[test]
2948
+ fn method_call_completion_includes_protected_method_through_included_module() {
2949
+ let mut context = GraphTest::new();
2950
+ context.index_uri(
2951
+ "file:///foo.rb",
2952
+ "
2953
+ module Sharable
2954
+ protected
2955
+
2956
+ def shared_secret; end
2957
+ end
2958
+
2959
+ class A
2960
+ include Sharable
2961
+ end
2962
+
2963
+ class B
2964
+ include Sharable
2965
+ end
2966
+ ",
2967
+ );
2968
+ context.resolve();
2969
+
2970
+ assert_completion_eq!(
2971
+ context,
2972
+ CompletionReceiver::MethodCall {
2973
+ self_decl_id: Some(DeclarationId::from("A")),
2974
+ receiver_decl_id: DeclarationId::from("B"),
2975
+ },
2976
+ ["Sharable#shared_secret()"]
2977
+ );
2978
+ }
2979
+
2980
+ #[test]
2981
+ fn method_call_completion_excludes_protected_when_caller_class_is_not_descendant_of_defined_class() {
2982
+ let mut context = GraphTest::new();
2983
+ context.index_uri(
2984
+ "file:///foo.rb",
2985
+ "
2986
+ class Animal
2987
+ end
2988
+
2989
+ class Dog < Animal
2990
+ protected
2991
+
2992
+ def secret_trick; end
2993
+ end
2994
+ ",
2995
+ );
2996
+ context.resolve();
2997
+
2998
+ assert_completion_eq!(
2999
+ context,
3000
+ CompletionReceiver::MethodCall {
3001
+ self_decl_id: Some(DeclarationId::from("Animal")),
3002
+ receiver_decl_id: DeclarationId::from("Dog"),
3003
+ },
3004
+ [] as [&str; 0]
3005
+ );
3006
+ }
3007
+
3008
+ #[test]
3009
+ fn method_call_completion_excludes_visibility_restricted_methods_at_top_level() {
3010
+ let mut context = GraphTest::new();
3011
+ context.index_uri(
3012
+ "file:///foo.rb",
3013
+ "
3014
+ class Foo
3015
+ def pub_inst; end
3016
+
3017
+ protected
3018
+
3019
+ def prot_inst; end
3020
+
3021
+ private
3022
+
3023
+ def priv_inst; end
3024
+ end
3025
+ ",
3026
+ );
3027
+ context.resolve();
3028
+
3029
+ assert_completion_eq!(
3030
+ context,
3031
+ CompletionReceiver::MethodCall {
3032
+ self_decl_id: None,
3033
+ receiver_decl_id: DeclarationId::from("Foo"),
3034
+ },
3035
+ ["Foo#pub_inst()"]
3036
+ );
3037
+ }
3038
+
3039
+ #[test]
3040
+ fn method_call_completion_hides_method_when_subclass_overrides_with_stricter_visibility() {
3041
+ let mut context = GraphTest::new();
3042
+ context.index_uri(
3043
+ "file:///foo.rb",
3044
+ "
3045
+ class Parent
3046
+ def foo; end
3047
+ end
3048
+
3049
+ class Child < Parent
3050
+ private
3051
+
3052
+ def foo; end
3053
+ end
3054
+ ",
3055
+ );
3056
+ context.resolve();
3057
+
3058
+ assert_completion_eq!(
3059
+ context,
3060
+ CompletionReceiver::MethodCall {
3061
+ self_decl_id: None,
3062
+ receiver_decl_id: DeclarationId::from("Child"),
3063
+ },
3064
+ [] as [&str; 0]
3065
+ );
3066
+ }
3067
+
3068
+ #[test]
3069
+ fn namespace_access_completion_excludes_private_constant() {
3070
+ let mut context = GraphTest::new();
3071
+ context.index_uri(
3072
+ "file:///foo.rb",
3073
+ "
3074
+ class Foo
3075
+ PUB = 1
3076
+ PRIV = 2
3077
+ private_constant :PRIV
3078
+ end
3079
+ ",
3080
+ );
3081
+ context.resolve();
3082
+
3083
+ assert_completion_eq!(
3084
+ context,
3085
+ CompletionReceiver::NamespaceAccess {
3086
+ self_decl_id: None,
3087
+ namespace_decl_id: DeclarationId::from("Foo"),
3088
+ },
3089
+ ["Foo::PUB"]
3090
+ );
3091
+ }
3092
+
3093
+ #[test]
3094
+ fn namespace_access_completion_excludes_inherited_private_constant() {
3095
+ let mut context = GraphTest::new();
3096
+ context.index_uri(
3097
+ "file:///foo.rb",
3098
+ "
3099
+ class Parent
3100
+ SECRET = 1
3101
+ private_constant :SECRET
3102
+ end
3103
+
3104
+ class Child < Parent
3105
+ end
3106
+ ",
3107
+ );
3108
+ context.resolve();
3109
+
3110
+ assert_completion_eq!(
3111
+ context,
3112
+ CompletionReceiver::NamespaceAccess {
3113
+ self_decl_id: None,
3114
+ namespace_decl_id: DeclarationId::from("Child"),
3115
+ },
3116
+ [] as [&str; 0]
3117
+ );
3118
+ }
3119
+
3120
+ #[test]
3121
+ fn expression_completion_includes_private_constant_within_lexical_scope() {
3122
+ let mut context = GraphTest::new();
3123
+ context.index_uri(
3124
+ "file:///foo.rb",
3125
+ "
3126
+ class Foo
3127
+ SECRET = 1
3128
+ private_constant :SECRET
3129
+
3130
+ def use_it
3131
+ # completion here
3132
+ end
3133
+ end
3134
+ ",
3135
+ );
3136
+ context.resolve();
3137
+
3138
+ let foo_name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
3139
+ assert_declaration_completion_eq!(
3140
+ context,
3141
+ CompletionReceiver::Expression {
3142
+ self_decl_id: None,
3143
+ nesting_name_id: foo_name_id,
3144
+ },
3145
+ [
3146
+ "Module",
3147
+ "Class",
3148
+ "Object",
3149
+ "BasicObject",
3150
+ "Kernel",
3151
+ "Foo",
3152
+ "Foo::SECRET",
3153
+ "Foo#use_it()"
3154
+ ]
3155
+ );
3156
+ }
1841
3157
  }