rubydex 0.1.0.beta12-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +23 -0
  3. data/README.md +125 -0
  4. data/THIRD_PARTY_LICENSES.html +4562 -0
  5. data/exe/rdx +47 -0
  6. data/ext/rubydex/declaration.c +453 -0
  7. data/ext/rubydex/declaration.h +23 -0
  8. data/ext/rubydex/definition.c +284 -0
  9. data/ext/rubydex/definition.h +28 -0
  10. data/ext/rubydex/diagnostic.c +6 -0
  11. data/ext/rubydex/diagnostic.h +11 -0
  12. data/ext/rubydex/document.c +97 -0
  13. data/ext/rubydex/document.h +10 -0
  14. data/ext/rubydex/extconf.rb +138 -0
  15. data/ext/rubydex/graph.c +681 -0
  16. data/ext/rubydex/graph.h +10 -0
  17. data/ext/rubydex/handle.h +44 -0
  18. data/ext/rubydex/location.c +22 -0
  19. data/ext/rubydex/location.h +15 -0
  20. data/ext/rubydex/reference.c +123 -0
  21. data/ext/rubydex/reference.h +15 -0
  22. data/ext/rubydex/rubydex.c +22 -0
  23. data/ext/rubydex/utils.c +108 -0
  24. data/ext/rubydex/utils.h +34 -0
  25. data/lib/rubydex/3.2/rubydex.so +0 -0
  26. data/lib/rubydex/3.3/rubydex.so +0 -0
  27. data/lib/rubydex/3.4/rubydex.so +0 -0
  28. data/lib/rubydex/4.0/rubydex.so +0 -0
  29. data/lib/rubydex/comment.rb +17 -0
  30. data/lib/rubydex/diagnostic.rb +21 -0
  31. data/lib/rubydex/failures.rb +15 -0
  32. data/lib/rubydex/graph.rb +98 -0
  33. data/lib/rubydex/keyword.rb +17 -0
  34. data/lib/rubydex/keyword_parameter.rb +13 -0
  35. data/lib/rubydex/librubydex_sys.so +0 -0
  36. data/lib/rubydex/location.rb +90 -0
  37. data/lib/rubydex/mixin.rb +22 -0
  38. data/lib/rubydex/version.rb +5 -0
  39. data/lib/rubydex.rb +23 -0
  40. data/rbi/rubydex.rbi +422 -0
  41. data/rust/Cargo.lock +1851 -0
  42. data/rust/Cargo.toml +29 -0
  43. data/rust/about.hbs +78 -0
  44. data/rust/about.toml +10 -0
  45. data/rust/rubydex/Cargo.toml +42 -0
  46. data/rust/rubydex/src/compile_assertions.rs +13 -0
  47. data/rust/rubydex/src/diagnostic.rs +110 -0
  48. data/rust/rubydex/src/errors.rs +28 -0
  49. data/rust/rubydex/src/indexing/local_graph.rs +224 -0
  50. data/rust/rubydex/src/indexing/rbs_indexer.rs +1551 -0
  51. data/rust/rubydex/src/indexing/ruby_indexer.rs +2329 -0
  52. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +4962 -0
  53. data/rust/rubydex/src/indexing.rs +210 -0
  54. data/rust/rubydex/src/integrity.rs +279 -0
  55. data/rust/rubydex/src/job_queue.rs +205 -0
  56. data/rust/rubydex/src/lib.rs +17 -0
  57. data/rust/rubydex/src/listing.rs +371 -0
  58. data/rust/rubydex/src/main.rs +160 -0
  59. data/rust/rubydex/src/model/built_in.rs +83 -0
  60. data/rust/rubydex/src/model/comment.rs +24 -0
  61. data/rust/rubydex/src/model/declaration.rs +671 -0
  62. data/rust/rubydex/src/model/definitions.rs +1682 -0
  63. data/rust/rubydex/src/model/document.rs +222 -0
  64. data/rust/rubydex/src/model/encoding.rs +22 -0
  65. data/rust/rubydex/src/model/graph.rs +3754 -0
  66. data/rust/rubydex/src/model/id.rs +110 -0
  67. data/rust/rubydex/src/model/identity_maps.rs +58 -0
  68. data/rust/rubydex/src/model/ids.rs +60 -0
  69. data/rust/rubydex/src/model/keywords.rs +256 -0
  70. data/rust/rubydex/src/model/name.rs +298 -0
  71. data/rust/rubydex/src/model/references.rs +111 -0
  72. data/rust/rubydex/src/model/string_ref.rs +50 -0
  73. data/rust/rubydex/src/model/visibility.rs +41 -0
  74. data/rust/rubydex/src/model.rs +15 -0
  75. data/rust/rubydex/src/offset.rs +147 -0
  76. data/rust/rubydex/src/position.rs +6 -0
  77. data/rust/rubydex/src/query.rs +1841 -0
  78. data/rust/rubydex/src/resolution.rs +6517 -0
  79. data/rust/rubydex/src/stats/memory.rs +71 -0
  80. data/rust/rubydex/src/stats/orphan_report.rs +264 -0
  81. data/rust/rubydex/src/stats/timer.rs +127 -0
  82. data/rust/rubydex/src/stats.rs +11 -0
  83. data/rust/rubydex/src/test_utils/context.rs +226 -0
  84. data/rust/rubydex/src/test_utils/graph_test.rs +730 -0
  85. data/rust/rubydex/src/test_utils/local_graph_test.rs +602 -0
  86. data/rust/rubydex/src/test_utils.rs +52 -0
  87. data/rust/rubydex/src/visualization/dot.rs +192 -0
  88. data/rust/rubydex/src/visualization.rs +6 -0
  89. data/rust/rubydex/tests/cli.rs +185 -0
  90. data/rust/rubydex-mcp/Cargo.toml +28 -0
  91. data/rust/rubydex-mcp/src/main.rs +48 -0
  92. data/rust/rubydex-mcp/src/server.rs +1145 -0
  93. data/rust/rubydex-mcp/src/tools.rs +49 -0
  94. data/rust/rubydex-mcp/tests/mcp.rs +302 -0
  95. data/rust/rubydex-sys/Cargo.toml +20 -0
  96. data/rust/rubydex-sys/build.rs +14 -0
  97. data/rust/rubydex-sys/cbindgen.toml +12 -0
  98. data/rust/rubydex-sys/src/declaration_api.rs +485 -0
  99. data/rust/rubydex-sys/src/definition_api.rs +443 -0
  100. data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
  101. data/rust/rubydex-sys/src/document_api.rs +85 -0
  102. data/rust/rubydex-sys/src/graph_api.rs +948 -0
  103. data/rust/rubydex-sys/src/lib.rs +79 -0
  104. data/rust/rubydex-sys/src/location_api.rs +79 -0
  105. data/rust/rubydex-sys/src/name_api.rs +135 -0
  106. data/rust/rubydex-sys/src/reference_api.rs +267 -0
  107. data/rust/rubydex-sys/src/utils.rs +70 -0
  108. data/rust/rustfmt.toml +2 -0
  109. metadata +159 -0
@@ -0,0 +1,730 @@
1
+ use super::normalize_indentation;
2
+ #[cfg(test)]
3
+ use crate::diagnostic::Rule;
4
+ use crate::indexing::{self, LanguageId};
5
+ use crate::model::graph::{Graph, NameDependent};
6
+ use crate::model::ids::{NameId, StringId};
7
+ use crate::resolution::Resolver;
8
+
9
+ #[derive(Default)]
10
+ pub struct GraphTest {
11
+ graph: Graph,
12
+ }
13
+
14
+ impl GraphTest {
15
+ #[must_use]
16
+ pub fn new() -> Self {
17
+ Self { graph: Graph::new() }
18
+ }
19
+
20
+ #[must_use]
21
+ pub fn graph(&self) -> &Graph {
22
+ &self.graph
23
+ }
24
+
25
+ #[must_use]
26
+ pub fn into_graph(self) -> Graph {
27
+ self.graph
28
+ }
29
+
30
+ /// Indexes a Ruby source
31
+ pub fn index_uri(&mut self, uri: &str, source: &str) {
32
+ let source = normalize_indentation(source);
33
+ indexing::index_source(&mut self.graph, uri, &source, &LanguageId::Ruby);
34
+ }
35
+
36
+ /// Indexes an RBS source
37
+ pub fn index_rbs_uri(&mut self, uri: &str, source: &str) {
38
+ let source = normalize_indentation(source);
39
+ indexing::index_source(&mut self.graph, uri, &source, &LanguageId::Rbs);
40
+ }
41
+
42
+ pub fn delete_uri(&mut self, uri: &str) {
43
+ self.graph.delete_document(uri);
44
+ }
45
+
46
+ pub fn resolve(&mut self) {
47
+ let mut resolver = Resolver::new(&mut self.graph);
48
+ resolver.resolve();
49
+ }
50
+
51
+ // Name dependents helpers (shared with LocalGraphTest for assert_dependents! macro)
52
+
53
+ /// # Panics
54
+ ///
55
+ /// Panics if no names match the given path.
56
+ #[must_use]
57
+ pub fn find_name_ids(&self, path: &str) -> Vec<NameId> {
58
+ let (parent, name) = match path.rsplit_once("::") {
59
+ Some((p, n)) => (Some(p), n),
60
+ None => (None, path),
61
+ };
62
+ let target_str_id = StringId::from(name);
63
+ let ids: Vec<NameId> = self
64
+ .graph()
65
+ .names()
66
+ .iter()
67
+ .filter(|(_, name_ref)| {
68
+ if *name_ref.str() != target_str_id {
69
+ return false;
70
+ }
71
+ match parent {
72
+ None => name_ref.parent_scope().as_ref().is_none(),
73
+ Some(p) => name_ref.parent_scope().as_ref().is_some_and(|ps_id| {
74
+ let ps = self.graph().names().get(ps_id).unwrap();
75
+ *ps.str() == StringId::from(p)
76
+ }),
77
+ }
78
+ })
79
+ .map(|(id, _)| *id)
80
+ .collect();
81
+ assert!(!ids.is_empty(), "could not find name `{path}`");
82
+ ids
83
+ }
84
+
85
+ #[must_use]
86
+ pub fn name_dependents_for(&self, name_id: NameId) -> Vec<NameDependent> {
87
+ self.graph()
88
+ .name_dependents()
89
+ .get(&name_id)
90
+ .cloned()
91
+ .unwrap_or_default()
92
+ }
93
+
94
+ /// # Panics
95
+ ///
96
+ /// Panics if the name's string is not in the strings map.
97
+ #[must_use]
98
+ pub fn name_str(&self, name_id: &NameId) -> Option<&str> {
99
+ self.graph()
100
+ .names()
101
+ .get(name_id)
102
+ .map(|n| self.graph().strings().get(n.str()).unwrap().as_str())
103
+ }
104
+
105
+ /// Returns the unqualified name string for a `NameDependent`, if available.
106
+ #[must_use]
107
+ pub fn dependent_name_str(&self, dep: &NameDependent) -> Option<&str> {
108
+ match dep {
109
+ NameDependent::ChildName(id) | NameDependent::NestedName(id) => self.name_str(id),
110
+ NameDependent::Definition(id) => self
111
+ .graph()
112
+ .definitions()
113
+ .get(id)
114
+ .and_then(|d| d.name_id())
115
+ .and_then(|name_id| self.name_str(name_id)),
116
+ NameDependent::Reference(id) => self
117
+ .graph()
118
+ .constant_references()
119
+ .get(id)
120
+ .and_then(|r| self.name_str(r.name_id())),
121
+ }
122
+ }
123
+
124
+ /// # Panics
125
+ ///
126
+ /// Panics if a diagnostic points to an invalid document
127
+ #[cfg(test)]
128
+ #[must_use]
129
+ pub fn format_diagnostics(&self, ignore_rules: &[Rule]) -> Vec<String> {
130
+ let mut diagnostics: Vec<_> = self
131
+ .graph()
132
+ .all_diagnostics()
133
+ .into_iter()
134
+ .filter(|d| !ignore_rules.contains(d.rule()))
135
+ .collect();
136
+
137
+ diagnostics.sort_by_key(|d| {
138
+ let uri = self.graph().documents().get(d.uri_id()).unwrap().uri();
139
+ (uri, d.offset())
140
+ });
141
+
142
+ diagnostics
143
+ .iter()
144
+ .map(|d| {
145
+ let document = self.graph().documents().get(d.uri_id()).unwrap();
146
+ d.formatted(document)
147
+ })
148
+ .collect()
149
+ }
150
+ }
151
+
152
+ #[cfg(test)]
153
+ #[macro_export]
154
+ macro_rules! assert_declaration_exists {
155
+ ($context:expr, $declaration_name:expr) => {
156
+ assert!(
157
+ $context
158
+ .graph()
159
+ .declarations()
160
+ .get(&$crate::model::ids::DeclarationId::from($declaration_name))
161
+ .is_some(),
162
+ "Expected declaration `{}` to exist",
163
+ $declaration_name
164
+ );
165
+ };
166
+ }
167
+
168
+ #[cfg(test)]
169
+ #[macro_export]
170
+ macro_rules! assert_declaration_kind_eq {
171
+ ($context:expr, $declaration_name:expr, $expected_kind:expr) => {
172
+ let declaration = $context
173
+ .graph()
174
+ .declarations()
175
+ .get(&$crate::model::ids::DeclarationId::from($declaration_name))
176
+ .unwrap();
177
+ assert_eq!(
178
+ declaration.kind(),
179
+ $expected_kind,
180
+ "Expected declaration `{}` to be a {}, got {}",
181
+ $declaration_name,
182
+ $expected_kind,
183
+ declaration.kind()
184
+ );
185
+ };
186
+ }
187
+
188
+ #[cfg(test)]
189
+ #[macro_export]
190
+ macro_rules! assert_declaration_does_not_exist {
191
+ ($context:expr, $declaration_name:expr) => {
192
+ assert!(
193
+ $context
194
+ .graph()
195
+ .declarations()
196
+ .get(&$crate::model::ids::DeclarationId::from($declaration_name))
197
+ .is_none(),
198
+ "Expected declaration `{}` to not exist",
199
+ $declaration_name
200
+ );
201
+ };
202
+ }
203
+
204
+ #[cfg(test)]
205
+ #[macro_export]
206
+ macro_rules! assert_declaration_definitions_count_eq {
207
+ ($context:expr, $declaration_name:expr, $expected_definitions:expr) => {
208
+ let declaration = $context
209
+ .graph()
210
+ .declarations()
211
+ .get(&$crate::model::ids::DeclarationId::from($declaration_name))
212
+ .unwrap();
213
+
214
+ assert_eq!(
215
+ declaration.definitions().len(),
216
+ $expected_definitions,
217
+ "Expected exactly {} definitions for `{}`, but got {}",
218
+ $expected_definitions,
219
+ $declaration_name,
220
+ declaration.definitions().len()
221
+ );
222
+ };
223
+ }
224
+
225
+ #[cfg(test)]
226
+ #[macro_export]
227
+ macro_rules! assert_constant_alias_target_eq {
228
+ ($context:expr, $alias_name:expr, $target_name:expr) => {{
229
+ let decl_id = $crate::model::ids::DeclarationId::from($alias_name);
230
+ let target = $context
231
+ .graph()
232
+ .alias_targets(&decl_id)
233
+ .and_then(|t| t.first().copied());
234
+ assert_eq!(
235
+ target,
236
+ Some($crate::model::ids::DeclarationId::from($target_name)),
237
+ "Expected alias '{}' to have primary target '{}'",
238
+ $alias_name,
239
+ $target_name
240
+ );
241
+ }};
242
+ }
243
+
244
+ #[cfg(test)]
245
+ #[macro_export]
246
+ macro_rules! assert_no_constant_alias_target {
247
+ ($context:expr, $alias_name:expr) => {{
248
+ let decl_id = $crate::model::ids::DeclarationId::from($alias_name);
249
+ let targets = $context.graph().alias_targets(&decl_id).unwrap_or_default();
250
+ assert!(
251
+ targets.is_empty(),
252
+ "Expected no alias target for '{}', but found {:?}",
253
+ $alias_name,
254
+ targets
255
+ );
256
+ }};
257
+ }
258
+
259
+ #[cfg(test)]
260
+ #[macro_export]
261
+ macro_rules! assert_alias_targets_contain {
262
+ ($context:expr, $alias_name:expr, $($target_name:expr),+ $(,)?) => {{
263
+ let decl_id = $crate::model::ids::DeclarationId::from($alias_name);
264
+ let targets = $context.graph().alias_targets(&decl_id).unwrap_or_default();
265
+ $(
266
+ let expected_id = $crate::model::ids::DeclarationId::from($target_name);
267
+ assert!(
268
+ targets.contains(&expected_id),
269
+ "Expected alias '{}' to contain target '{}', but targets were {:?}",
270
+ $alias_name,
271
+ $target_name,
272
+ targets
273
+ );
274
+ )+
275
+ }};
276
+ }
277
+
278
+ /// Asserts that a declaration has a constant reference at the specified location
279
+ ///
280
+ /// This macro:
281
+ /// 1. Parses the location string into `(uri, start_offset, end_offset)`
282
+ /// 2. Finds the declaration by name
283
+ /// 3. Finds a constant reference to that declaration at the given uri and start offset
284
+ /// 4. Asserts the end offset matches
285
+ ///
286
+ /// Location format: "uri:start_line:start_column-end_line:end_column"
287
+ /// Example: `<file:///foo.rb:3:0-3:5>`
288
+ #[cfg(test)]
289
+ #[macro_export]
290
+ macro_rules! assert_constant_reference_to {
291
+ ($context:expr, $declaration_name:expr, $location:expr) => {
292
+ let mut all_references = $context
293
+ .graph()
294
+ .constant_references()
295
+ .values()
296
+ .map(|reference| {
297
+ (
298
+ reference,
299
+ format!(
300
+ "{}:{}",
301
+ $context.graph().documents().get(&reference.uri_id()).unwrap().uri(),
302
+ reference
303
+ .offset()
304
+ .to_display_range($context.graph().documents().get(&reference.uri_id()).unwrap())
305
+ ),
306
+ )
307
+ })
308
+ .collect::<Vec<_>>();
309
+
310
+ all_references.sort_by_key(|(_, reference_location)| reference_location.clone());
311
+
312
+ let reference_at_location = all_references
313
+ .iter()
314
+ .find(|(_, reference_location)| reference_location == $location)
315
+ .map(|(reference, _)| reference)
316
+ .expect(&format!(
317
+ "No constant reference at `{}`, found references at {:?}",
318
+ $location,
319
+ all_references
320
+ .iter()
321
+ .map(|(_reference, reference_location)| reference_location)
322
+ .collect::<Vec<_>>()
323
+ ));
324
+
325
+ let reference_name = $context.graph().names().get(reference_at_location.name_id()).unwrap();
326
+ let NameRef::Resolved(resolved_name) = reference_name else {
327
+ panic!("Reference to found at `{}` is unresolved", $location);
328
+ };
329
+
330
+ let resolved_name_name = $context
331
+ .graph()
332
+ .declarations()
333
+ .get(resolved_name.declaration_id())
334
+ .unwrap()
335
+ .name();
336
+ assert_eq!(
337
+ resolved_name_name, $declaration_name,
338
+ "Expected reference at `{}` to be resolved to `{}`, but got `{}`",
339
+ $location, $declaration_name, resolved_name_name
340
+ );
341
+ };
342
+ }
343
+
344
+ #[cfg(test)]
345
+ #[macro_export]
346
+ macro_rules! assert_declaration_references_count_eq {
347
+ ($context:expr, $declaration_name:expr, $expected_references:expr) => {
348
+ let declaration = $context
349
+ .graph()
350
+ .declarations()
351
+ .get(&$crate::model::ids::DeclarationId::from($declaration_name))
352
+ .unwrap();
353
+
354
+ let count = declaration.reference_count();
355
+
356
+ assert_eq!(
357
+ count, $expected_references,
358
+ "Expected exactly {} references for `{}`, but got {}",
359
+ $expected_references, $declaration_name, count,
360
+ );
361
+ };
362
+ }
363
+
364
+ #[cfg(test)]
365
+ #[macro_export]
366
+ macro_rules! assert_constant_reference_unresolved {
367
+ ($context:expr, $unqualified_name:expr) => {
368
+ let reference_name = $context
369
+ .graph()
370
+ .constant_references()
371
+ .values()
372
+ .find_map(|r| {
373
+ let name = $context.graph().names().get(r.name_id()).unwrap();
374
+ if $context.graph().strings().get(name.str()).unwrap().as_str() == $unqualified_name {
375
+ Some(name)
376
+ } else {
377
+ None
378
+ }
379
+ })
380
+ .unwrap_or_else(|| panic!("No constant reference with unqualified name `{}`", $unqualified_name));
381
+
382
+ assert!(
383
+ matches!(reference_name, $crate::model::name::NameRef::Unresolved(_)),
384
+ "Expected constant reference `{}` to be unresolved, but it was resolved",
385
+ $unqualified_name
386
+ );
387
+ };
388
+ ($context:expr, $unqualified_name:expr, $location:expr) => {
389
+ let mut all_references = $context
390
+ .graph()
391
+ .constant_references()
392
+ .values()
393
+ .map(|reference| {
394
+ (
395
+ reference,
396
+ format!(
397
+ "{}:{}",
398
+ $context.graph().documents().get(&reference.uri_id()).unwrap().uri(),
399
+ reference
400
+ .offset()
401
+ .to_display_range($context.graph().documents().get(&reference.uri_id()).unwrap())
402
+ ),
403
+ )
404
+ })
405
+ .collect::<Vec<_>>();
406
+
407
+ all_references.sort_by_key(|(_, reference_location)| reference_location.clone());
408
+
409
+ let reference_at_location = all_references
410
+ .iter()
411
+ .find(|(_, reference_location)| reference_location == $location)
412
+ .map(|(reference, _)| reference)
413
+ .expect(&format!(
414
+ "No constant reference at `{}`, found references at {:?}",
415
+ $location,
416
+ all_references
417
+ .iter()
418
+ .map(|(_reference, reference_location)| reference_location)
419
+ .collect::<Vec<_>>()
420
+ ));
421
+
422
+ let reference_name = $context.graph().names().get(reference_at_location.name_id()).unwrap();
423
+ assert!(
424
+ matches!(reference_name, $crate::model::name::NameRef::Unresolved(_)),
425
+ "Expected constant reference at `{}` to be unresolved, but it was resolved to `{}`",
426
+ $location,
427
+ if let $crate::model::name::NameRef::Resolved(resolved) = reference_name {
428
+ $context
429
+ .graph()
430
+ .declarations()
431
+ .get(resolved.declaration_id())
432
+ .unwrap()
433
+ .name()
434
+ .to_string()
435
+ } else {
436
+ String::new()
437
+ }
438
+ );
439
+ };
440
+ }
441
+
442
+ #[cfg(test)]
443
+ #[macro_export]
444
+ macro_rules! assert_ancestors_eq {
445
+ // Arm with mixed Complete/Partial entries: ["Foo", Partial("M1"), "Object"]
446
+ ($context:expr, $name:expr, [$($entry:tt $( ($partial_name:expr) )?),* $(,)?]) => {{
447
+ let declaration = $context
448
+ .graph()
449
+ .declarations()
450
+ .get(&$crate::model::ids::DeclarationId::from($name))
451
+ .unwrap();
452
+
453
+ let actual = match declaration.as_namespace().unwrap().ancestors() {
454
+ $crate::model::declaration::Ancestors::Complete(a)
455
+ | $crate::model::declaration::Ancestors::Cyclic(a)
456
+ | $crate::model::declaration::Ancestors::Partial(a) => a,
457
+ };
458
+
459
+ let actual_strs: Vec<String> = actual.iter().map(|a| match a {
460
+ $crate::model::declaration::Ancestor::Complete(id) => {
461
+ $context.graph().declarations().get(id).unwrap().name().to_string()
462
+ }
463
+ $crate::model::declaration::Ancestor::Partial(name_id) => {
464
+ let name = $context.graph().names().get(name_id).unwrap();
465
+ format!("Partial({})", $context.graph().strings().get(name.str()).unwrap().as_str())
466
+ }
467
+ }).collect();
468
+
469
+ let expected_strs: Vec<String> = vec![
470
+ $($crate::assert_ancestors_eq!(@str $entry $( ($partial_name) )?)),*
471
+ ];
472
+
473
+ assert_eq!(
474
+ expected_strs, actual_strs,
475
+ "Incorrect ancestors for {}",
476
+ $name
477
+ );
478
+ }};
479
+
480
+ // Arm for variable expressions (e.g., `empty_ancestors`): all entries assumed Complete
481
+ ($context:expr, $name:expr, $expected:expr) => {{
482
+ let declaration = $context
483
+ .graph()
484
+ .declarations()
485
+ .get(&$crate::model::ids::DeclarationId::from($name))
486
+ .unwrap();
487
+
488
+ let expected_ancestors: Vec<$crate::model::declaration::Ancestor> = $expected
489
+ .iter()
490
+ .map(|n| {
491
+ $crate::model::declaration::Ancestor::Complete($crate::model::ids::DeclarationId::from(*n))
492
+ })
493
+ .collect();
494
+
495
+ let actual = match declaration.as_namespace().unwrap().ancestors() {
496
+ $crate::model::declaration::Ancestors::Complete(a)
497
+ | $crate::model::declaration::Ancestors::Cyclic(a)
498
+ | $crate::model::declaration::Ancestors::Partial(a) => a,
499
+ };
500
+
501
+ assert_eq!(
502
+ expected_ancestors, *actual,
503
+ "Incorrect ancestors for {}",
504
+ $name
505
+ );
506
+ }};
507
+
508
+ // Internal: Partial("name") → "Partial(name)" string
509
+ (@str Partial ($name:expr)) => {
510
+ format!("Partial({})", $name)
511
+ };
512
+
513
+ // Internal: "name" → "name" string
514
+ (@str $name:expr) => {
515
+ $name.to_string()
516
+ };
517
+ }
518
+
519
+ #[cfg(test)]
520
+ #[macro_export]
521
+ macro_rules! assert_descendants {
522
+ ($context:expr, $parent:expr, $descendants:expr) => {
523
+ let parent = $context
524
+ .graph()
525
+ .declarations()
526
+ .get(&$crate::model::ids::DeclarationId::from($parent))
527
+ .unwrap();
528
+ let actual = match parent {
529
+ $crate::model::declaration::Declaration::Namespace($crate::model::declaration::Namespace::Class(class)) => {
530
+ class.descendants().iter().cloned().collect::<Vec<_>>()
531
+ }
532
+ $crate::model::declaration::Declaration::Namespace($crate::model::declaration::Namespace::Module(
533
+ module,
534
+ )) => module.descendants().iter().cloned().collect::<Vec<_>>(),
535
+ $crate::model::declaration::Declaration::Namespace(
536
+ $crate::model::declaration::Namespace::SingletonClass(singleton),
537
+ ) => singleton.descendants().iter().cloned().collect::<Vec<_>>(),
538
+ _ => panic!("Tried to get descendants for a declaration that isn't a namespace"),
539
+ };
540
+
541
+ for descendant in &$descendants {
542
+ let descendant_id = $crate::model::ids::DeclarationId::from(*descendant);
543
+
544
+ assert!(
545
+ actual.contains(&descendant_id),
546
+ "Expected '{}' to be a descendant of '{}'",
547
+ $context.graph().declarations().get(&descendant_id).unwrap().name(),
548
+ parent.name()
549
+ );
550
+ }
551
+ };
552
+ }
553
+
554
+ #[cfg(test)]
555
+ #[macro_export]
556
+ macro_rules! assert_members_eq {
557
+ ($context:expr, $declaration_id:expr, $expected_members:expr) => {
558
+ let mut actual_members = $context
559
+ .graph()
560
+ .declarations()
561
+ .get(&$crate::model::ids::DeclarationId::from($declaration_id))
562
+ .unwrap()
563
+ .as_namespace()
564
+ .unwrap()
565
+ .members()
566
+ .iter()
567
+ .map(|(str_id, _)| $context.graph().strings().get(str_id).unwrap().as_str())
568
+ .collect::<Vec<_>>();
569
+
570
+ actual_members.sort();
571
+
572
+ assert_eq!($expected_members, actual_members.as_slice());
573
+ };
574
+ }
575
+
576
+ #[cfg(test)]
577
+ #[macro_export]
578
+ macro_rules! assert_no_members {
579
+ ($context:expr, $declaration_id:expr) => {
580
+ assert_members_eq!($context, $declaration_id, [] as [&str; 0]);
581
+ };
582
+ }
583
+
584
+ #[cfg(test)]
585
+ #[macro_export]
586
+ macro_rules! assert_owner_eq {
587
+ ($context:expr, $declaration_id:expr, $expected_owner_name:expr) => {
588
+ let actual_owner_id = $context
589
+ .graph()
590
+ .declarations()
591
+ .get(&$crate::model::ids::DeclarationId::from($declaration_id))
592
+ .unwrap()
593
+ .owner_id();
594
+
595
+ let actual_owner_name = $context.graph().declarations().get(actual_owner_id).unwrap().name();
596
+
597
+ assert_eq!($expected_owner_name, actual_owner_name);
598
+ };
599
+ }
600
+
601
+ #[cfg(test)]
602
+ #[macro_export]
603
+ macro_rules! assert_singleton_class_eq {
604
+ ($context:expr, $declaration_id:expr, $expected_singleton_class_name:expr) => {
605
+ let declaration = $context
606
+ .graph()
607
+ .declarations()
608
+ .get(&$crate::model::ids::DeclarationId::from($declaration_id))
609
+ .unwrap();
610
+
611
+ assert_eq!(
612
+ $expected_singleton_class_name,
613
+ $context
614
+ .graph()
615
+ .declarations()
616
+ .get(declaration.as_namespace().unwrap().singleton_class().unwrap())
617
+ .unwrap()
618
+ .name()
619
+ );
620
+ };
621
+ }
622
+
623
+ #[cfg(test)]
624
+ #[macro_export]
625
+ macro_rules! assert_instance_variables_eq {
626
+ ($context:expr, $declaration_id:expr, $expected_instance_variables:expr) => {
627
+ let mut actual_instance_variables = $context
628
+ .graph()
629
+ .declarations()
630
+ .get(&$crate::model::ids::DeclarationId::from($declaration_id))
631
+ .unwrap()
632
+ .as_namespace()
633
+ .unwrap()
634
+ .members()
635
+ .iter()
636
+ .filter_map(
637
+ |(str_id, member_id)| match $context.graph().declarations().get(member_id) {
638
+ Some($crate::model::declaration::Declaration::InstanceVariable(_)) => {
639
+ Some($context.graph().strings().get(str_id).unwrap().as_str())
640
+ }
641
+ _ => None,
642
+ },
643
+ )
644
+ .collect::<Vec<_>>();
645
+
646
+ actual_instance_variables.sort();
647
+
648
+ assert_eq!($expected_instance_variables, actual_instance_variables.as_slice());
649
+ };
650
+ }
651
+
652
+ #[cfg(test)]
653
+ #[macro_export]
654
+ macro_rules! assert_diagnostics_eq {
655
+ ($context:expr, $expected_diagnostics:expr) => {{
656
+ assert_eq!($expected_diagnostics, $context.format_diagnostics(&[]).as_slice());
657
+ }};
658
+ ($context:expr, $expected_diagnostics:expr, $ignore_rules:expr) => {{
659
+ assert_eq!($expected_diagnostics, $context.format_diagnostics($ignore_rules));
660
+ }};
661
+ }
662
+
663
+ #[cfg(test)]
664
+ #[macro_export]
665
+ macro_rules! assert_no_diagnostics {
666
+ ($context:expr) => {{
667
+ let diagnostics = $context.format_diagnostics(&[]);
668
+ assert!(diagnostics.is_empty(), "expected no diagnostics, got {:?}", diagnostics);
669
+ }};
670
+ ($context:expr, $ignore_rules:expr) => {{
671
+ let diagnostics = $context.format_diagnostics($ignore_rules);
672
+ assert!(diagnostics.is_empty(), "expected no diagnostics, got {:?}", diagnostics);
673
+ }};
674
+ }
675
+
676
+ #[cfg(test)]
677
+ mod tests {
678
+ use super::*;
679
+
680
+ #[test]
681
+ fn test_index_uri_with_single_line() {
682
+ let mut context = GraphTest::new();
683
+
684
+ context.index_uri("file://method.rb", "class Foo; end");
685
+ context.resolve();
686
+
687
+ let foo_defs = context.graph.get("Foo").unwrap();
688
+ assert_eq!(foo_defs.len(), 1);
689
+ assert_eq!(foo_defs[0].offset().start(), 0);
690
+ assert_eq!(foo_defs[0].offset().end(), 14);
691
+ }
692
+
693
+ #[test]
694
+ fn test_index_uri_with_multiple_lines() {
695
+ let mut context = GraphTest::new();
696
+
697
+ context.index_uri("file://method.rb", {
698
+ "
699
+ class Foo
700
+ class Bar; end
701
+ end
702
+ "
703
+ });
704
+
705
+ context.resolve();
706
+
707
+ let foo_defs = context.graph.get("Foo").unwrap();
708
+ assert_eq!(foo_defs.len(), 1);
709
+ assert_eq!(foo_defs[0].offset().start(), 0);
710
+ assert_eq!(foo_defs[0].offset().end(), 30);
711
+
712
+ let bar_defs = context.graph.get("Foo::Bar").unwrap();
713
+ assert_eq!(bar_defs.len(), 1);
714
+ assert_eq!(bar_defs[0].offset().start(), 12);
715
+ assert_eq!(bar_defs[0].offset().end(), 26);
716
+ }
717
+
718
+ #[test]
719
+ fn test_index_uri_with_new_lines() {
720
+ let mut context = GraphTest::new();
721
+
722
+ context.index_uri("file://method.rb", "\n\nclass Foo; end");
723
+ context.resolve();
724
+
725
+ let foo_defs = context.graph.get("Foo").unwrap();
726
+ assert_eq!(foo_defs.len(), 1);
727
+ assert_eq!(foo_defs[0].offset().start(), 2);
728
+ assert_eq!(foo_defs[0].offset().end(), 16);
729
+ }
730
+ }