rubydex 0.1.0.beta11 → 0.1.0.beta13

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +23 -23
  3. data/README.md +125 -125
  4. data/THIRD_PARTY_LICENSES.html +2018 -945
  5. data/exe/rdx +47 -47
  6. data/ext/rubydex/declaration.c +453 -388
  7. data/ext/rubydex/declaration.h +23 -23
  8. data/ext/rubydex/definition.c +284 -197
  9. data/ext/rubydex/definition.h +28 -28
  10. data/ext/rubydex/diagnostic.c +6 -6
  11. data/ext/rubydex/diagnostic.h +11 -11
  12. data/ext/rubydex/document.c +97 -98
  13. data/ext/rubydex/document.h +10 -10
  14. data/ext/rubydex/extconf.rb +146 -127
  15. data/ext/rubydex/graph.c +701 -512
  16. data/ext/rubydex/graph.h +10 -10
  17. data/ext/rubydex/handle.h +44 -44
  18. data/ext/rubydex/location.c +22 -22
  19. data/ext/rubydex/location.h +15 -15
  20. data/ext/rubydex/reference.c +123 -104
  21. data/ext/rubydex/reference.h +15 -16
  22. data/ext/rubydex/rubydex.c +22 -22
  23. data/ext/rubydex/utils.c +108 -86
  24. data/ext/rubydex/utils.h +34 -28
  25. data/lib/rubydex/comment.rb +17 -17
  26. data/lib/rubydex/declaration.rb +11 -0
  27. data/lib/rubydex/diagnostic.rb +21 -21
  28. data/lib/rubydex/failures.rb +15 -15
  29. data/lib/rubydex/graph.rb +98 -92
  30. data/lib/rubydex/keyword.rb +17 -0
  31. data/lib/rubydex/keyword_parameter.rb +13 -0
  32. data/lib/rubydex/location.rb +90 -90
  33. data/lib/rubydex/mixin.rb +22 -0
  34. data/lib/rubydex/version.rb +5 -5
  35. data/lib/rubydex.rb +24 -20
  36. data/rbi/rubydex.rbi +425 -310
  37. data/rust/Cargo.lock +1851 -1851
  38. data/rust/Cargo.toml +29 -29
  39. data/rust/about.toml +10 -10
  40. data/rust/{about.hbs → about_templates/about.hbs} +81 -78
  41. data/rust/about_templates/mingw_licenses.hbs +1071 -0
  42. data/rust/rubydex/Cargo.toml +42 -42
  43. data/rust/rubydex/src/compile_assertions.rs +13 -13
  44. data/rust/rubydex/src/diagnostic.rs +110 -109
  45. data/rust/rubydex/src/errors.rs +28 -28
  46. data/rust/rubydex/src/indexing/local_graph.rs +224 -224
  47. data/rust/rubydex/src/indexing/rbs_indexer.rs +1551 -1554
  48. data/rust/rubydex/src/indexing/ruby_indexer.rs +2329 -6753
  49. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +4962 -0
  50. data/rust/rubydex/src/indexing.rs +210 -210
  51. data/rust/rubydex/src/integrity.rs +279 -278
  52. data/rust/rubydex/src/job_queue.rs +199 -205
  53. data/rust/rubydex/src/lib.rs +17 -17
  54. data/rust/rubydex/src/listing.rs +371 -272
  55. data/rust/rubydex/src/main.rs +160 -160
  56. data/rust/rubydex/src/model/built_in.rs +83 -0
  57. data/rust/rubydex/src/model/comment.rs +24 -24
  58. data/rust/rubydex/src/model/declaration.rs +679 -588
  59. data/rust/rubydex/src/model/definitions.rs +1682 -1602
  60. data/rust/rubydex/src/model/document.rs +222 -252
  61. data/rust/rubydex/src/model/encoding.rs +22 -22
  62. data/rust/rubydex/src/model/graph.rs +3782 -3556
  63. data/rust/rubydex/src/model/id.rs +110 -110
  64. data/rust/rubydex/src/model/identity_maps.rs +58 -58
  65. data/rust/rubydex/src/model/ids.rs +60 -38
  66. data/rust/rubydex/src/model/keywords.rs +256 -256
  67. data/rust/rubydex/src/model/name.rs +298 -298
  68. data/rust/rubydex/src/model/references.rs +111 -111
  69. data/rust/rubydex/src/model/string_ref.rs +50 -50
  70. data/rust/rubydex/src/model/visibility.rs +41 -41
  71. data/rust/rubydex/src/model.rs +15 -14
  72. data/rust/rubydex/src/offset.rs +147 -147
  73. data/rust/rubydex/src/position.rs +6 -6
  74. data/rust/rubydex/src/query.rs +1841 -1700
  75. data/rust/rubydex/src/resolution.rs +1852 -5895
  76. data/rust/rubydex/src/resolution_tests.rs +4701 -0
  77. data/rust/rubydex/src/stats/memory.rs +71 -71
  78. data/rust/rubydex/src/stats/orphan_report.rs +264 -263
  79. data/rust/rubydex/src/stats/timer.rs +127 -127
  80. data/rust/rubydex/src/stats.rs +11 -11
  81. data/rust/rubydex/src/test_utils/context.rs +226 -226
  82. data/rust/rubydex/src/test_utils/graph_test.rs +730 -679
  83. data/rust/rubydex/src/test_utils/local_graph_test.rs +602 -602
  84. data/rust/rubydex/src/test_utils.rs +52 -52
  85. data/rust/rubydex/src/visualization/dot.rs +192 -176
  86. data/rust/rubydex/src/visualization.rs +6 -6
  87. data/rust/rubydex/tests/cli.rs +185 -167
  88. data/rust/rubydex-mcp/Cargo.toml +28 -28
  89. data/rust/rubydex-mcp/src/main.rs +48 -48
  90. data/rust/rubydex-mcp/src/server.rs +1145 -1145
  91. data/rust/rubydex-mcp/src/tools.rs +49 -49
  92. data/rust/rubydex-mcp/tests/mcp.rs +302 -302
  93. data/rust/rubydex-sys/Cargo.toml +20 -20
  94. data/rust/rubydex-sys/build.rs +14 -14
  95. data/rust/rubydex-sys/cbindgen.toml +12 -12
  96. data/rust/rubydex-sys/src/declaration_api.rs +485 -469
  97. data/rust/rubydex-sys/src/definition_api.rs +443 -352
  98. data/rust/rubydex-sys/src/diagnostic_api.rs +99 -99
  99. data/rust/rubydex-sys/src/document_api.rs +85 -54
  100. data/rust/rubydex-sys/src/graph_api.rs +1017 -700
  101. data/rust/rubydex-sys/src/lib.rs +79 -9
  102. data/rust/rubydex-sys/src/location_api.rs +79 -79
  103. data/rust/rubydex-sys/src/name_api.rs +187 -135
  104. data/rust/rubydex-sys/src/reference_api.rs +267 -195
  105. data/rust/rubydex-sys/src/utils.rs +70 -70
  106. data/rust/rustfmt.toml +2 -2
  107. metadata +16 -9
  108. data/lib/rubydex/librubydex_sys.so +0 -0
@@ -1,679 +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
- assert_eq!(
355
- declaration.references().len(),
356
- $expected_references,
357
- "Expected exactly {} references for `{}`, but got {}",
358
- $expected_references,
359
- $declaration_name,
360
- declaration.references().len()
361
- );
362
- };
363
- }
364
-
365
- #[cfg(test)]
366
- #[macro_export]
367
- macro_rules! assert_constant_reference_unresolved {
368
- ($context:expr, $unqualified_name:expr) => {
369
- let reference_name = $context
370
- .graph()
371
- .constant_references()
372
- .values()
373
- .find_map(|r| {
374
- let name = $context.graph().names().get(r.name_id()).unwrap();
375
- if $context.graph().strings().get(name.str()).unwrap().as_str() == $unqualified_name {
376
- Some(name)
377
- } else {
378
- None
379
- }
380
- })
381
- .unwrap_or_else(|| panic!("No constant reference with unqualified name `{}`", $unqualified_name));
382
-
383
- assert!(
384
- matches!(reference_name, $crate::model::name::NameRef::Unresolved(_)),
385
- "Expected constant reference `{}` to be unresolved, but it was resolved",
386
- $unqualified_name
387
- );
388
- };
389
- }
390
-
391
- #[cfg(test)]
392
- #[macro_export]
393
- macro_rules! assert_ancestors_eq {
394
- // Arm with mixed Complete/Partial entries: ["Foo", Partial("M1"), "Object"]
395
- ($context:expr, $name:expr, [$($entry:tt $( ($partial_name:expr) )?),* $(,)?]) => {{
396
- let declaration = $context
397
- .graph()
398
- .declarations()
399
- .get(&$crate::model::ids::DeclarationId::from($name))
400
- .unwrap();
401
-
402
- let actual = match declaration.as_namespace().unwrap().ancestors() {
403
- $crate::model::declaration::Ancestors::Complete(a)
404
- | $crate::model::declaration::Ancestors::Cyclic(a)
405
- | $crate::model::declaration::Ancestors::Partial(a) => a,
406
- };
407
-
408
- let actual_strs: Vec<String> = actual.iter().map(|a| match a {
409
- $crate::model::declaration::Ancestor::Complete(id) => {
410
- $context.graph().declarations().get(id).unwrap().name().to_string()
411
- }
412
- $crate::model::declaration::Ancestor::Partial(name_id) => {
413
- let name = $context.graph().names().get(name_id).unwrap();
414
- format!("Partial({})", $context.graph().strings().get(name.str()).unwrap().as_str())
415
- }
416
- }).collect();
417
-
418
- let expected_strs: Vec<String> = vec![
419
- $($crate::assert_ancestors_eq!(@str $entry $( ($partial_name) )?)),*
420
- ];
421
-
422
- assert_eq!(
423
- expected_strs, actual_strs,
424
- "Incorrect ancestors for {}",
425
- $name
426
- );
427
- }};
428
-
429
- // Arm for variable expressions (e.g., `empty_ancestors`): all entries assumed Complete
430
- ($context:expr, $name:expr, $expected:expr) => {{
431
- let declaration = $context
432
- .graph()
433
- .declarations()
434
- .get(&$crate::model::ids::DeclarationId::from($name))
435
- .unwrap();
436
-
437
- let expected_ancestors: Vec<$crate::model::declaration::Ancestor> = $expected
438
- .iter()
439
- .map(|n| {
440
- $crate::model::declaration::Ancestor::Complete($crate::model::ids::DeclarationId::from(*n))
441
- })
442
- .collect();
443
-
444
- let actual = match declaration.as_namespace().unwrap().ancestors() {
445
- $crate::model::declaration::Ancestors::Complete(a)
446
- | $crate::model::declaration::Ancestors::Cyclic(a)
447
- | $crate::model::declaration::Ancestors::Partial(a) => a,
448
- };
449
-
450
- assert_eq!(
451
- expected_ancestors, *actual,
452
- "Incorrect ancestors for {}",
453
- $name
454
- );
455
- }};
456
-
457
- // Internal: Partial("name") → "Partial(name)" string
458
- (@str Partial ($name:expr)) => {
459
- format!("Partial({})", $name)
460
- };
461
-
462
- // Internal: "name" → "name" string
463
- (@str $name:expr) => {
464
- $name.to_string()
465
- };
466
- }
467
-
468
- #[cfg(test)]
469
- #[macro_export]
470
- macro_rules! assert_descendants {
471
- ($context:expr, $parent:expr, $descendants:expr) => {
472
- let parent = $context
473
- .graph()
474
- .declarations()
475
- .get(&$crate::model::ids::DeclarationId::from($parent))
476
- .unwrap();
477
- let actual = match parent {
478
- $crate::model::declaration::Declaration::Namespace($crate::model::declaration::Namespace::Class(class)) => {
479
- class.descendants().iter().cloned().collect::<Vec<_>>()
480
- }
481
- $crate::model::declaration::Declaration::Namespace($crate::model::declaration::Namespace::Module(
482
- module,
483
- )) => module.descendants().iter().cloned().collect::<Vec<_>>(),
484
- $crate::model::declaration::Declaration::Namespace(
485
- $crate::model::declaration::Namespace::SingletonClass(singleton),
486
- ) => singleton.descendants().iter().cloned().collect::<Vec<_>>(),
487
- _ => panic!("Tried to get descendants for a declaration that isn't a namespace"),
488
- };
489
-
490
- for descendant in &$descendants {
491
- let descendant_id = $crate::model::ids::DeclarationId::from(*descendant);
492
-
493
- assert!(
494
- actual.contains(&descendant_id),
495
- "Expected '{}' to be a descendant of '{}'",
496
- $context.graph().declarations().get(&descendant_id).unwrap().name(),
497
- parent.name()
498
- );
499
- }
500
- };
501
- }
502
-
503
- #[cfg(test)]
504
- #[macro_export]
505
- macro_rules! assert_members_eq {
506
- ($context:expr, $declaration_id:expr, $expected_members:expr) => {
507
- let mut actual_members = $context
508
- .graph()
509
- .declarations()
510
- .get(&$crate::model::ids::DeclarationId::from($declaration_id))
511
- .unwrap()
512
- .as_namespace()
513
- .unwrap()
514
- .members()
515
- .iter()
516
- .map(|(str_id, _)| $context.graph().strings().get(str_id).unwrap().as_str())
517
- .collect::<Vec<_>>();
518
-
519
- actual_members.sort();
520
-
521
- assert_eq!($expected_members, actual_members.as_slice());
522
- };
523
- }
524
-
525
- #[cfg(test)]
526
- #[macro_export]
527
- macro_rules! assert_no_members {
528
- ($context:expr, $declaration_id:expr) => {
529
- assert_members_eq!($context, $declaration_id, [] as [&str; 0]);
530
- };
531
- }
532
-
533
- #[cfg(test)]
534
- #[macro_export]
535
- macro_rules! assert_owner_eq {
536
- ($context:expr, $declaration_id:expr, $expected_owner_name:expr) => {
537
- let actual_owner_id = $context
538
- .graph()
539
- .declarations()
540
- .get(&$crate::model::ids::DeclarationId::from($declaration_id))
541
- .unwrap()
542
- .owner_id();
543
-
544
- let actual_owner_name = $context.graph().declarations().get(actual_owner_id).unwrap().name();
545
-
546
- assert_eq!($expected_owner_name, actual_owner_name);
547
- };
548
- }
549
-
550
- #[cfg(test)]
551
- #[macro_export]
552
- macro_rules! assert_singleton_class_eq {
553
- ($context:expr, $declaration_id:expr, $expected_singleton_class_name:expr) => {
554
- let declaration = $context
555
- .graph()
556
- .declarations()
557
- .get(&$crate::model::ids::DeclarationId::from($declaration_id))
558
- .unwrap();
559
-
560
- assert_eq!(
561
- $expected_singleton_class_name,
562
- $context
563
- .graph()
564
- .declarations()
565
- .get(declaration.as_namespace().unwrap().singleton_class().unwrap())
566
- .unwrap()
567
- .name()
568
- );
569
- };
570
- }
571
-
572
- #[cfg(test)]
573
- #[macro_export]
574
- macro_rules! assert_instance_variables_eq {
575
- ($context:expr, $declaration_id:expr, $expected_instance_variables:expr) => {
576
- let mut actual_instance_variables = $context
577
- .graph()
578
- .declarations()
579
- .get(&$crate::model::ids::DeclarationId::from($declaration_id))
580
- .unwrap()
581
- .as_namespace()
582
- .unwrap()
583
- .members()
584
- .iter()
585
- .filter_map(
586
- |(str_id, member_id)| match $context.graph().declarations().get(member_id) {
587
- Some($crate::model::declaration::Declaration::InstanceVariable(_)) => {
588
- Some($context.graph().strings().get(str_id).unwrap().as_str())
589
- }
590
- _ => None,
591
- },
592
- )
593
- .collect::<Vec<_>>();
594
-
595
- actual_instance_variables.sort();
596
-
597
- assert_eq!($expected_instance_variables, actual_instance_variables.as_slice());
598
- };
599
- }
600
-
601
- #[cfg(test)]
602
- #[macro_export]
603
- macro_rules! assert_diagnostics_eq {
604
- ($context:expr, $expected_diagnostics:expr) => {{
605
- assert_eq!($expected_diagnostics, $context.format_diagnostics(&[]).as_slice());
606
- }};
607
- ($context:expr, $expected_diagnostics:expr, $ignore_rules:expr) => {{
608
- assert_eq!($expected_diagnostics, $context.format_diagnostics($ignore_rules));
609
- }};
610
- }
611
-
612
- #[cfg(test)]
613
- #[macro_export]
614
- macro_rules! assert_no_diagnostics {
615
- ($context:expr) => {{
616
- let diagnostics = $context.format_diagnostics(&[]);
617
- assert!(diagnostics.is_empty(), "expected no diagnostics, got {:?}", diagnostics);
618
- }};
619
- ($context:expr, $ignore_rules:expr) => {{
620
- let diagnostics = $context.format_diagnostics($ignore_rules);
621
- assert!(diagnostics.is_empty(), "expected no diagnostics, got {:?}", diagnostics);
622
- }};
623
- }
624
-
625
- #[cfg(test)]
626
- mod tests {
627
- use super::*;
628
-
629
- #[test]
630
- fn test_index_uri_with_single_line() {
631
- let mut context = GraphTest::new();
632
-
633
- context.index_uri("file://method.rb", "class Foo; end");
634
- context.resolve();
635
-
636
- let foo_defs = context.graph.get("Foo").unwrap();
637
- assert_eq!(foo_defs.len(), 1);
638
- assert_eq!(foo_defs[0].offset().start(), 0);
639
- assert_eq!(foo_defs[0].offset().end(), 14);
640
- }
641
-
642
- #[test]
643
- fn test_index_uri_with_multiple_lines() {
644
- let mut context = GraphTest::new();
645
-
646
- context.index_uri("file://method.rb", {
647
- "
648
- class Foo
649
- class Bar; end
650
- end
651
- "
652
- });
653
-
654
- context.resolve();
655
-
656
- let foo_defs = context.graph.get("Foo").unwrap();
657
- assert_eq!(foo_defs.len(), 1);
658
- assert_eq!(foo_defs[0].offset().start(), 0);
659
- assert_eq!(foo_defs[0].offset().end(), 30);
660
-
661
- let bar_defs = context.graph.get("Foo::Bar").unwrap();
662
- assert_eq!(bar_defs.len(), 1);
663
- assert_eq!(bar_defs[0].offset().start(), 12);
664
- assert_eq!(bar_defs[0].offset().end(), 26);
665
- }
666
-
667
- #[test]
668
- fn test_index_uri_with_new_lines() {
669
- let mut context = GraphTest::new();
670
-
671
- context.index_uri("file://method.rb", "\n\nclass Foo; end");
672
- context.resolve();
673
-
674
- let foo_defs = context.graph.get("Foo").unwrap();
675
- assert_eq!(foo_defs.len(), 1);
676
- assert_eq!(foo_defs[0].offset().start(), 2);
677
- assert_eq!(foo_defs[0].offset().end(), 16);
678
- }
679
- }
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
+ }