rubydex 0.2.4-aarch64-linux → 0.2.6-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -16
  3. data/THIRD_PARTY_LICENSES.html +6 -6
  4. data/exe/rubydex_mcp +17 -0
  5. data/ext/rubydex/definition.c +89 -2
  6. data/ext/rubydex/document.c +36 -0
  7. data/ext/rubydex/extconf.rb +8 -0
  8. data/ext/rubydex/graph.c +32 -18
  9. data/ext/rubydex/handle.h +21 -5
  10. data/lib/rubydex/3.2/rubydex.so +0 -0
  11. data/lib/rubydex/3.3/rubydex.so +0 -0
  12. data/lib/rubydex/3.4/rubydex.so +0 -0
  13. data/lib/rubydex/4.0/rubydex.so +0 -0
  14. data/lib/rubydex/bin/rubydex_mcp +0 -0
  15. data/lib/rubydex/declaration.rb +3 -3
  16. data/lib/rubydex/errors.rb +8 -0
  17. data/lib/rubydex/graph.rb +3 -1
  18. data/lib/rubydex/librubydex_sys.so +0 -0
  19. data/lib/rubydex/location.rb +24 -0
  20. data/lib/rubydex/version.rb +1 -1
  21. data/lib/rubydex.rb +1 -0
  22. data/rbi/rubydex.rbi +37 -14
  23. data/rust/Cargo.lock +3 -3
  24. data/rust/rubydex/Cargo.toml +7 -1
  25. data/rust/rubydex/src/dot.rs +609 -0
  26. data/rust/rubydex/src/indexing/local_graph.rs +38 -0
  27. data/rust/rubydex/src/indexing/rbs_indexer.rs +19 -1
  28. data/rust/rubydex/src/indexing/ruby_indexer.rs +10 -0
  29. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +5 -1
  30. data/rust/rubydex/src/indexing.rs +38 -12
  31. data/rust/rubydex/src/lib.rs +2 -1
  32. data/rust/rubydex/src/listing.rs +14 -3
  33. data/rust/rubydex/src/main.rs +35 -7
  34. data/rust/rubydex/src/model/built_in.rs +5 -2
  35. data/rust/rubydex/src/model/comment.rs +2 -0
  36. data/rust/rubydex/src/model/declaration.rs +1 -0
  37. data/rust/rubydex/src/model/definitions.rs +20 -19
  38. data/rust/rubydex/src/model/document.rs +2 -0
  39. data/rust/rubydex/src/model/encoding.rs +2 -0
  40. data/rust/rubydex/src/model/graph.rs +51 -13
  41. data/rust/rubydex/src/model/identity_maps.rs +3 -0
  42. data/rust/rubydex/src/model/ids.rs +27 -1
  43. data/rust/rubydex/src/model/keywords.rs +3 -0
  44. data/rust/rubydex/src/model/name.rs +2 -0
  45. data/rust/rubydex/src/model/string_ref.rs +2 -0
  46. data/rust/rubydex/src/model/visibility.rs +3 -0
  47. data/rust/rubydex/src/operation/applier.rs +520 -0
  48. data/rust/rubydex/src/operation/mod.rs +285 -0
  49. data/rust/rubydex/src/operation/printer.rs +260 -0
  50. data/rust/rubydex/src/operation/ruby_builder.rs +2919 -0
  51. data/rust/rubydex/src/query.rs +114 -33
  52. data/rust/rubydex/src/resolution.rs +22 -9
  53. data/rust/rubydex/src/resolution_tests.rs +349 -209
  54. data/rust/rubydex/src/test_utils/graph_test.rs +19 -4
  55. data/rust/rubydex/src/test_utils/local_graph_test.rs +7 -6
  56. data/rust/rubydex/tests/cli.rs +17 -61
  57. data/rust/rubydex-mcp/Cargo.toml +9 -3
  58. data/rust/rubydex-mcp/src/server.rs +5 -1
  59. data/rust/rubydex-sys/Cargo.toml +9 -2
  60. data/rust/rubydex-sys/src/definition_api.rs +96 -2
  61. data/rust/rubydex-sys/src/document_api.rs +28 -0
  62. data/rust/rubydex-sys/src/graph_api.rs +2 -4
  63. metadata +11 -4
  64. data/rust/rubydex/src/visualization/dot.rs +0 -192
  65. data/rust/rubydex/src/visualization.rs +0 -6
@@ -0,0 +1,520 @@
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.name_offset,
223
+ op.comments,
224
+ op.flags,
225
+ lexical_nesting_id,
226
+ op.signatures,
227
+ visibility,
228
+ receiver,
229
+ );
230
+ let def_id = self.local_graph.add_definition(Definition::Method(Box::new(def)));
231
+ self.add_member(lexical_nesting_id, def_id);
232
+ self.scope_stack.push(ApplierScope::Method { definition_id: def_id });
233
+ }
234
+
235
+ fn apply_alias_method(&mut self, op: AliasMethod) {
236
+ let lexical_nesting_id = self.current_owner_id();
237
+ let receiver = self.resolve_receiver(op.receiver.as_ref());
238
+ let def = MethodAliasDefinition::new(
239
+ op.new_name_str_id,
240
+ op.old_name_str_id,
241
+ op.uri_id,
242
+ op.offset,
243
+ op.comments,
244
+ op.flags,
245
+ lexical_nesting_id,
246
+ receiver,
247
+ );
248
+ let def_id = self.local_graph.add_definition(Definition::MethodAlias(Box::new(def)));
249
+ self.add_member(lexical_nesting_id, def_id);
250
+ }
251
+
252
+ fn apply_set_method_visibility(&mut self, op: SetMethodVisibility) {
253
+ let lexical_nesting_id = self.current_owner_id();
254
+ let def = MethodVisibilityDefinition::new(
255
+ op.str_id,
256
+ op.visibility,
257
+ op.uri_id,
258
+ op.offset,
259
+ Box::default(),
260
+ op.flags,
261
+ lexical_nesting_id,
262
+ );
263
+ let def_id = self
264
+ .local_graph
265
+ .add_definition(Definition::MethodVisibility(Box::new(def)));
266
+ self.add_member(lexical_nesting_id, def_id);
267
+ }
268
+
269
+ fn apply_define_constant(&mut self, op: DefineConstant) {
270
+ let lexical_nesting_id = self.current_lexical_scope_id();
271
+ let def = ConstantDefinition::new(
272
+ op.name_id,
273
+ op.uri_id,
274
+ op.offset,
275
+ op.comments,
276
+ op.flags,
277
+ lexical_nesting_id,
278
+ );
279
+ let def_id = self.local_graph.add_definition(Definition::Constant(Box::new(def)));
280
+ self.add_member(lexical_nesting_id, def_id);
281
+ }
282
+
283
+ fn apply_alias_constant(&mut self, op: AliasConstant) {
284
+ let lexical_nesting_id = self.current_lexical_scope_id();
285
+ let constant = ConstantDefinition::new(
286
+ op.name_id,
287
+ op.uri_id,
288
+ op.offset,
289
+ op.comments,
290
+ op.flags,
291
+ lexical_nesting_id,
292
+ );
293
+ let def = ConstantAliasDefinition::new(op.target_name_id, constant);
294
+ let def_id = self
295
+ .local_graph
296
+ .add_definition(Definition::ConstantAlias(Box::new(def)));
297
+ self.add_member(lexical_nesting_id, def_id);
298
+ }
299
+
300
+ fn apply_set_constant_visibility(&mut self, op: SetConstantVisibility) {
301
+ let lexical_nesting_id = self.current_owner_id();
302
+ let receiver = match op.receiver {
303
+ Some(Target::Constant(name_id)) => Some(name_id),
304
+ Some(Target::ExplicitSelf | Target::Other) | None => None,
305
+ };
306
+ let def = ConstantVisibilityDefinition::new(
307
+ receiver,
308
+ op.target,
309
+ op.visibility,
310
+ op.uri_id,
311
+ op.offset,
312
+ op.comments,
313
+ op.flags,
314
+ lexical_nesting_id,
315
+ );
316
+ let def_id = self
317
+ .local_graph
318
+ .add_definition(Definition::ConstantVisibility(Box::new(def)));
319
+ self.add_member(lexical_nesting_id, def_id);
320
+ }
321
+
322
+ fn apply_mixin(&mut self, op: &crate::operation::Mixin) {
323
+ let Some(owner_id) = self.current_owner_id() else {
324
+ return;
325
+ };
326
+
327
+ let constant_reference_id = match op.target {
328
+ Target::Constant(name_id) => self.constant_ref_ids.get(&name_id).copied(),
329
+ Target::ExplicitSelf | Target::Other => None,
330
+ };
331
+
332
+ let Some(constant_reference_id) = constant_reference_id else {
333
+ return;
334
+ };
335
+
336
+ let mixin = match op.kind {
337
+ MixinKind::Include => Mixin::Include(IncludeDefinition::new(constant_reference_id)),
338
+ MixinKind::Prepend => Mixin::Prepend(PrependDefinition::new(constant_reference_id)),
339
+ MixinKind::Extend => Mixin::Extend(ExtendDefinition::new(constant_reference_id)),
340
+ };
341
+
342
+ if let Some(owner) = self.local_graph.get_definition_mut(owner_id) {
343
+ match owner {
344
+ Definition::Class(class) => class.add_mixin(mixin),
345
+ Definition::Module(module) => module.add_mixin(mixin),
346
+ Definition::SingletonClass(singleton) => singleton.add_mixin(mixin),
347
+ _ => {}
348
+ }
349
+ }
350
+ }
351
+
352
+ fn apply_define_attribute(&mut self, op: DefineAttribute) {
353
+ let lexical_nesting_id = self.current_scope_id();
354
+ let visibility = self.resolve_visibility(false);
355
+ let def_id = match op.kind {
356
+ AttrKind::Accessor => {
357
+ let def = AttrAccessorDefinition::new(
358
+ op.str_id,
359
+ op.uri_id,
360
+ op.offset,
361
+ op.comments,
362
+ op.flags,
363
+ lexical_nesting_id,
364
+ visibility,
365
+ );
366
+ self.local_graph.add_definition(Definition::AttrAccessor(Box::new(def)))
367
+ }
368
+ AttrKind::Reader => {
369
+ let def = AttrReaderDefinition::new(
370
+ op.str_id,
371
+ op.uri_id,
372
+ op.offset,
373
+ op.comments,
374
+ op.flags,
375
+ lexical_nesting_id,
376
+ visibility,
377
+ );
378
+ self.local_graph.add_definition(Definition::AttrReader(Box::new(def)))
379
+ }
380
+ AttrKind::Writer => {
381
+ let def = AttrWriterDefinition::new(
382
+ op.str_id,
383
+ op.uri_id,
384
+ op.offset,
385
+ op.comments,
386
+ op.flags,
387
+ lexical_nesting_id,
388
+ visibility,
389
+ );
390
+ self.local_graph.add_definition(Definition::AttrWriter(Box::new(def)))
391
+ }
392
+ };
393
+ self.add_member(lexical_nesting_id, def_id);
394
+ }
395
+
396
+ fn apply_define_global_variable(&mut self, op: DefineGlobalVariable) {
397
+ let lexical_nesting_id = self.current_scope_id();
398
+ let member_owner_id = self.current_owner_id();
399
+ let def = GlobalVariableDefinition::new(
400
+ op.str_id,
401
+ op.uri_id,
402
+ op.offset,
403
+ op.comments,
404
+ op.flags,
405
+ lexical_nesting_id,
406
+ );
407
+ let def_id = self
408
+ .local_graph
409
+ .add_definition(Definition::GlobalVariable(Box::new(def)));
410
+ self.add_member(member_owner_id, def_id);
411
+ }
412
+
413
+ fn apply_define_instance_variable(&mut self, op: DefineInstanceVariable) {
414
+ let lexical_nesting_id = self.current_scope_id();
415
+ let member_owner_id = self.current_owner_id();
416
+ let def = InstanceVariableDefinition::new(
417
+ op.str_id,
418
+ op.uri_id,
419
+ op.offset,
420
+ op.comments,
421
+ op.flags,
422
+ lexical_nesting_id,
423
+ );
424
+ let def_id = self
425
+ .local_graph
426
+ .add_definition(Definition::InstanceVariable(Box::new(def)));
427
+ self.add_member(member_owner_id, def_id);
428
+ }
429
+
430
+ fn apply_define_class_variable(&mut self, op: DefineClassVariable) {
431
+ let lexical_nesting_id = self.current_lexical_scope_id();
432
+ let member_owner_id = self.current_owner_id();
433
+ let def = ClassVariableDefinition::new(
434
+ op.str_id,
435
+ op.uri_id,
436
+ op.offset,
437
+ op.comments,
438
+ op.flags,
439
+ lexical_nesting_id,
440
+ );
441
+ let def_id = self
442
+ .local_graph
443
+ .add_definition(Definition::ClassVariable(Box::new(def)));
444
+ self.add_member(member_owner_id, def_id);
445
+ }
446
+
447
+ fn apply_alias_global_variable(&mut self, op: AliasGlobalVariable) {
448
+ let lexical_nesting_id = self.current_scope_id();
449
+ let def = GlobalVariableAliasDefinition::new(
450
+ op.new_name_str_id,
451
+ op.old_name_str_id,
452
+ op.uri_id,
453
+ op.offset,
454
+ op.comments,
455
+ op.flags,
456
+ lexical_nesting_id,
457
+ );
458
+ self.local_graph
459
+ .add_definition(Definition::GlobalVariableAlias(Box::new(def)));
460
+ }
461
+
462
+ fn apply_reference_constant(&mut self, op: ReferenceConstant) {
463
+ let ref_id = self
464
+ .local_graph
465
+ .add_constant_reference(ConstantReference::new(op.name_id, op.uri_id, op.offset));
466
+ self.constant_ref_ids.insert(op.name_id, ref_id);
467
+ }
468
+
469
+ fn apply_reference_method(&mut self, op: ReferenceMethod) {
470
+ let receiver = match op.receiver {
471
+ Some(Target::Constant(name_id)) => Some(name_id),
472
+ Some(Target::ExplicitSelf | Target::Other) | None => None,
473
+ };
474
+ self.local_graph
475
+ .add_method_reference(MethodRef::new(op.str_id, op.uri_id, op.offset, receiver));
476
+ }
477
+ }
478
+
479
+ /// Converts an `OperationBuilderResult` into a `LocalGraph`.
480
+ ///
481
+ /// Walks the operations in order, creating `Definition` objects and registering members/mixins.
482
+ /// Scope context is derived from the scope stack maintained by Enter/Exit operations.
483
+ #[must_use]
484
+ pub fn apply_operations(result: OperationBuilderResult) -> LocalGraph {
485
+ let OperationBuilderResult {
486
+ uri_id,
487
+ document,
488
+ operations,
489
+ strings,
490
+ names,
491
+ } = result;
492
+
493
+ let mut applier = OperationApplier {
494
+ local_graph: LocalGraph::from_parts(uri_id, document, strings, names),
495
+ scope_stack: Vec::new(),
496
+ scope_visibility: HashMap::new(),
497
+ constant_ref_ids: HashMap::new(),
498
+ };
499
+
500
+ for op in operations {
501
+ applier.apply_operation(op);
502
+ }
503
+
504
+ applier.local_graph
505
+ }
506
+
507
+ #[cfg(test)]
508
+ fn backend() -> crate::indexing::IndexerBackend {
509
+ crate::indexing::IndexerBackend::OperationBuilder
510
+ }
511
+
512
+ #[cfg(test)]
513
+ #[allow(clippy::duplicate_mod)]
514
+ #[path = "../indexing/ruby_indexer_tests.rs"]
515
+ mod applier_tests;
516
+
517
+ #[cfg(test)]
518
+ #[allow(clippy::duplicate_mod)]
519
+ #[path = "../resolution_tests.rs"]
520
+ mod resolution_tests;