rubydex 0.2.3 → 0.2.5

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.
@@ -15,7 +15,7 @@ use crate::model::name::{Name, NameRef, ParentScope, ResolvedName};
15
15
  use crate::model::references::{ConstantReference, MethodRef};
16
16
  use crate::model::string_ref::StringRef;
17
17
  use crate::model::visibility::Visibility;
18
- use crate::stats;
18
+ use crate::{query, stats};
19
19
 
20
20
  /// An entity whose validity depends on a particular `NameId`.
21
21
  /// Used as the value type in the `name_dependents` reverse index.
@@ -650,7 +650,7 @@ impl Graph {
650
650
  let definitions = declaration.definitions();
651
651
 
652
652
  match declaration {
653
- Declaration::Namespace(Namespace::Class(_) | Namespace::Module(_))
653
+ Declaration::Namespace(Namespace::Class(_) | Namespace::Module(_) | Namespace::Todo(_))
654
654
  | Declaration::Constant(_)
655
655
  | Declaration::ConstantAlias(_) => {
656
656
  for def_id in definitions.iter().rev() {
@@ -661,25 +661,42 @@ impl Graph {
661
661
  Some(Visibility::Public)
662
662
  }
663
663
  Declaration::Method(_) => {
664
+ let mut latest_alias: Option<DefinitionId> = None;
665
+
664
666
  for def_id in definitions.iter().rev() {
665
667
  let Some(definition) = self.definitions.get(def_id) else {
666
668
  continue;
667
669
  };
670
+
668
671
  let visibility = match definition {
669
672
  Definition::MethodVisibility(vis) => Some(*vis.visibility()),
670
673
  Definition::Method(method) => Some(*method.visibility()),
671
674
  Definition::AttrAccessor(attr) => Some(*attr.visibility()),
672
675
  Definition::AttrReader(attr) => Some(*attr.visibility()),
673
676
  Definition::AttrWriter(attr) => Some(*attr.visibility()),
677
+ Definition::MethodAlias(_) => {
678
+ if latest_alias.is_none() {
679
+ latest_alias = Some(*def_id);
680
+ }
681
+ None
682
+ }
674
683
  _ => None,
675
684
  };
685
+
676
686
  if visibility.is_some() {
677
687
  return visibility;
678
688
  }
679
689
  }
680
- None
690
+
691
+ if let Some(alias_def_id) = latest_alias
692
+ && let Ok(target_id) = query::follow_method_alias(self, alias_def_id)
693
+ {
694
+ return self.visibility(&target_id);
695
+ }
696
+
697
+ Some(Visibility::Public)
681
698
  }
682
- Declaration::Namespace(Namespace::SingletonClass(_) | Namespace::Todo(_))
699
+ Declaration::Namespace(Namespace::SingletonClass(_))
683
700
  | Declaration::GlobalVariable(_)
684
701
  | Declaration::InstanceVariable(_)
685
702
  | Declaration::ClassVariable(_) => None,
@@ -1,4 +1,8 @@
1
- use crate::{assert_mem_size, model::id::Id};
1
+ use crate::{
2
+ assert_mem_size,
3
+ model::{definitions::Receiver, id::Id},
4
+ offset::Offset,
5
+ };
2
6
 
3
7
  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
4
8
  pub struct DeclarationMarker;
@@ -12,6 +16,28 @@ pub struct DefinitionMarker;
12
16
  pub type DefinitionId = Id<DefinitionMarker>;
13
17
  assert_mem_size!(DefinitionId, 8);
14
18
 
19
+ #[must_use]
20
+ pub fn namespace_definition_id(uri_id: UriId, offset: &Offset, name_id: NameId) -> DefinitionId {
21
+ DefinitionId::from(&format!("{}{}{}", *uri_id, offset.start(), *name_id))
22
+ }
23
+
24
+ #[must_use]
25
+ pub fn method_definition_id(
26
+ uri_id: UriId,
27
+ offset: &Offset,
28
+ str_id: StringId,
29
+ receiver: Option<&Receiver>,
30
+ ) -> DefinitionId {
31
+ let mut formatted_id = format!("{}{}{}", *uri_id, offset.start(), *str_id);
32
+ if let Some(receiver) = receiver {
33
+ match receiver {
34
+ Receiver::SelfReceiver(def_id) => formatted_id.push_str(&def_id.to_string()),
35
+ Receiver::ConstantReceiver(name_id) => formatted_id.push_str(&name_id.to_string()),
36
+ }
37
+ }
38
+ DefinitionId::from(&formatted_id)
39
+ }
40
+
15
41
  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
16
42
  pub struct UriMarker;
17
43
  // UriId represents the ID of a URI, which is the unique identifier for a document
@@ -0,0 +1,519 @@
1
+ //! Converts an `OperationBuilderResult` into a `LocalGraph` by walking operations and creating definitions.
2
+ //!
3
+ //! This is the second phase of the two-phase operation pipeline:
4
+ //! 1. `RubyOperationBuilder` parses source → produces ordered operations
5
+ //! 2. `apply_operations` walks operations → creates definitions in a `LocalGraph`
6
+ //!
7
+ //! The applier maintains its own scope stack to derive `lexical_nesting_id` for definitions.
8
+ //! Operations carry only their own data; scope context comes from Enter/Exit scope operations.
9
+
10
+ use std::collections::HashMap;
11
+
12
+ use crate::indexing::local_graph::LocalGraph;
13
+ use crate::model::definitions::{
14
+ AttrAccessorDefinition, AttrReaderDefinition, AttrWriterDefinition, ClassDefinition, ClassVariableDefinition,
15
+ ConstantAliasDefinition, ConstantDefinition, ConstantVisibilityDefinition, Definition, ExtendDefinition,
16
+ GlobalVariableAliasDefinition, GlobalVariableDefinition, IncludeDefinition, InstanceVariableDefinition,
17
+ MethodAliasDefinition, MethodDefinition, MethodVisibilityDefinition, Mixin, ModuleDefinition, PrependDefinition,
18
+ Receiver, SingletonClassDefinition,
19
+ };
20
+ use crate::model::ids::{ConstantReferenceId, DefinitionId, NameId};
21
+ use crate::model::references::{ConstantReference, MethodRef};
22
+ use crate::model::visibility::Visibility;
23
+ use crate::operation::ruby_builder::OperationBuilderResult;
24
+ use crate::operation::{
25
+ AliasConstant, AliasGlobalVariable, AliasMethod, AttrKind, DefineAttribute, DefineClassVariable, DefineConstant,
26
+ DefineGlobalVariable, DefineInstanceVariable, EnterClass, EnterMethod, EnterModule, EnterSingletonClass, MixinKind,
27
+ Operation, ReferenceConstant, ReferenceMethod, SetConstantVisibility, SetMethodVisibility, Target,
28
+ };
29
+
30
+ enum ApplierScope {
31
+ Namespace {
32
+ definition_id: DefinitionId,
33
+ is_lexical_scope: bool,
34
+ },
35
+ Method {
36
+ definition_id: DefinitionId,
37
+ },
38
+ }
39
+
40
+ struct OperationApplier {
41
+ local_graph: LocalGraph,
42
+ scope_stack: Vec<ApplierScope>,
43
+ scope_visibility: HashMap<Option<DefinitionId>, Visibility>,
44
+ // Maps the most recently emitted ReferenceConstant per name. The builder emits
45
+ // ReferenceConstant immediately before the operation that consumes it (Mixin,
46
+ // EnterClass superclass, SetConstantVisibility), so the last entry always wins.
47
+ constant_ref_ids: HashMap<NameId, ConstantReferenceId>,
48
+ }
49
+
50
+ impl OperationApplier {
51
+ fn current_owner_id(&self) -> Option<DefinitionId> {
52
+ self.scope_stack.iter().rev().find_map(|scope| match scope {
53
+ ApplierScope::Namespace { definition_id, .. } => Some(*definition_id),
54
+ ApplierScope::Method { .. } => None,
55
+ })
56
+ }
57
+
58
+ fn current_lexical_scope_id(&self) -> Option<DefinitionId> {
59
+ self.scope_stack.iter().rev().find_map(|scope| match scope {
60
+ ApplierScope::Namespace {
61
+ definition_id,
62
+ is_lexical_scope: true,
63
+ } => Some(*definition_id),
64
+ _ => None,
65
+ })
66
+ }
67
+
68
+ fn current_scope_id(&self) -> Option<DefinitionId> {
69
+ self.scope_stack.last().map(|scope| match scope {
70
+ ApplierScope::Namespace { definition_id, .. } | ApplierScope::Method { definition_id } => *definition_id,
71
+ })
72
+ }
73
+
74
+ fn resolve_receiver(&self, receiver: Option<&Target>) -> Option<Receiver> {
75
+ let current_owner_id = self.current_owner_id();
76
+ match receiver {
77
+ Some(Target::ExplicitSelf) => current_owner_id.map(Receiver::SelfReceiver),
78
+ Some(Target::Constant(name_id)) => Some(Receiver::ConstantReceiver(*name_id)),
79
+ Some(Target::Other) | None => None,
80
+ }
81
+ }
82
+
83
+ fn resolve_visibility(&self, has_receiver: bool) -> Visibility {
84
+ if has_receiver {
85
+ return Visibility::Public;
86
+ }
87
+ let scope = self.current_owner_id();
88
+ let default = self
89
+ .scope_visibility
90
+ .get(&scope)
91
+ .copied()
92
+ .unwrap_or(if scope.is_none() {
93
+ Visibility::Private
94
+ } else {
95
+ Visibility::Public
96
+ });
97
+ match default {
98
+ Visibility::ModuleFunction => Visibility::Private,
99
+ v => v,
100
+ }
101
+ }
102
+
103
+ fn add_member(&mut self, owner_id: Option<DefinitionId>, member_id: DefinitionId) {
104
+ let Some(owner_id) = owner_id else {
105
+ return;
106
+ };
107
+
108
+ let Some(owner) = self.local_graph.get_definition_mut(owner_id) else {
109
+ return;
110
+ };
111
+
112
+ match owner {
113
+ Definition::Class(class) => class.add_member(member_id),
114
+ Definition::Module(module) => module.add_member(member_id),
115
+ Definition::SingletonClass(singleton) => singleton.add_member(member_id),
116
+ _ => {}
117
+ }
118
+ }
119
+ }
120
+
121
+ impl OperationApplier {
122
+ fn apply_operation(&mut self, op: Operation) {
123
+ match op {
124
+ Operation::EnterClass(op) => self.apply_enter_class(op),
125
+ Operation::EnterModule(op) => self.apply_enter_module(op),
126
+ Operation::EnterSingletonClass(op) => self.apply_enter_singleton_class(op),
127
+ Operation::EnterMethod(op) => self.apply_enter_method(op),
128
+ Operation::ExitScope => {
129
+ debug_assert!(!self.scope_stack.is_empty(), "ExitScope with empty scope stack");
130
+ self.scope_stack.pop();
131
+ }
132
+ Operation::AliasMethod(op) => self.apply_alias_method(op),
133
+ Operation::SetMethodVisibility(op) => self.apply_set_method_visibility(op),
134
+ Operation::SetDefaultVisibility(op) => {
135
+ let scope = self.current_owner_id();
136
+ self.scope_visibility.insert(scope, op.visibility);
137
+ }
138
+ Operation::DefineConstant(op) => self.apply_define_constant(op),
139
+ Operation::AliasConstant(op) => self.apply_alias_constant(op),
140
+ Operation::SetConstantVisibility(op) => self.apply_set_constant_visibility(op),
141
+ Operation::Mixin(ref op) => self.apply_mixin(op),
142
+ Operation::DefineAttribute(op) => self.apply_define_attribute(op),
143
+ Operation::DefineGlobalVariable(op) => self.apply_define_global_variable(op),
144
+ Operation::DefineInstanceVariable(op) => self.apply_define_instance_variable(op),
145
+ Operation::DefineClassVariable(op) => self.apply_define_class_variable(op),
146
+ Operation::AliasGlobalVariable(op) => self.apply_alias_global_variable(op),
147
+ Operation::ReferenceConstant(op) => self.apply_reference_constant(op),
148
+ Operation::ReferenceMethod(op) => self.apply_reference_method(op),
149
+ }
150
+ }
151
+
152
+ fn apply_enter_class(&mut self, op: EnterClass) {
153
+ let lexical_nesting_id = self.current_lexical_scope_id();
154
+ let superclass_ref = op.superclass_name.and_then(|n| self.constant_ref_ids.get(&n).copied());
155
+ let def = ClassDefinition::new(
156
+ op.name_id,
157
+ op.uri_id,
158
+ op.offset,
159
+ op.name_offset,
160
+ op.comments,
161
+ op.flags,
162
+ lexical_nesting_id,
163
+ superclass_ref,
164
+ );
165
+ let def_id = self.local_graph.add_definition(Definition::Class(Box::new(def)));
166
+ self.add_member(lexical_nesting_id, def_id);
167
+ self.scope_stack.push(ApplierScope::Namespace {
168
+ definition_id: def_id,
169
+ is_lexical_scope: op.is_lexical_scope,
170
+ });
171
+ }
172
+
173
+ fn apply_enter_module(&mut self, op: EnterModule) {
174
+ let lexical_nesting_id = self.current_lexical_scope_id();
175
+ let def = ModuleDefinition::new(
176
+ op.name_id,
177
+ op.uri_id,
178
+ op.offset,
179
+ op.name_offset,
180
+ op.comments,
181
+ op.flags,
182
+ lexical_nesting_id,
183
+ );
184
+ let def_id = self.local_graph.add_definition(Definition::Module(Box::new(def)));
185
+ self.add_member(lexical_nesting_id, def_id);
186
+ self.scope_stack.push(ApplierScope::Namespace {
187
+ definition_id: def_id,
188
+ is_lexical_scope: op.is_lexical_scope,
189
+ });
190
+ }
191
+
192
+ fn apply_enter_singleton_class(&mut self, op: EnterSingletonClass) {
193
+ let lexical_nesting_id = self.current_lexical_scope_id();
194
+ let def = SingletonClassDefinition::new(
195
+ op.name_id,
196
+ op.uri_id,
197
+ op.offset,
198
+ op.name_offset,
199
+ op.comments,
200
+ op.flags,
201
+ lexical_nesting_id,
202
+ );
203
+ let def_id = self
204
+ .local_graph
205
+ .add_definition(Definition::SingletonClass(Box::new(def)));
206
+ self.add_member(lexical_nesting_id, def_id);
207
+ self.scope_stack.push(ApplierScope::Namespace {
208
+ definition_id: def_id,
209
+ is_lexical_scope: true,
210
+ });
211
+ }
212
+
213
+ fn apply_enter_method(&mut self, op: EnterMethod) {
214
+ let lexical_nesting_id = self.current_owner_id();
215
+ let has_receiver = op.receiver.is_some();
216
+ let receiver = self.resolve_receiver(op.receiver.as_ref());
217
+ let visibility = self.resolve_visibility(has_receiver);
218
+ let def = MethodDefinition::new(
219
+ op.str_id,
220
+ op.uri_id,
221
+ op.offset,
222
+ op.comments,
223
+ op.flags,
224
+ lexical_nesting_id,
225
+ op.signatures,
226
+ visibility,
227
+ receiver,
228
+ );
229
+ let def_id = self.local_graph.add_definition(Definition::Method(Box::new(def)));
230
+ self.add_member(lexical_nesting_id, def_id);
231
+ self.scope_stack.push(ApplierScope::Method { definition_id: def_id });
232
+ }
233
+
234
+ fn apply_alias_method(&mut self, op: AliasMethod) {
235
+ let lexical_nesting_id = self.current_owner_id();
236
+ let receiver = self.resolve_receiver(op.receiver.as_ref());
237
+ let def = MethodAliasDefinition::new(
238
+ op.new_name_str_id,
239
+ op.old_name_str_id,
240
+ op.uri_id,
241
+ op.offset,
242
+ op.comments,
243
+ op.flags,
244
+ lexical_nesting_id,
245
+ receiver,
246
+ );
247
+ let def_id = self.local_graph.add_definition(Definition::MethodAlias(Box::new(def)));
248
+ self.add_member(lexical_nesting_id, def_id);
249
+ }
250
+
251
+ fn apply_set_method_visibility(&mut self, op: SetMethodVisibility) {
252
+ let lexical_nesting_id = self.current_owner_id();
253
+ let def = MethodVisibilityDefinition::new(
254
+ op.str_id,
255
+ op.visibility,
256
+ op.uri_id,
257
+ op.offset,
258
+ Box::default(),
259
+ op.flags,
260
+ lexical_nesting_id,
261
+ );
262
+ let def_id = self
263
+ .local_graph
264
+ .add_definition(Definition::MethodVisibility(Box::new(def)));
265
+ self.add_member(lexical_nesting_id, def_id);
266
+ }
267
+
268
+ fn apply_define_constant(&mut self, op: DefineConstant) {
269
+ let lexical_nesting_id = self.current_lexical_scope_id();
270
+ let def = ConstantDefinition::new(
271
+ op.name_id,
272
+ op.uri_id,
273
+ op.offset,
274
+ op.comments,
275
+ op.flags,
276
+ lexical_nesting_id,
277
+ );
278
+ let def_id = self.local_graph.add_definition(Definition::Constant(Box::new(def)));
279
+ self.add_member(lexical_nesting_id, def_id);
280
+ }
281
+
282
+ fn apply_alias_constant(&mut self, op: AliasConstant) {
283
+ let lexical_nesting_id = self.current_lexical_scope_id();
284
+ let constant = ConstantDefinition::new(
285
+ op.name_id,
286
+ op.uri_id,
287
+ op.offset,
288
+ op.comments,
289
+ op.flags,
290
+ lexical_nesting_id,
291
+ );
292
+ let def = ConstantAliasDefinition::new(op.target_name_id, constant);
293
+ let def_id = self
294
+ .local_graph
295
+ .add_definition(Definition::ConstantAlias(Box::new(def)));
296
+ self.add_member(lexical_nesting_id, def_id);
297
+ }
298
+
299
+ fn apply_set_constant_visibility(&mut self, op: SetConstantVisibility) {
300
+ let lexical_nesting_id = self.current_owner_id();
301
+ let receiver = match op.receiver {
302
+ Some(Target::Constant(name_id)) => Some(name_id),
303
+ Some(Target::ExplicitSelf | Target::Other) | None => None,
304
+ };
305
+ let def = ConstantVisibilityDefinition::new(
306
+ receiver,
307
+ op.target,
308
+ op.visibility,
309
+ op.uri_id,
310
+ op.offset,
311
+ op.comments,
312
+ op.flags,
313
+ lexical_nesting_id,
314
+ );
315
+ let def_id = self
316
+ .local_graph
317
+ .add_definition(Definition::ConstantVisibility(Box::new(def)));
318
+ self.add_member(lexical_nesting_id, def_id);
319
+ }
320
+
321
+ fn apply_mixin(&mut self, op: &crate::operation::Mixin) {
322
+ let Some(owner_id) = self.current_owner_id() else {
323
+ return;
324
+ };
325
+
326
+ let constant_reference_id = match op.target {
327
+ Target::Constant(name_id) => self.constant_ref_ids.get(&name_id).copied(),
328
+ Target::ExplicitSelf | Target::Other => None,
329
+ };
330
+
331
+ let Some(constant_reference_id) = constant_reference_id else {
332
+ return;
333
+ };
334
+
335
+ let mixin = match op.kind {
336
+ MixinKind::Include => Mixin::Include(IncludeDefinition::new(constant_reference_id)),
337
+ MixinKind::Prepend => Mixin::Prepend(PrependDefinition::new(constant_reference_id)),
338
+ MixinKind::Extend => Mixin::Extend(ExtendDefinition::new(constant_reference_id)),
339
+ };
340
+
341
+ if let Some(owner) = self.local_graph.get_definition_mut(owner_id) {
342
+ match owner {
343
+ Definition::Class(class) => class.add_mixin(mixin),
344
+ Definition::Module(module) => module.add_mixin(mixin),
345
+ Definition::SingletonClass(singleton) => singleton.add_mixin(mixin),
346
+ _ => {}
347
+ }
348
+ }
349
+ }
350
+
351
+ fn apply_define_attribute(&mut self, op: DefineAttribute) {
352
+ let lexical_nesting_id = self.current_scope_id();
353
+ let visibility = self.resolve_visibility(false);
354
+ let def_id = match op.kind {
355
+ AttrKind::Accessor => {
356
+ let def = AttrAccessorDefinition::new(
357
+ op.str_id,
358
+ op.uri_id,
359
+ op.offset,
360
+ op.comments,
361
+ op.flags,
362
+ lexical_nesting_id,
363
+ visibility,
364
+ );
365
+ self.local_graph.add_definition(Definition::AttrAccessor(Box::new(def)))
366
+ }
367
+ AttrKind::Reader => {
368
+ let def = AttrReaderDefinition::new(
369
+ op.str_id,
370
+ op.uri_id,
371
+ op.offset,
372
+ op.comments,
373
+ op.flags,
374
+ lexical_nesting_id,
375
+ visibility,
376
+ );
377
+ self.local_graph.add_definition(Definition::AttrReader(Box::new(def)))
378
+ }
379
+ AttrKind::Writer => {
380
+ let def = AttrWriterDefinition::new(
381
+ op.str_id,
382
+ op.uri_id,
383
+ op.offset,
384
+ op.comments,
385
+ op.flags,
386
+ lexical_nesting_id,
387
+ visibility,
388
+ );
389
+ self.local_graph.add_definition(Definition::AttrWriter(Box::new(def)))
390
+ }
391
+ };
392
+ self.add_member(lexical_nesting_id, def_id);
393
+ }
394
+
395
+ fn apply_define_global_variable(&mut self, op: DefineGlobalVariable) {
396
+ let lexical_nesting_id = self.current_scope_id();
397
+ let member_owner_id = self.current_owner_id();
398
+ let def = GlobalVariableDefinition::new(
399
+ op.str_id,
400
+ op.uri_id,
401
+ op.offset,
402
+ op.comments,
403
+ op.flags,
404
+ lexical_nesting_id,
405
+ );
406
+ let def_id = self
407
+ .local_graph
408
+ .add_definition(Definition::GlobalVariable(Box::new(def)));
409
+ self.add_member(member_owner_id, def_id);
410
+ }
411
+
412
+ fn apply_define_instance_variable(&mut self, op: DefineInstanceVariable) {
413
+ let lexical_nesting_id = self.current_scope_id();
414
+ let member_owner_id = self.current_owner_id();
415
+ let def = InstanceVariableDefinition::new(
416
+ op.str_id,
417
+ op.uri_id,
418
+ op.offset,
419
+ op.comments,
420
+ op.flags,
421
+ lexical_nesting_id,
422
+ );
423
+ let def_id = self
424
+ .local_graph
425
+ .add_definition(Definition::InstanceVariable(Box::new(def)));
426
+ self.add_member(member_owner_id, def_id);
427
+ }
428
+
429
+ fn apply_define_class_variable(&mut self, op: DefineClassVariable) {
430
+ let lexical_nesting_id = self.current_lexical_scope_id();
431
+ let member_owner_id = self.current_owner_id();
432
+ let def = ClassVariableDefinition::new(
433
+ op.str_id,
434
+ op.uri_id,
435
+ op.offset,
436
+ op.comments,
437
+ op.flags,
438
+ lexical_nesting_id,
439
+ );
440
+ let def_id = self
441
+ .local_graph
442
+ .add_definition(Definition::ClassVariable(Box::new(def)));
443
+ self.add_member(member_owner_id, def_id);
444
+ }
445
+
446
+ fn apply_alias_global_variable(&mut self, op: AliasGlobalVariable) {
447
+ let lexical_nesting_id = self.current_scope_id();
448
+ let def = GlobalVariableAliasDefinition::new(
449
+ op.new_name_str_id,
450
+ op.old_name_str_id,
451
+ op.uri_id,
452
+ op.offset,
453
+ op.comments,
454
+ op.flags,
455
+ lexical_nesting_id,
456
+ );
457
+ self.local_graph
458
+ .add_definition(Definition::GlobalVariableAlias(Box::new(def)));
459
+ }
460
+
461
+ fn apply_reference_constant(&mut self, op: ReferenceConstant) {
462
+ let ref_id = self
463
+ .local_graph
464
+ .add_constant_reference(ConstantReference::new(op.name_id, op.uri_id, op.offset));
465
+ self.constant_ref_ids.insert(op.name_id, ref_id);
466
+ }
467
+
468
+ fn apply_reference_method(&mut self, op: ReferenceMethod) {
469
+ let receiver = match op.receiver {
470
+ Some(Target::Constant(name_id)) => Some(name_id),
471
+ Some(Target::ExplicitSelf | Target::Other) | None => None,
472
+ };
473
+ self.local_graph
474
+ .add_method_reference(MethodRef::new(op.str_id, op.uri_id, op.offset, receiver));
475
+ }
476
+ }
477
+
478
+ /// Converts an `OperationBuilderResult` into a `LocalGraph`.
479
+ ///
480
+ /// Walks the operations in order, creating `Definition` objects and registering members/mixins.
481
+ /// Scope context is derived from the scope stack maintained by Enter/Exit operations.
482
+ #[must_use]
483
+ pub fn apply_operations(result: OperationBuilderResult) -> LocalGraph {
484
+ let OperationBuilderResult {
485
+ uri_id,
486
+ document,
487
+ operations,
488
+ strings,
489
+ names,
490
+ } = result;
491
+
492
+ let mut applier = OperationApplier {
493
+ local_graph: LocalGraph::from_parts(uri_id, document, strings, names),
494
+ scope_stack: Vec::new(),
495
+ scope_visibility: HashMap::new(),
496
+ constant_ref_ids: HashMap::new(),
497
+ };
498
+
499
+ for op in operations {
500
+ applier.apply_operation(op);
501
+ }
502
+
503
+ applier.local_graph
504
+ }
505
+
506
+ #[cfg(test)]
507
+ fn backend() -> crate::indexing::IndexerBackend {
508
+ crate::indexing::IndexerBackend::OperationBuilder
509
+ }
510
+
511
+ #[cfg(test)]
512
+ #[allow(clippy::duplicate_mod)]
513
+ #[path = "../indexing/ruby_indexer_tests.rs"]
514
+ mod applier_tests;
515
+
516
+ #[cfg(test)]
517
+ #[allow(clippy::duplicate_mod)]
518
+ #[path = "../resolution_tests.rs"]
519
+ mod resolution_tests;