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
@@ -0,0 +1,4701 @@
1
+ use super::Resolver;
2
+ use crate::{
3
+ assert_alias_targets_contain, assert_ancestors_eq, assert_constant_alias_target_eq, assert_constant_reference_to,
4
+ assert_constant_reference_unresolved, assert_declaration_definitions_count_eq, assert_declaration_does_not_exist,
5
+ assert_declaration_exists, assert_declaration_kind_eq, assert_declaration_references_count_eq, assert_descendants,
6
+ assert_diagnostics_eq, assert_instance_variables_eq, assert_members_eq, assert_no_constant_alias_target,
7
+ assert_no_diagnostics, assert_no_members, assert_owner_eq, assert_singleton_class_eq,
8
+ diagnostic::Rule,
9
+ model::{declaration::Ancestors, ids::DeclarationId, name::NameRef},
10
+ test_utils::GraphTest,
11
+ };
12
+
13
+ #[test]
14
+ fn resolving_top_level_references() {
15
+ let mut context = GraphTest::new();
16
+ context.index_uri("file:///bar.rb", {
17
+ r"
18
+ class Bar; end
19
+
20
+ ::Bar
21
+ Bar
22
+ "
23
+ });
24
+ context.index_uri("file:///foo.rb", {
25
+ r"
26
+ module Foo
27
+ ::Bar
28
+ end
29
+ "
30
+ });
31
+ context.resolve();
32
+
33
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
34
+
35
+ assert_constant_reference_to!(context, "Bar", "file:///bar.rb:3:3-3:6");
36
+ assert_constant_reference_to!(context, "Bar", "file:///bar.rb:4:1-4:4");
37
+ assert_constant_reference_to!(context, "Bar", "file:///foo.rb:2:5-2:8");
38
+ }
39
+
40
+ #[test]
41
+ fn resolving_nested_reference() {
42
+ let mut context = GraphTest::new();
43
+ context.index_uri("file:///bar.rb", {
44
+ r"
45
+ module Foo
46
+ CONST = 123
47
+
48
+ class Bar
49
+ CONST
50
+ Foo::CONST
51
+ end
52
+ end
53
+ "
54
+ });
55
+ context.resolve();
56
+
57
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///bar.rb:5:5-5:10");
58
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///bar.rb:6:10-6:15");
59
+ }
60
+
61
+ #[test]
62
+ fn resolving_nested_reference_that_refer_to_top_level_constant() {
63
+ let mut context = GraphTest::new();
64
+ context.index_uri("file:///bar.rb", {
65
+ r"
66
+ class Baz; end
67
+
68
+ module Foo
69
+ class Bar
70
+ Baz
71
+ end
72
+ end
73
+ "
74
+ });
75
+ context.resolve();
76
+
77
+ assert_no_diagnostics!(&context);
78
+
79
+ assert_constant_reference_to!(context, "Baz", "file:///bar.rb:5:5-5:8");
80
+ }
81
+
82
+ #[test]
83
+ fn resolving_constant_path_references_at_top_level() {
84
+ let mut context = GraphTest::new();
85
+ context.index_uri("file:///bar.rb", {
86
+ r"
87
+ module Foo
88
+ class Bar; end
89
+ end
90
+
91
+ Foo::Bar
92
+ "
93
+ });
94
+ context.resolve();
95
+
96
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
97
+
98
+ assert_constant_reference_to!(context, "Foo::Bar", "file:///bar.rb:5:6-5:9");
99
+ }
100
+
101
+ #[test]
102
+ fn resolving_reference_for_non_existing_declaration() {
103
+ let mut context = GraphTest::new();
104
+ context.index_uri("file:///foo.rb", {
105
+ r"
106
+ Foo
107
+ "
108
+ });
109
+ context.resolve();
110
+
111
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
112
+ assert_constant_reference_unresolved!(context, "Foo");
113
+ }
114
+
115
+ #[test]
116
+ fn resolution_creates_global_declaration() {
117
+ let mut context = GraphTest::new();
118
+ context.index_uri("file:///foo.rb", {
119
+ r"
120
+ module Foo
121
+ class Bar
122
+ end
123
+ end
124
+
125
+ class Foo::Baz
126
+ end
127
+ "
128
+ });
129
+ context.resolve();
130
+
131
+ assert_no_diagnostics!(&context);
132
+
133
+ assert_members_eq!(context, "Foo", ["Bar", "Baz"]);
134
+ assert_owner_eq!(context, "Foo", "Object");
135
+
136
+ assert_no_members!(context, "Foo::Bar");
137
+ assert_owner_eq!(context, "Foo::Bar", "Foo");
138
+
139
+ assert_no_members!(context, "Foo::Baz");
140
+ assert_owner_eq!(context, "Foo::Baz", "Foo");
141
+ }
142
+
143
+ #[test]
144
+ fn resolution_for_non_constant_declarations() {
145
+ let mut context = GraphTest::new();
146
+ context.index_uri("file:///foo.rb", {
147
+ r"
148
+ class Foo
149
+ def initialize
150
+ @name = 123
151
+ end
152
+ end
153
+ "
154
+ });
155
+ context.resolve();
156
+
157
+ assert_no_diagnostics!(&context);
158
+
159
+ assert_members_eq!(context, "Foo", ["@name", "initialize()"]);
160
+ assert_owner_eq!(context, "Foo", "Object");
161
+ }
162
+
163
+ #[test]
164
+ fn resolution_for_ambiguous_namespace_definitions() {
165
+ // Like many examples of Ruby code that is ambiguous to static analysis, this example is ambiguous due to
166
+ // require order. If `foo.rb` is loaded first, then `Bar` doesn't exist, Ruby crashes and we should emit an
167
+ // error or warning for a non existing constant.
168
+ //
169
+ // If `bar.rb` is loaded first, then `Bar` resolves to top level `Bar` and `Bar::Baz` is defined, completely
170
+ // escaping the `Foo` nesting.
171
+ let mut context = GraphTest::new();
172
+ context.index_uri("file:///foo.rb", {
173
+ r"
174
+ module Foo
175
+ class Bar::Baz
176
+ end
177
+ end
178
+ "
179
+ });
180
+ context.index_uri("file:///bar.rb", {
181
+ r"
182
+ module Bar
183
+ end
184
+ "
185
+ });
186
+ context.resolve();
187
+
188
+ assert_no_diagnostics!(&context);
189
+
190
+ assert_no_members!(context, "Foo");
191
+ assert_owner_eq!(context, "Foo", "Object");
192
+
193
+ assert_members_eq!(context, "Bar", ["Baz"]);
194
+ assert_owner_eq!(context, "Bar", "Object");
195
+ }
196
+
197
+ #[test]
198
+ fn resolution_for_top_level_references() {
199
+ let mut context = GraphTest::new();
200
+ context.index_uri("file:///foo.rb", {
201
+ r"
202
+ module Foo
203
+ class ::Bar
204
+ class Baz
205
+ end
206
+ end
207
+ end
208
+ "
209
+ });
210
+ context.resolve();
211
+
212
+ assert_no_diagnostics!(&context);
213
+
214
+ assert_no_members!(context, "Foo");
215
+ assert_owner_eq!(context, "Foo", "Object");
216
+
217
+ assert_members_eq!(context, "Bar", ["Baz"]);
218
+ assert_owner_eq!(context, "Bar", "Object");
219
+
220
+ assert_no_members!(context, "Bar::Baz");
221
+ assert_owner_eq!(context, "Bar::Baz", "Bar");
222
+ }
223
+
224
+ #[test]
225
+ fn expected_name_depth_order() {
226
+ let mut context = GraphTest::new();
227
+ context.index_uri("file:///foo.rb", {
228
+ r"
229
+ module Foo
230
+ module Bar
231
+ module Baz
232
+ end
233
+
234
+ module ::Top
235
+ class AfterTop
236
+ end
237
+ end
238
+ end
239
+
240
+ module Qux::Zip
241
+ module Zap
242
+ class Zop::Boop
243
+ end
244
+ end
245
+ end
246
+ end
247
+ "
248
+ });
249
+
250
+ let depths = Resolver::compute_name_depths(context.graph().names());
251
+ let mut names = context
252
+ .graph()
253
+ .names()
254
+ .iter()
255
+ .filter(|(_, n)| {
256
+ !["Kernel", "BasicObject", "Object", "Module", "Class"]
257
+ .contains(&context.graph().strings().get(n.str()).unwrap().as_str())
258
+ })
259
+ .collect::<Vec<_>>();
260
+ assert_eq!(10, names.len());
261
+
262
+ names.sort_by_key(|(id, _)| depths.get(id).unwrap());
263
+
264
+ assert_eq!(
265
+ [
266
+ "Top", "Foo", "Bar", "Qux", "AfterTop", "Baz", "Zip", "Zap", "Zop", "Boop"
267
+ ],
268
+ names
269
+ .iter()
270
+ .map(|(_, n)| context.graph().strings().get(n.str()).unwrap().as_str())
271
+ .collect::<Vec<_>>()
272
+ .as_slice()
273
+ );
274
+ }
275
+
276
+ #[test]
277
+ fn resolution_for_singleton_class() {
278
+ let mut context = GraphTest::new();
279
+ context.index_uri("file:///foo.rb", {
280
+ r"
281
+ class Foo
282
+ class << self
283
+ def bar; end
284
+ BAZ = 123
285
+ end
286
+ end
287
+ "
288
+ });
289
+ context.resolve();
290
+
291
+ assert_no_diagnostics!(&context);
292
+
293
+ assert_no_members!(context, "Foo");
294
+ assert_owner_eq!(context, "Foo", "Object");
295
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
296
+
297
+ assert_members_eq!(context, "Foo::<Foo>", ["BAZ", "bar()"]);
298
+ assert_owner_eq!(context, "Foo::<Foo>", "Foo");
299
+ }
300
+
301
+ #[test]
302
+ fn resolution_for_nested_singleton_class() {
303
+ let mut context = GraphTest::new();
304
+ context.index_uri("file:///foo.rb", {
305
+ r"
306
+ class Foo
307
+ class << self
308
+ class << self
309
+ def baz; end
310
+ end
311
+ end
312
+ end
313
+ "
314
+ });
315
+ context.resolve();
316
+
317
+ assert_no_diagnostics!(&context);
318
+
319
+ assert_no_members!(context, "Foo");
320
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
321
+
322
+ assert_no_members!(context, "Foo::<Foo>");
323
+ assert_singleton_class_eq!(context, "Foo::<Foo>", "Foo::<Foo>::<<Foo>>");
324
+
325
+ assert_members_eq!(context, "Foo::<Foo>::<<Foo>>", ["baz()"]);
326
+ assert_owner_eq!(context, "Foo::<Foo>::<<Foo>>", "Foo::<Foo>");
327
+ }
328
+
329
+ #[test]
330
+ fn resolution_for_singleton_class_of_external_constant() {
331
+ let mut context = GraphTest::new();
332
+ context.index_uri("file:///foo.rb", {
333
+ r"
334
+ class Foo; end
335
+ class Bar
336
+ class << Foo
337
+ def baz; end
338
+
339
+ class Baz; end
340
+ end
341
+ end
342
+ "
343
+ });
344
+ context.resolve();
345
+
346
+ assert_no_diagnostics!(&context);
347
+
348
+ assert_no_members!(context, "Foo");
349
+ assert_owner_eq!(context, "Foo", "Object");
350
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
351
+
352
+ assert_no_members!(context, "Bar");
353
+ assert_owner_eq!(context, "Bar", "Object");
354
+
355
+ assert_members_eq!(context, "Foo::<Foo>", ["Baz", "baz()"]);
356
+ assert_owner_eq!(context, "Foo::<Foo>", "Foo");
357
+ }
358
+
359
+ #[test]
360
+ fn resolution_for_class_variable_in_nested_singleton_class() {
361
+ let mut context = GraphTest::new();
362
+ context.index_uri("file:///foo.rb", {
363
+ r"
364
+ class Foo
365
+ class << self
366
+ @@bar = 123
367
+
368
+ class << self
369
+ @@baz = 456
370
+ end
371
+ end
372
+ end
373
+ "
374
+ });
375
+ context.resolve();
376
+
377
+ assert_no_diagnostics!(&context);
378
+
379
+ assert_members_eq!(context, "Foo", ["@@bar", "@@baz"]);
380
+ assert_owner_eq!(context, "Foo", "Object");
381
+ }
382
+
383
+ #[test]
384
+ fn resolution_for_class_variable_in_method() {
385
+ let mut context = GraphTest::new();
386
+ context.index_uri("file:///foo.rb", {
387
+ r"
388
+ class Foo
389
+ def bar
390
+ @@baz = 456
391
+ end
392
+ end
393
+ "
394
+ });
395
+ context.resolve();
396
+
397
+ assert_no_diagnostics!(&context);
398
+
399
+ assert_members_eq!(context, "Foo", ["@@baz", "bar()"]);
400
+ }
401
+
402
+ #[test]
403
+ fn resolution_for_class_variable_only_follows_lexical_nesting() {
404
+ let mut context = GraphTest::new();
405
+ context.index_uri("file:///foo.rb", {
406
+ r"
407
+ class Foo; end
408
+ class Bar
409
+ def Foo.demo
410
+ @@cvar1 = 1
411
+ end
412
+
413
+ class << Foo
414
+ def demo2
415
+ @@cvar2 = 1
416
+ end
417
+ end
418
+ end
419
+ "
420
+ });
421
+ context.resolve();
422
+
423
+ assert_no_diagnostics!(&context);
424
+
425
+ assert_no_members!(context, "Foo");
426
+ assert_members_eq!(context, "Bar", ["@@cvar1", "@@cvar2"]);
427
+ }
428
+
429
+ #[test]
430
+ fn resolution_for_class_variable_at_top_level() {
431
+ let mut context = GraphTest::new();
432
+ context.index_uri("file:///foo.rb", {
433
+ r"
434
+ @@var = 123
435
+ "
436
+ });
437
+ context.resolve();
438
+
439
+ assert_no_diagnostics!(&context);
440
+
441
+ // TODO: this should push an error diagnostic
442
+ assert_declaration_does_not_exist!(context, "Object::@@var");
443
+ }
444
+
445
+ #[test]
446
+ fn singleton_class_is_set() {
447
+ let mut context = GraphTest::new();
448
+ context.index_uri("file:///foo.rb", {
449
+ r"
450
+ class Foo
451
+ class << self
452
+ end
453
+ end
454
+ "
455
+ });
456
+
457
+ context.resolve();
458
+
459
+ assert_no_diagnostics!(&context);
460
+
461
+ assert_declaration_exists!(context, "Foo::<Foo>");
462
+ assert_singleton_class_eq!(context, "Foo", "Foo::<Foo>");
463
+ }
464
+
465
+ #[test]
466
+ fn resolution_for_method_with_receiver() {
467
+ let mut context = GraphTest::new();
468
+ context.index_uri("file:///foo.rb", {
469
+ r"
470
+ class Foo
471
+ def self.bar; end
472
+
473
+ class << self
474
+ def self.nested_bar; end
475
+ end
476
+ end
477
+
478
+ class Bar
479
+ def Foo.baz; end
480
+
481
+ def self.qux; end
482
+ end
483
+ "
484
+ });
485
+ context.resolve();
486
+
487
+ assert_no_diagnostics!(&context);
488
+
489
+ assert_members_eq!(context, "Foo::<Foo>", ["bar()", "baz()"]);
490
+ assert_owner_eq!(context, "Foo::<Foo>", "Foo");
491
+
492
+ assert_members_eq!(context, "Foo::<Foo>::<<Foo>>", ["nested_bar()"]);
493
+ assert_owner_eq!(context, "Foo::<Foo>::<<Foo>>", "Foo::<Foo>");
494
+
495
+ assert_members_eq!(context, "Bar::<Bar>", ["qux()"]);
496
+ assert_owner_eq!(context, "Bar::<Bar>", "Bar");
497
+ }
498
+
499
+ #[test]
500
+ fn resolution_for_self_method_with_same_name_instance_method() {
501
+ let mut context = GraphTest::new();
502
+ context.index_uri(
503
+ "file:///foo.rb",
504
+ r"
505
+ class Foo
506
+ def self.run; end
507
+ def run; end
508
+ end
509
+ ",
510
+ );
511
+ context.resolve();
512
+
513
+ assert_no_diagnostics!(&context);
514
+
515
+ assert_members_eq!(context, "Foo", ["run()"]);
516
+ assert_members_eq!(context, "Foo::<Foo>", ["run()"]);
517
+ }
518
+
519
+ #[test]
520
+ fn resolution_for_self_method_alias_with_same_name_instance_method() {
521
+ let mut context = GraphTest::new();
522
+ context.index_rbs_uri(
523
+ "file:///foo.rbs",
524
+ r"
525
+ class Foo
526
+ def self.run: () -> void
527
+ def run: () -> void
528
+ alias self.execute self.run
529
+ alias execute run
530
+ end
531
+ ",
532
+ );
533
+ context.resolve();
534
+
535
+ assert_no_diagnostics!(&context);
536
+
537
+ assert_members_eq!(context, "Foo::<Foo>", ["execute()", "run()"]);
538
+ assert_members_eq!(context, "Foo", ["execute()", "run()"]);
539
+ }
540
+
541
+ #[test]
542
+ fn linearizing_super_classes() {
543
+ let mut context = GraphTest::new();
544
+ context.index_uri("file:///foo.rb", {
545
+ r"
546
+ class Foo; end
547
+ class Bar < Foo; end
548
+ class Baz < Bar; end
549
+ class Qux < Baz; end
550
+ "
551
+ });
552
+ context.resolve();
553
+
554
+ assert_no_diagnostics!(&context);
555
+
556
+ assert_ancestors_eq!(
557
+ context,
558
+ "Qux",
559
+ ["Qux", "Baz", "Bar", "Foo", "Object", "Kernel", "BasicObject"]
560
+ );
561
+ }
562
+
563
+ #[test]
564
+ fn descendants_are_tracked_for_parent_classes() {
565
+ let mut context = GraphTest::new();
566
+ context.index_uri("file:///foo.rb", {
567
+ r"
568
+ class Foo
569
+ CONST = 123
570
+ end
571
+
572
+ class Bar < Foo; end
573
+
574
+ class Baz < Bar
575
+ CONST
576
+ end
577
+
578
+ class Qux < Bar
579
+ CONST
580
+ end
581
+ "
582
+ });
583
+ context.resolve();
584
+
585
+ assert_no_diagnostics!(&context);
586
+
587
+ assert_descendants!(context, "Foo", ["Bar"]);
588
+ assert_descendants!(context, "Bar", ["Baz", "Qux"]);
589
+ }
590
+
591
+ #[test]
592
+ fn linearizing_circular_super_classes() {
593
+ let mut context = GraphTest::new();
594
+ context.index_uri("file:///foo.rb", {
595
+ r"
596
+ class Foo < Bar; end
597
+ class Bar < Baz; end
598
+ class Baz < Foo; end
599
+ "
600
+ });
601
+ context.resolve();
602
+
603
+ assert_no_diagnostics!(&context);
604
+
605
+ assert_ancestors_eq!(context, "Foo", ["Foo", "Bar", "Baz", "Object"]);
606
+ }
607
+
608
+ #[test]
609
+ fn resolving_a_constant_inherited_from_the_super_class() {
610
+ let mut context = GraphTest::new();
611
+ context.index_uri("file:///foo.rb", {
612
+ r"
613
+ class Foo
614
+ CONST = 123
615
+ end
616
+
617
+ class Bar < Foo
618
+ CONST
619
+ end
620
+ "
621
+ });
622
+ context.resolve();
623
+
624
+ assert_no_diagnostics!(&context);
625
+
626
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:6:3-6:8");
627
+ }
628
+
629
+ #[test]
630
+ fn does_not_loop_forever_on_non_existing_parents() {
631
+ let mut context = GraphTest::new();
632
+ context.index_uri("file:///foo.rb", {
633
+ r"
634
+ class Bar < Foo
635
+ CONST
636
+ end
637
+ "
638
+ });
639
+ context.resolve();
640
+
641
+ assert_no_diagnostics!(&context);
642
+
643
+ let declaration = context.graph().declarations().get(&DeclarationId::from("Bar")).unwrap();
644
+ assert!(matches!(
645
+ declaration.as_namespace().unwrap().clone_ancestors(),
646
+ Ancestors::Partial(_)
647
+ ));
648
+ }
649
+
650
+ #[test]
651
+ fn resolving_inherited_constant_dependent_on_complex_parent() {
652
+ let mut context = GraphTest::new();
653
+ context.index_uri("file:///foo.rb", {
654
+ r"
655
+ module Foo
656
+ module Bar
657
+ class Baz
658
+ CONST = 123
659
+ end
660
+ end
661
+ end
662
+ class Qux < Foo::Bar::Baz
663
+ CONST
664
+ end
665
+ "
666
+ });
667
+ context.resolve();
668
+
669
+ assert_no_diagnostics!(&context);
670
+
671
+ assert_constant_reference_to!(context, "Foo::Bar::Baz::CONST", "file:///foo.rb:9:3-9:8");
672
+ }
673
+
674
+ #[test]
675
+ fn resolution_for_instance_and_class_instance_variables() {
676
+ let mut context = GraphTest::new();
677
+ context.index_uri("file:///foo.rb", {
678
+ r"
679
+ class Foo
680
+ @foo = 0
681
+
682
+ def initialize
683
+ @bar = 1
684
+ end
685
+
686
+ def self.baz
687
+ @baz = 2
688
+ end
689
+
690
+ class << self
691
+ def qux
692
+ @qux = 3
693
+ end
694
+
695
+ def self.nested
696
+ @nested = 4
697
+ end
698
+ end
699
+ end
700
+ "
701
+ });
702
+ context.resolve();
703
+
704
+ assert_no_diagnostics!(&context);
705
+
706
+ assert_instance_variables_eq!(context, "Foo", ["@bar"]);
707
+ // @qux in `class << self; def qux` - self is Foo when called, so @qux belongs to Foo's singleton class
708
+ assert_instance_variables_eq!(context, "Foo::<Foo>", ["@baz", "@foo", "@qux"]);
709
+ assert_instance_variables_eq!(context, "Foo::<Foo>::<<Foo>>", ["@nested"]);
710
+ }
711
+
712
+ #[test]
713
+ fn resolution_for_instance_variables_with_dynamic_method_owner() {
714
+ let mut context = GraphTest::new();
715
+ context.index_uri("file:///foo.rb", {
716
+ r"
717
+ class Foo
718
+ end
719
+
720
+ class Bar
721
+ def Foo.bar
722
+ @foo = 0
723
+ end
724
+
725
+ class << Foo
726
+ def Bar.baz
727
+ @baz = 1
728
+ end
729
+ end
730
+ end
731
+ "
732
+ });
733
+ context.resolve();
734
+
735
+ assert_no_diagnostics!(&context);
736
+
737
+ assert_instance_variables_eq!(context, "Foo::<Foo>", ["@foo"]);
738
+ assert_instance_variables_eq!(context, "Bar::<Bar>", ["@baz"]);
739
+ }
740
+
741
+ #[test]
742
+ fn resolution_for_class_instance_variable_in_compact_namespace() {
743
+ let mut context = GraphTest::new();
744
+ context.index_uri("file:///foo.rb", {
745
+ r"
746
+ class Bar; end
747
+
748
+ class Foo
749
+ class Bar::Baz
750
+ @baz = 1
751
+ end
752
+ end
753
+ "
754
+ });
755
+ context.resolve();
756
+
757
+ assert_no_diagnostics!(&context);
758
+
759
+ // The class is `Bar::Baz`, so its singleton class is `Bar::Baz::<Baz>`
760
+ assert_instance_variables_eq!(context, "Bar::Baz::<Baz>", ["@baz"]);
761
+ }
762
+
763
+ #[test]
764
+ fn resolution_for_instance_variable_in_singleton_class_body() {
765
+ let mut context = GraphTest::new();
766
+ context.index_uri("file:///foo.rb", {
767
+ r"
768
+ class Foo
769
+ class << self
770
+ @bar = 1
771
+
772
+ class << self
773
+ @baz = 2
774
+ end
775
+ end
776
+ end
777
+ "
778
+ });
779
+ context.resolve();
780
+
781
+ assert_no_diagnostics!(&context);
782
+
783
+ assert_instance_variables_eq!(context, "Foo::<Foo>::<<Foo>>", ["@bar"]);
784
+ assert_instance_variables_eq!(context, "Foo::<Foo>::<<Foo>>::<<<Foo>>>", ["@baz"]);
785
+ }
786
+
787
+ #[test]
788
+ fn resolution_for_instance_variable_in_constant_receiver_method() {
789
+ let mut context = GraphTest::new();
790
+ context.index_uri(
791
+ "file:///foo.rb",
792
+ r"
793
+ class Foo; end
794
+
795
+ def Foo.bar
796
+ @bar = 1
797
+ end
798
+ ",
799
+ );
800
+ context.resolve();
801
+
802
+ assert_no_diagnostics!(&context);
803
+
804
+ assert_declaration_exists!(context, "Foo::<Foo>#bar()");
805
+ assert_instance_variables_eq!(context, "Foo::<Foo>", ["@bar"]);
806
+ }
807
+
808
+ #[test]
809
+ fn resolution_for_top_level_instance_variable() {
810
+ let mut context = GraphTest::new();
811
+ context.index_uri("file:///foo.rb", {
812
+ r"
813
+ @foo = 0
814
+ "
815
+ });
816
+ context.resolve();
817
+
818
+ assert_no_diagnostics!(&context);
819
+
820
+ // Top-level instance variables belong to `<main>`, not `Object`.
821
+ // We can't represent `<main>` yet, so no declaration is created.
822
+ assert_declaration_does_not_exist!(context, "Object::@foo");
823
+ }
824
+
825
+ #[test]
826
+ fn resolution_for_instance_variable_with_unresolved_receiver() {
827
+ let mut context = GraphTest::new();
828
+ context.index_uri("file:///foo.rb", {
829
+ r"
830
+ class Foo
831
+ def foo.bar
832
+ @baz = 0
833
+ end
834
+ end
835
+ "
836
+ });
837
+ context.resolve();
838
+
839
+ assert_diagnostics_eq!(
840
+ &context,
841
+ ["dynamic-singleton-definition: Dynamic receiver for singleton method definition (2:3-4:6)",]
842
+ );
843
+
844
+ // Instance variable in method with unresolved receiver should not create a declaration
845
+ assert_declaration_does_not_exist!(context, "Object::@baz");
846
+ assert_declaration_does_not_exist!(context, "Foo::@baz");
847
+ }
848
+
849
+ #[test]
850
+ fn resolving_method_alias() {
851
+ let mut context = GraphTest::new();
852
+ context.index_uri("file:///foo.rb", {
853
+ r"
854
+ class Foo
855
+ def foo; end
856
+
857
+ alias bar foo
858
+ end
859
+ "
860
+ });
861
+ context.resolve();
862
+
863
+ assert_no_diagnostics!(&context);
864
+
865
+ assert_members_eq!(context, "Foo", ["bar()", "foo()"]);
866
+ }
867
+
868
+ #[test]
869
+ fn resolving_method_alias_with_self_receiver() {
870
+ // SelfReceiver resolves to instance methods (the class directly), not the singleton
871
+ let mut context = GraphTest::new();
872
+ context.index_uri("file:///foo.rb", {
873
+ r"
874
+ class Foo
875
+ def original; end
876
+ self.alias_method :aliased, :original
877
+ end
878
+ "
879
+ });
880
+ context.resolve();
881
+
882
+ assert_no_diagnostics!(&context);
883
+
884
+ assert_members_eq!(context, "Foo", ["aliased()", "original()"]);
885
+ }
886
+
887
+ #[test]
888
+ fn resolving_alias_method_in_singleton_class_lands_on_singleton() {
889
+ // `class << self; alias_method ...; end` — alias lands on singleton via lexical nesting
890
+ let mut context = GraphTest::new();
891
+ context.index_uri("file:///foo.rb", {
892
+ r"
893
+ class Foo
894
+ def self.find; end
895
+
896
+ class << self
897
+ alias_method :find_old, :find
898
+ end
899
+ end
900
+ "
901
+ });
902
+ context.resolve();
903
+
904
+ assert_no_diagnostics!(&context);
905
+
906
+ assert_members_eq!(context, "Foo::<Foo>", ["find()", "find_old()"]);
907
+ }
908
+
909
+ #[test]
910
+ fn resolving_self_alias_method_is_equivalent_to_bare_alias_method() {
911
+ // `self.alias_method` and bare `alias_method` resolve identically (instance methods)
912
+ let mut context = GraphTest::new();
913
+ context.index_uri("file:///with_self.rb", {
914
+ r"
915
+ class WithSelf
916
+ def original; end
917
+ self.alias_method :aliased, :original
918
+ end
919
+ "
920
+ });
921
+ context.index_uri("file:///without_self.rb", {
922
+ r"
923
+ class WithoutSelf
924
+ def original; end
925
+ alias_method :aliased, :original
926
+ end
927
+ "
928
+ });
929
+ context.resolve();
930
+
931
+ assert_no_diagnostics!(&context);
932
+
933
+ // Both resolve identically: alias lands on instance methods
934
+ assert_members_eq!(context, "WithSelf", ["aliased()", "original()"]);
935
+ assert_members_eq!(context, "WithoutSelf", ["aliased()", "original()"]);
936
+ }
937
+
938
+ #[test]
939
+ fn resolving_method_alias_with_constant_receiver() {
940
+ let mut context = GraphTest::new();
941
+ context.index_uri("file:///foo.rb", {
942
+ r"
943
+ class Bar
944
+ def to_s; end
945
+ end
946
+
947
+ class Foo
948
+ Bar.alias_method(:new_to_s, :to_s)
949
+ end
950
+ "
951
+ });
952
+ context.resolve();
953
+
954
+ assert_no_diagnostics!(&context);
955
+
956
+ // Bar.alias_method places the alias on Bar's instance methods
957
+ assert_no_members!(context, "Foo");
958
+ assert_members_eq!(context, "Bar", ["new_to_s()", "to_s()"]);
959
+ }
960
+
961
+ #[test]
962
+ fn resolving_global_variable_alias() {
963
+ let mut context = GraphTest::new();
964
+ context.index_uri("file:///foo.rb", {
965
+ r"
966
+ $foo = 123
967
+ alias $bar $foo
968
+ "
969
+ });
970
+ context.resolve();
971
+
972
+ assert_no_diagnostics!(&context);
973
+
974
+ assert_members_eq!(
975
+ context,
976
+ "Object",
977
+ ["$bar", "$foo", "BasicObject", "Class", "Kernel", "Module", "Object"]
978
+ );
979
+ }
980
+
981
+ #[test]
982
+ fn linearizing_parent_classes_with_parent_scope() {
983
+ let mut context = GraphTest::new();
984
+ context.index_uri("file:///foo.rb", {
985
+ r"
986
+ module Foo
987
+ class Bar
988
+ end
989
+ end
990
+ class Baz < Foo::Bar
991
+ end
992
+ "
993
+ });
994
+ context.resolve();
995
+
996
+ assert_no_diagnostics!(&context);
997
+
998
+ assert_ancestors_eq!(context, "Baz", ["Baz", "Foo::Bar", "Object", "Kernel", "BasicObject"]);
999
+ }
1000
+
1001
+ #[test]
1002
+ fn resolving_constant_references_involved_in_prepends() {
1003
+ let mut context = GraphTest::new();
1004
+
1005
+ // To linearize the ancestors of `Bar`, we need to resolve `Foo` first. However, during that resolution, we need
1006
+ // to check `Bar`'s ancestor chain before checking the top level (which is where we'll find `Foo`). In these
1007
+ // scenarios, we need to realize the dependency and skip ancestors
1008
+ context.index_uri("file:///foo.rb", {
1009
+ r"
1010
+ module Foo; end
1011
+ module Bar
1012
+ prepend Foo
1013
+ end
1014
+ "
1015
+ });
1016
+ context.resolve();
1017
+
1018
+ assert_no_diagnostics!(&context);
1019
+
1020
+ assert_ancestors_eq!(context, "Bar", ["Foo", "Bar"]);
1021
+ }
1022
+
1023
+ #[test]
1024
+ fn resolving_prepend_using_inherited_constant() {
1025
+ let mut context = GraphTest::new();
1026
+ // Prepending `Foo` makes `Bar` available, which we can then prepend as well. This requires resolving constants
1027
+ // with partially linearized ancestors
1028
+ context.index_uri("file:///foo.rb", {
1029
+ r"
1030
+ module Foo
1031
+ module Bar; end
1032
+ end
1033
+ class Baz
1034
+ prepend Foo
1035
+ prepend Bar
1036
+ end
1037
+ "
1038
+ });
1039
+ context.resolve();
1040
+
1041
+ assert_no_diagnostics!(&context);
1042
+
1043
+ assert_ancestors_eq!(
1044
+ context,
1045
+ "Baz",
1046
+ ["Foo::Bar", "Foo", "Baz", "Object", "Kernel", "BasicObject"]
1047
+ );
1048
+ }
1049
+
1050
+ #[test]
1051
+ fn linearizing_prepended_modules() {
1052
+ let mut context = GraphTest::new();
1053
+ context.index_uri("file:///foo.rb", {
1054
+ r"
1055
+ module Foo; end
1056
+ module Bar
1057
+ prepend Foo
1058
+ end
1059
+ class Baz
1060
+ prepend Bar
1061
+ end
1062
+ class Qux < Baz; end
1063
+ "
1064
+ });
1065
+ context.resolve();
1066
+
1067
+ assert_no_diagnostics!(&context);
1068
+
1069
+ assert_ancestors_eq!(context, "Foo", ["Foo"]);
1070
+ assert_ancestors_eq!(context, "Bar", ["Foo", "Bar"]);
1071
+ assert_ancestors_eq!(
1072
+ context,
1073
+ "Qux",
1074
+ ["Qux", "Foo", "Bar", "Baz", "Object", "Kernel", "BasicObject"]
1075
+ );
1076
+ }
1077
+
1078
+ #[test]
1079
+ fn prepend_on_dynamic_namespace_definitions() {
1080
+ let mut context = GraphTest::new();
1081
+ context.index_uri("file:///foo.rb", {
1082
+ r"
1083
+ module B; end
1084
+ A = Struct.new do
1085
+ prepend B
1086
+ end
1087
+
1088
+ C = Class.new do
1089
+ prepend B
1090
+ end
1091
+
1092
+ D = Module.new do
1093
+ prepend B
1094
+ end
1095
+ "
1096
+ });
1097
+ context.resolve();
1098
+
1099
+ assert_no_diagnostics!(&context);
1100
+
1101
+ assert_ancestors_eq!(context, "B", ["B"]);
1102
+ // TODO: this is a temporary hack to avoid crashing on `Struct.new`, `Class.new` and `Module.new`
1103
+ //assert_ancestors_eq!(context, "A", Vec::<&str>::new());
1104
+ assert_ancestors_eq!(context, "C", ["B", "C", "Object", "Kernel", "BasicObject"]);
1105
+ assert_ancestors_eq!(context, "D", ["B", "D"]);
1106
+ }
1107
+
1108
+ #[test]
1109
+ fn prepends_track_descendants() {
1110
+ let mut context = GraphTest::new();
1111
+ context.index_uri("file:///foo.rb", {
1112
+ r"
1113
+ module Foo; end
1114
+ module Bar
1115
+ prepend Foo
1116
+ end
1117
+ class Baz
1118
+ prepend Bar
1119
+ end
1120
+ "
1121
+ });
1122
+ context.resolve();
1123
+
1124
+ assert_no_diagnostics!(&context);
1125
+
1126
+ assert_descendants!(context, "Foo", ["Bar", "Baz"]);
1127
+ assert_descendants!(context, "Bar", ["Baz"]);
1128
+ }
1129
+
1130
+ #[test]
1131
+ fn cyclic_prepend() {
1132
+ let mut context = GraphTest::new();
1133
+ context.index_uri("file:///foo.rb", {
1134
+ r"
1135
+ module Foo
1136
+ prepend Foo
1137
+ end
1138
+ "
1139
+ });
1140
+ context.resolve();
1141
+
1142
+ assert_no_diagnostics!(&context);
1143
+
1144
+ assert_ancestors_eq!(context, "Foo", ["Foo"]);
1145
+ }
1146
+
1147
+ #[test]
1148
+ fn duplicate_prepends() {
1149
+ let mut context = GraphTest::new();
1150
+ context.index_uri("file:///foo.rb", {
1151
+ r"
1152
+ module Foo
1153
+ end
1154
+
1155
+ module Bar
1156
+ prepend Foo
1157
+ prepend Foo
1158
+ end
1159
+ "
1160
+ });
1161
+ context.resolve();
1162
+
1163
+ assert_no_diagnostics!(&context);
1164
+
1165
+ assert_ancestors_eq!(context, "Bar", ["Foo", "Bar"]);
1166
+ }
1167
+
1168
+ #[test]
1169
+ fn indirect_duplicate_prepends() {
1170
+ let mut context = GraphTest::new();
1171
+ context.index_uri("file:///foo.rb", {
1172
+ r"
1173
+ module A; end
1174
+
1175
+ module B
1176
+ prepend A
1177
+ end
1178
+
1179
+ module C
1180
+ prepend A
1181
+ end
1182
+
1183
+ module Foo
1184
+ prepend B
1185
+ prepend C
1186
+ end
1187
+ "
1188
+ });
1189
+ context.resolve();
1190
+
1191
+ assert_no_diagnostics!(&context);
1192
+
1193
+ assert_ancestors_eq!(context, "A", ["A"]);
1194
+ assert_ancestors_eq!(context, "B", ["A", "B"]);
1195
+ assert_ancestors_eq!(context, "C", ["A", "C"]);
1196
+ assert_ancestors_eq!(context, "Foo", ["A", "C", "B", "Foo"]);
1197
+ }
1198
+
1199
+ #[test]
1200
+ fn multiple_mixins_in_same_prepend() {
1201
+ let mut context = GraphTest::new();
1202
+ context.index_uri("file:///foo.rb", {
1203
+ r"
1204
+ module A; end
1205
+ module B; end
1206
+
1207
+ class Foo
1208
+ prepend A, B
1209
+ end
1210
+ "
1211
+ });
1212
+ context.resolve();
1213
+
1214
+ assert_no_diagnostics!(&context);
1215
+
1216
+ assert_ancestors_eq!(context, "Foo", ["A", "B", "Foo", "Object", "Kernel", "BasicObject"]);
1217
+ }
1218
+
1219
+ #[test]
1220
+ fn prepends_involving_parent_scopes() {
1221
+ let mut context = GraphTest::new();
1222
+ context.index_uri("file:///foo.rb", {
1223
+ r"
1224
+ module A
1225
+ module B
1226
+ module C; end
1227
+ end
1228
+ end
1229
+
1230
+ module D
1231
+ prepend A::B::C
1232
+ end
1233
+
1234
+ module Foo
1235
+ prepend D
1236
+ prepend A::B::C
1237
+ end
1238
+
1239
+ module Bar
1240
+ prepend A::B::C
1241
+ prepend D
1242
+ end
1243
+ "
1244
+ });
1245
+ context.resolve();
1246
+
1247
+ assert_no_diagnostics!(&context);
1248
+
1249
+ assert_ancestors_eq!(context, "Foo", ["A::B::C", "D", "Foo"]);
1250
+ assert_ancestors_eq!(context, "Bar", ["A::B::C", "D", "Bar"]);
1251
+ }
1252
+
1253
+ #[test]
1254
+ fn duplicate_prepends_in_parents() {
1255
+ let mut context = GraphTest::new();
1256
+ context.index_uri("file:///foo.rb", {
1257
+ r"
1258
+ module A; end
1259
+
1260
+ module B
1261
+ prepend A
1262
+ end
1263
+
1264
+ class Parent
1265
+ prepend B
1266
+ end
1267
+
1268
+ class Child < Parent
1269
+ prepend B
1270
+ end
1271
+ "
1272
+ });
1273
+ context.resolve();
1274
+
1275
+ assert_no_diagnostics!(&context);
1276
+
1277
+ assert_ancestors_eq!(
1278
+ context,
1279
+ "Child",
1280
+ ["A", "B", "Child", "A", "B", "Parent", "Object", "Kernel", "BasicObject"]
1281
+ );
1282
+ }
1283
+
1284
+ #[test]
1285
+ fn prepended_modules_involved_in_definitions() {
1286
+ let mut context = GraphTest::new();
1287
+ context.index_uri("file:///foo.rb", {
1288
+ r"
1289
+ module Foo
1290
+ module Bar; end
1291
+ end
1292
+
1293
+ module Baz
1294
+ prepend Foo
1295
+
1296
+ class Bar::Qux
1297
+ end
1298
+ end
1299
+ "
1300
+ });
1301
+ context.resolve();
1302
+
1303
+ assert_no_diagnostics!(&context);
1304
+
1305
+ assert_members_eq!(context, "Foo::Bar", ["Qux"]);
1306
+ assert_owner_eq!(context, "Foo::Bar", "Foo");
1307
+
1308
+ assert_no_members!(context, "Foo::Bar::Qux");
1309
+ assert_owner_eq!(context, "Foo::Bar::Qux", "Foo::Bar");
1310
+ }
1311
+
1312
+ #[test]
1313
+ fn resolving_constant_references_involved_in_includes() {
1314
+ let mut context = GraphTest::new();
1315
+ context.index_uri("file:///foo.rb", {
1316
+ r"
1317
+ module Foo; end
1318
+ module Bar
1319
+ include Foo
1320
+ end
1321
+ "
1322
+ });
1323
+ context.resolve();
1324
+
1325
+ assert_no_diagnostics!(&context);
1326
+
1327
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Foo"]);
1328
+ }
1329
+
1330
+ #[test]
1331
+ fn resolving_include_using_inherited_constant() {
1332
+ let mut context = GraphTest::new();
1333
+ context.index_uri("file:///foo.rb", {
1334
+ r"
1335
+ module Foo
1336
+ module Bar; end
1337
+ end
1338
+ class Baz
1339
+ include Foo
1340
+ include Bar
1341
+ end
1342
+ "
1343
+ });
1344
+ context.resolve();
1345
+
1346
+ assert_no_diagnostics!(&context);
1347
+
1348
+ assert_ancestors_eq!(
1349
+ context,
1350
+ "Baz",
1351
+ ["Baz", "Foo::Bar", "Foo", "Object", "Kernel", "BasicObject"]
1352
+ );
1353
+ }
1354
+
1355
+ #[test]
1356
+ fn linearizing_included_modules() {
1357
+ let mut context = GraphTest::new();
1358
+ context.index_uri("file:///foo.rb", {
1359
+ r"
1360
+ module Foo; end
1361
+ module Bar
1362
+ prepend Foo
1363
+ end
1364
+ class Baz
1365
+ prepend Bar
1366
+ end
1367
+ class Qux < Baz; end
1368
+ "
1369
+ });
1370
+ context.resolve();
1371
+
1372
+ assert_no_diagnostics!(&context);
1373
+
1374
+ assert_ancestors_eq!(context, "Foo", ["Foo"]);
1375
+ assert_ancestors_eq!(context, "Bar", ["Foo", "Bar"]);
1376
+ assert_ancestors_eq!(
1377
+ context,
1378
+ "Qux",
1379
+ ["Qux", "Foo", "Bar", "Baz", "Object", "Kernel", "BasicObject"]
1380
+ );
1381
+ }
1382
+
1383
+ #[test]
1384
+ fn include_on_dynamic_namespace_definitions() {
1385
+ let mut context = GraphTest::new();
1386
+ context.index_uri("file:///foo.rb", {
1387
+ r"
1388
+ module B; end
1389
+ A = Struct.new do
1390
+ include B
1391
+ end
1392
+
1393
+ C = Class.new do
1394
+ include B
1395
+ end
1396
+
1397
+ D = Module.new do
1398
+ include B
1399
+ end
1400
+ "
1401
+ });
1402
+ context.resolve();
1403
+
1404
+ assert_no_diagnostics!(&context);
1405
+
1406
+ assert_ancestors_eq!(context, "B", ["B"]);
1407
+ // TODO: this is a temporary hack to avoid crashing on `Struct.new`, `Class.new` and `Module.new`
1408
+ //assert_ancestors_eq!(context, "A", Vec::<&str>::new());
1409
+ assert_ancestors_eq!(context, "C", ["C", "B", "Object", "Kernel", "BasicObject"]);
1410
+ assert_ancestors_eq!(context, "D", ["D", "B"]);
1411
+ }
1412
+
1413
+ #[test]
1414
+ fn cyclic_include() {
1415
+ let mut context = GraphTest::new();
1416
+ context.index_uri("file:///foo.rb", {
1417
+ r"
1418
+ module Foo
1419
+ include Foo
1420
+ end
1421
+ "
1422
+ });
1423
+ context.resolve();
1424
+
1425
+ assert_no_diagnostics!(&context);
1426
+
1427
+ assert_ancestors_eq!(context, "Foo", ["Foo"]);
1428
+ }
1429
+
1430
+ #[test]
1431
+ fn duplicate_includes() {
1432
+ let mut context = GraphTest::new();
1433
+ context.index_uri("file:///foo.rb", {
1434
+ r"
1435
+ module Foo
1436
+ end
1437
+
1438
+ module Bar
1439
+ include Foo
1440
+ include Foo
1441
+ end
1442
+ "
1443
+ });
1444
+ context.resolve();
1445
+
1446
+ assert_no_diagnostics!(&context);
1447
+
1448
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Foo"]);
1449
+ }
1450
+
1451
+ #[test]
1452
+ fn indirect_duplicate_includes() {
1453
+ let mut context = GraphTest::new();
1454
+ context.index_uri("file:///foo.rb", {
1455
+ r"
1456
+ module A; end
1457
+
1458
+ module B
1459
+ include A
1460
+ end
1461
+
1462
+ module C
1463
+ include A
1464
+ end
1465
+
1466
+ module Foo
1467
+ include B
1468
+ include C
1469
+ end
1470
+ "
1471
+ });
1472
+ context.resolve();
1473
+
1474
+ assert_no_diagnostics!(&context);
1475
+
1476
+ assert_ancestors_eq!(context, "A", ["A"]);
1477
+ assert_ancestors_eq!(context, "B", ["B", "A"]);
1478
+ assert_ancestors_eq!(context, "C", ["C", "A"]);
1479
+ assert_ancestors_eq!(context, "Foo", ["Foo", "C", "B", "A"]);
1480
+ }
1481
+
1482
+ #[test]
1483
+ fn includes_involving_parent_scopes() {
1484
+ let mut context = GraphTest::new();
1485
+ context.index_uri("file:///foo.rb", {
1486
+ r"
1487
+ module A
1488
+ module B
1489
+ module C; end
1490
+ end
1491
+ end
1492
+
1493
+ module D
1494
+ include A::B::C
1495
+ end
1496
+
1497
+ module Foo
1498
+ include D
1499
+ include A::B::C
1500
+ end
1501
+
1502
+ module Bar
1503
+ include A::B::C
1504
+ include D
1505
+ end
1506
+ "
1507
+ });
1508
+ context.resolve();
1509
+
1510
+ assert_no_diagnostics!(&context);
1511
+
1512
+ assert_ancestors_eq!(context, "Foo", ["Foo", "D", "A::B::C"]);
1513
+ assert_ancestors_eq!(context, "Bar", ["Bar", "D", "A::B::C"]);
1514
+ }
1515
+
1516
+ #[test]
1517
+ fn duplicate_includes_in_parents() {
1518
+ let mut context = GraphTest::new();
1519
+ context.index_uri("file:///foo.rb", {
1520
+ r"
1521
+ module A; end
1522
+
1523
+ module B
1524
+ include A
1525
+ end
1526
+
1527
+ class Parent
1528
+ include B
1529
+ end
1530
+
1531
+ class Child < Parent
1532
+ include B
1533
+ end
1534
+ "
1535
+ });
1536
+ context.resolve();
1537
+
1538
+ assert_no_diagnostics!(&context);
1539
+
1540
+ assert_ancestors_eq!(
1541
+ context,
1542
+ "Child",
1543
+ ["Child", "Parent", "B", "A", "Object", "Kernel", "BasicObject"]
1544
+ );
1545
+ }
1546
+
1547
+ #[test]
1548
+ fn included_modules_involved_in_definitions() {
1549
+ let mut context = GraphTest::new();
1550
+ context.index_uri("file:///foo.rb", {
1551
+ r"
1552
+ module Foo
1553
+ module Bar; end
1554
+ end
1555
+
1556
+ module Baz
1557
+ include Foo
1558
+
1559
+ class Bar::Qux
1560
+ end
1561
+ end
1562
+ "
1563
+ });
1564
+ context.resolve();
1565
+
1566
+ assert_no_diagnostics!(&context);
1567
+
1568
+ assert_members_eq!(context, "Foo::Bar", ["Qux"]);
1569
+ assert_owner_eq!(context, "Foo::Bar", "Foo");
1570
+
1571
+ assert_no_members!(context, "Foo::Bar::Qux");
1572
+ assert_owner_eq!(context, "Foo::Bar::Qux", "Foo::Bar");
1573
+ }
1574
+
1575
+ #[test]
1576
+ fn references_with_parent_scope_search_inheritance() {
1577
+ let mut context = GraphTest::new();
1578
+ context.index_uri("file:///foo.rb", {
1579
+ r"
1580
+ module Foo
1581
+ module Bar; end
1582
+ end
1583
+
1584
+ class Baz
1585
+ include Foo
1586
+ end
1587
+
1588
+ Baz::Bar
1589
+ "
1590
+ });
1591
+ context.resolve();
1592
+
1593
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
1594
+
1595
+ assert_constant_reference_to!(context, "Foo::Bar", "file:///foo.rb:9:6-9:9");
1596
+ }
1597
+
1598
+ #[test]
1599
+ fn duplicate_includes_and_prepends() {
1600
+ let mut context = GraphTest::new();
1601
+ context.index_uri("file:///foo.rb", {
1602
+ r"
1603
+ module A; end
1604
+
1605
+ class Foo
1606
+ prepend A
1607
+ include A
1608
+ end
1609
+
1610
+ class Bar
1611
+ include A
1612
+ prepend A
1613
+ end
1614
+ "
1615
+ });
1616
+ context.resolve();
1617
+
1618
+ assert_no_diagnostics!(&context);
1619
+
1620
+ assert_ancestors_eq!(context, "Foo", ["A", "Foo", "Object", "Kernel", "BasicObject"]);
1621
+ assert_ancestors_eq!(context, "Bar", ["A", "Bar", "A", "Object", "Kernel", "BasicObject"]);
1622
+ }
1623
+
1624
+ #[test]
1625
+ fn duplicate_indirect_includes_and_prepends() {
1626
+ let mut context = GraphTest::new();
1627
+ context.index_uri("file:///foo.rb", {
1628
+ r"
1629
+ module A; end
1630
+ module B
1631
+ include A
1632
+ end
1633
+ module C
1634
+ prepend A
1635
+ end
1636
+
1637
+ class Foo
1638
+ include C
1639
+ prepend B
1640
+ include A
1641
+ end
1642
+
1643
+ class Bar
1644
+ include A
1645
+ prepend B
1646
+ include C
1647
+ end
1648
+
1649
+ class Baz
1650
+ prepend B
1651
+ include C
1652
+ prepend A
1653
+ end
1654
+
1655
+ class Qux
1656
+ prepend A
1657
+ include C
1658
+ prepend B
1659
+ end
1660
+ "
1661
+ });
1662
+ context.resolve();
1663
+
1664
+ assert_no_diagnostics!(&context);
1665
+
1666
+ assert_ancestors_eq!(
1667
+ context,
1668
+ "Foo",
1669
+ ["B", "A", "Foo", "A", "C", "Object", "Kernel", "BasicObject"]
1670
+ );
1671
+ assert_ancestors_eq!(
1672
+ context,
1673
+ "Bar",
1674
+ ["B", "A", "Bar", "C", "A", "Object", "Kernel", "BasicObject"]
1675
+ );
1676
+ assert_ancestors_eq!(
1677
+ context,
1678
+ "Baz",
1679
+ ["B", "A", "Baz", "C", "Object", "Kernel", "BasicObject"]
1680
+ );
1681
+ assert_ancestors_eq!(
1682
+ context,
1683
+ "Qux",
1684
+ ["B", "A", "Qux", "C", "Object", "Kernel", "BasicObject"]
1685
+ );
1686
+ }
1687
+
1688
+ #[test]
1689
+ fn duplicate_includes_and_prepends_through_parents() {
1690
+ let mut context = GraphTest::new();
1691
+ context.index_uri("file:///foo.rb", {
1692
+ r"
1693
+ module A; end
1694
+
1695
+ class Parent
1696
+ include A
1697
+ end
1698
+
1699
+ class Foo < Parent
1700
+ prepend A
1701
+ end
1702
+
1703
+ class Bar < Parent
1704
+ include A
1705
+ end
1706
+ "
1707
+ });
1708
+ context.resolve();
1709
+
1710
+ assert_no_diagnostics!(&context);
1711
+
1712
+ assert_ancestors_eq!(
1713
+ context,
1714
+ "Foo",
1715
+ ["A", "Foo", "Parent", "A", "Object", "Kernel", "BasicObject"]
1716
+ );
1717
+ assert_ancestors_eq!(
1718
+ context,
1719
+ "Bar",
1720
+ ["Bar", "Parent", "A", "Object", "Kernel", "BasicObject"]
1721
+ );
1722
+ }
1723
+
1724
+ #[test]
1725
+ fn multiple_mixins_in_same_include() {
1726
+ let mut context = GraphTest::new();
1727
+ context.index_uri("file:///foo.rb", {
1728
+ r"
1729
+ module A; end
1730
+ module B; end
1731
+
1732
+ class Foo
1733
+ include A, B
1734
+ end
1735
+ "
1736
+ });
1737
+ context.resolve();
1738
+
1739
+ assert_no_diagnostics!(&context);
1740
+
1741
+ assert_ancestors_eq!(context, "Foo", ["Foo", "A", "B", "Object", "Kernel", "BasicObject"]);
1742
+ }
1743
+
1744
+ #[test]
1745
+ fn descendants_are_tracked_for_includes() {
1746
+ let mut context = GraphTest::new();
1747
+ context.index_uri("file:///foo.rb", {
1748
+ r"
1749
+ module Foo; end
1750
+ module Bar
1751
+ include Foo
1752
+ end
1753
+ module Baz
1754
+ include Bar
1755
+ end
1756
+ "
1757
+ });
1758
+ context.resolve();
1759
+
1760
+ assert_no_diagnostics!(&context);
1761
+
1762
+ assert_descendants!(context, "Bar", ["Baz"]);
1763
+ assert_descendants!(context, "Foo", ["Bar", "Baz"]);
1764
+ }
1765
+
1766
+ #[test]
1767
+ fn singleton_ancestors_for_classes() {
1768
+ let mut context = GraphTest::new();
1769
+ context.index_uri("file:///foo.rb", {
1770
+ r"
1771
+ module Foo; end
1772
+ module Qux; end
1773
+ module Zip; end
1774
+ class Bar; end
1775
+
1776
+ class Baz < Bar
1777
+ extend Foo
1778
+
1779
+ class << self
1780
+ include Qux
1781
+
1782
+ class << self
1783
+ include Zip
1784
+ end
1785
+ end
1786
+ end
1787
+ "
1788
+ });
1789
+ context.resolve();
1790
+
1791
+ assert_no_diagnostics!(&context);
1792
+
1793
+ assert_ancestors_eq!(
1794
+ context,
1795
+ "Baz::<Baz>",
1796
+ [
1797
+ "Baz::<Baz>",
1798
+ "Qux",
1799
+ "Foo",
1800
+ "Bar::<Bar>",
1801
+ "Object::<Object>",
1802
+ "BasicObject::<BasicObject>",
1803
+ "Class",
1804
+ "Module",
1805
+ "Object",
1806
+ "Kernel",
1807
+ "BasicObject"
1808
+ ]
1809
+ );
1810
+
1811
+ assert_ancestors_eq!(
1812
+ context,
1813
+ "Baz::<Baz>::<<Baz>>",
1814
+ [
1815
+ "Baz::<Baz>::<<Baz>>",
1816
+ "Zip",
1817
+ "Bar::<Bar>::<<Bar>>",
1818
+ "Object::<Object>::<<Object>>",
1819
+ "BasicObject::<BasicObject>::<<BasicObject>>",
1820
+ "Class::<Class>",
1821
+ "Module::<Module>",
1822
+ "Object::<Object>",
1823
+ "BasicObject::<BasicObject>",
1824
+ "Class",
1825
+ "Module",
1826
+ "Object",
1827
+ "Kernel",
1828
+ "BasicObject"
1829
+ ]
1830
+ );
1831
+ }
1832
+
1833
+ #[test]
1834
+ fn singleton_ancestors_for_modules() {
1835
+ let mut context = GraphTest::new();
1836
+ context.index_uri("file:///foo.rb", {
1837
+ r"
1838
+ module Foo; end
1839
+ module Qux; end
1840
+ module Zip; end
1841
+ class Bar; end
1842
+
1843
+ module Baz
1844
+ extend Foo
1845
+
1846
+ class << self
1847
+ include Qux
1848
+
1849
+ class << self
1850
+ include Zip
1851
+ end
1852
+ end
1853
+ end
1854
+ "
1855
+ });
1856
+ context.resolve();
1857
+
1858
+ assert_no_diagnostics!(&context);
1859
+
1860
+ assert_ancestors_eq!(
1861
+ context,
1862
+ "Baz::<Baz>",
1863
+ ["Baz::<Baz>", "Qux", "Foo", "Module", "Object", "Kernel", "BasicObject"]
1864
+ );
1865
+ assert_ancestors_eq!(
1866
+ context,
1867
+ "Baz::<Baz>::<<Baz>>",
1868
+ [
1869
+ "Baz::<Baz>::<<Baz>>",
1870
+ "Zip",
1871
+ "Module::<Module>",
1872
+ "Object::<Object>",
1873
+ "BasicObject::<BasicObject>",
1874
+ "Class",
1875
+ "Module",
1876
+ "Object",
1877
+ "Kernel",
1878
+ "BasicObject"
1879
+ ]
1880
+ );
1881
+ }
1882
+
1883
+ #[test]
1884
+ fn singleton_ancestors_with_inherited_parent_modules() {
1885
+ let mut context = GraphTest::new();
1886
+ context.index_uri("file:///foo.rb", {
1887
+ r"
1888
+ module Foo; end
1889
+ module Qux; end
1890
+ class Bar
1891
+ class << self
1892
+ include Foo
1893
+ prepend Qux
1894
+ end
1895
+ end
1896
+
1897
+ class Baz < Bar
1898
+ class << self
1899
+ class << self
1900
+ end
1901
+ end
1902
+ end
1903
+ "
1904
+ });
1905
+ context.resolve();
1906
+
1907
+ assert_no_diagnostics!(&context);
1908
+
1909
+ assert_ancestors_eq!(
1910
+ context,
1911
+ "Bar::<Bar>",
1912
+ [
1913
+ "Qux",
1914
+ "Bar::<Bar>",
1915
+ "Foo",
1916
+ "Object::<Object>",
1917
+ "BasicObject::<BasicObject>",
1918
+ "Class",
1919
+ "Module",
1920
+ "Object",
1921
+ "Kernel",
1922
+ "BasicObject"
1923
+ ]
1924
+ );
1925
+
1926
+ assert_ancestors_eq!(
1927
+ context,
1928
+ "Baz::<Baz>",
1929
+ [
1930
+ "Baz::<Baz>",
1931
+ "Qux",
1932
+ "Bar::<Bar>",
1933
+ "Foo",
1934
+ "Object::<Object>",
1935
+ "BasicObject::<BasicObject>",
1936
+ "Class",
1937
+ "Module",
1938
+ "Object",
1939
+ "Kernel",
1940
+ "BasicObject"
1941
+ ]
1942
+ );
1943
+ assert_ancestors_eq!(
1944
+ context,
1945
+ "Baz::<Baz>::<<Baz>>",
1946
+ [
1947
+ "Baz::<Baz>::<<Baz>>",
1948
+ "Bar::<Bar>::<<Bar>>",
1949
+ "Object::<Object>::<<Object>>",
1950
+ "BasicObject::<BasicObject>::<<BasicObject>>",
1951
+ "Class::<Class>",
1952
+ "Module::<Module>",
1953
+ "Object::<Object>",
1954
+ "BasicObject::<BasicObject>",
1955
+ "Class",
1956
+ "Module",
1957
+ "Object",
1958
+ "Kernel",
1959
+ "BasicObject"
1960
+ ]
1961
+ );
1962
+ }
1963
+
1964
+ #[test]
1965
+ fn resolving_global_variable_alias_inside_method() {
1966
+ let mut context = GraphTest::new();
1967
+ context.index_uri("file:///foo.rb", {
1968
+ r"
1969
+ class Foo
1970
+ def setup
1971
+ alias $bar $baz
1972
+ end
1973
+ end
1974
+ "
1975
+ });
1976
+ context.resolve();
1977
+
1978
+ assert_no_diagnostics!(&context);
1979
+
1980
+ // Global variable aliases should still be owned by Object, regardless of where defined
1981
+ assert_members_eq!(
1982
+ context,
1983
+ "Object",
1984
+ ["$bar", "BasicObject", "Class", "Foo", "Kernel", "Module", "Object"]
1985
+ );
1986
+ }
1987
+
1988
+ #[test]
1989
+ fn resolving_method_defined_inside_method() {
1990
+ let mut context = GraphTest::new();
1991
+ context.index_uri("file:///foo.rb", {
1992
+ r"
1993
+ class Foo
1994
+ def setup
1995
+ def inner_method; end
1996
+ end
1997
+ end
1998
+ "
1999
+ });
2000
+ context.resolve();
2001
+
2002
+ assert_no_diagnostics!(&context);
2003
+
2004
+ // inner_method should be owned by Foo, not by setup
2005
+ assert_members_eq!(context, "Foo", ["inner_method()", "setup()"]);
2006
+ }
2007
+
2008
+ #[test]
2009
+ fn resolving_attr_accessors_inside_method() {
2010
+ let mut context = GraphTest::new();
2011
+ context.index_uri("file:///foo.rb", {
2012
+ r"
2013
+ class Foo
2014
+ def self.setup
2015
+ attr_reader :reader_attr
2016
+ attr_writer :writer_attr
2017
+ attr_accessor :accessor_attr
2018
+ end
2019
+ end
2020
+ "
2021
+ });
2022
+ context.resolve();
2023
+
2024
+ assert_no_diagnostics!(&context);
2025
+
2026
+ assert_members_eq!(context, "Foo::<Foo>", ["setup()"]);
2027
+
2028
+ // All attr_* should be owned by Foo, not by setup
2029
+ assert_members_eq!(context, "Foo", ["accessor_attr()", "reader_attr()", "writer_attr()"]);
2030
+ }
2031
+
2032
+ #[test]
2033
+ fn resolving_constant_alias_to_module() {
2034
+ let mut context = GraphTest::new();
2035
+ context.index_uri("file:///foo.rb", {
2036
+ r"
2037
+ module Foo
2038
+ CONST = 123
2039
+ end
2040
+
2041
+ ALIAS = Foo
2042
+ ALIAS::CONST
2043
+ "
2044
+ });
2045
+ context.resolve();
2046
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2047
+
2048
+ assert_constant_alias_target_eq!(context, "ALIAS", "Foo");
2049
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:6:8-6:13");
2050
+ }
2051
+
2052
+ #[test]
2053
+ fn resolving_constant_alias_to_nested_module() {
2054
+ let mut context = GraphTest::new();
2055
+ context.index_uri("file:///foo.rb", {
2056
+ r"
2057
+ module Foo
2058
+ module Bar
2059
+ CONST = 123
2060
+ end
2061
+ end
2062
+
2063
+ ALIAS = Foo::Bar
2064
+ ALIAS::CONST
2065
+ "
2066
+ });
2067
+ context.resolve();
2068
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2069
+
2070
+ assert_constant_alias_target_eq!(context, "ALIAS", "Foo::Bar");
2071
+ assert_constant_reference_to!(context, "Foo::Bar::CONST", "file:///foo.rb:8:8-8:13");
2072
+ }
2073
+
2074
+ #[test]
2075
+ fn resolving_constant_alias_inside_module() {
2076
+ let mut context = GraphTest::new();
2077
+ context.index_uri("file:///foo.rb", {
2078
+ r"
2079
+ module Foo
2080
+ CONST = 123
2081
+ end
2082
+
2083
+ module Bar
2084
+ MyFoo = Foo
2085
+ MyFoo::CONST
2086
+ end
2087
+ "
2088
+ });
2089
+ context.resolve();
2090
+
2091
+ assert_no_diagnostics!(&context);
2092
+
2093
+ assert_constant_alias_target_eq!(context, "Bar::MyFoo", "Foo");
2094
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:7:10-7:15");
2095
+ }
2096
+
2097
+ #[test]
2098
+ fn resolving_constant_alias_in_superclass() {
2099
+ let mut context = GraphTest::new();
2100
+ context.index_uri("file:///foo.rb", {
2101
+ r"
2102
+ class Foo
2103
+ CONST = 123
2104
+ end
2105
+
2106
+ class Bar < Foo
2107
+ end
2108
+
2109
+ ALIAS = Bar
2110
+ ALIAS::CONST
2111
+ "
2112
+ });
2113
+ context.resolve();
2114
+
2115
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2116
+
2117
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:9:8-9:13");
2118
+ }
2119
+
2120
+ #[test]
2121
+ fn resolving_chained_constant_aliases() {
2122
+ let mut context = GraphTest::new();
2123
+ context.index_uri("file:///foo.rb", {
2124
+ r"
2125
+ module Foo
2126
+ CONST = 123
2127
+ end
2128
+
2129
+ ALIAS1 = Foo
2130
+ ALIAS2 = ALIAS1
2131
+ ALIAS2::CONST
2132
+ "
2133
+ });
2134
+ context.resolve();
2135
+
2136
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2137
+
2138
+ assert_constant_alias_target_eq!(context, "ALIAS1", "Foo");
2139
+ assert_constant_alias_target_eq!(context, "ALIAS2", "ALIAS1");
2140
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:7:9-7:14");
2141
+ }
2142
+
2143
+ #[test]
2144
+ fn resolving_constant_alias_to_non_existent_target() {
2145
+ let mut context = GraphTest::new();
2146
+ context.index_uri("file:///foo.rb", {
2147
+ r"
2148
+ ALIAS_1 = NonExistent
2149
+ ALIAS_2 = ALIAS_1
2150
+ "
2151
+ });
2152
+ context.resolve();
2153
+
2154
+ assert_no_diagnostics!(&context);
2155
+
2156
+ assert_constant_alias_target_eq!(context, "ALIAS_2", "ALIAS_1");
2157
+ assert_no_constant_alias_target!(context, "ALIAS_1");
2158
+ }
2159
+
2160
+ #[test]
2161
+ fn resolving_constant_alias_to_value_in_constant_path() {
2162
+ let mut context = GraphTest::new();
2163
+ context.index_uri("file:///foo.rb", {
2164
+ r"
2165
+ VALUE = 1
2166
+ ALIAS = VALUE
2167
+ ALIAS::NOPE
2168
+ "
2169
+ });
2170
+ context.resolve();
2171
+
2172
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2173
+
2174
+ assert_constant_alias_target_eq!(context, "ALIAS", "VALUE");
2175
+
2176
+ // NOPE can't be created because ALIAS points to a value constant, not a namespace
2177
+ assert_declaration_does_not_exist!(context, "VALUE::NOPE");
2178
+ }
2179
+
2180
+ #[test]
2181
+ fn resolving_constant_alias_defined_before_target() {
2182
+ let mut context = GraphTest::new();
2183
+ context.index_uri("file:///foo.rb", {
2184
+ r"
2185
+ ALIAS = Foo
2186
+ module Foo
2187
+ CONST = 1
2188
+ end
2189
+ ALIAS::CONST
2190
+ "
2191
+ });
2192
+ context.resolve();
2193
+
2194
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2195
+
2196
+ assert_constant_alias_target_eq!(context, "ALIAS", "Foo");
2197
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///foo.rb:5:8-5:13");
2198
+ }
2199
+
2200
+ #[test]
2201
+ fn resolving_constant_alias_to_value() {
2202
+ let mut context = GraphTest::new();
2203
+ context.index_uri("file:///foo.rb", {
2204
+ r"
2205
+ class Foo
2206
+ CONST = 1
2207
+ end
2208
+ class Bar
2209
+ CONST = Foo::CONST
2210
+ end
2211
+ BAZ = Bar::CONST
2212
+ "
2213
+ });
2214
+ context.resolve();
2215
+
2216
+ assert_no_diagnostics!(&context);
2217
+
2218
+ assert_constant_alias_target_eq!(context, "BAZ", "Bar::CONST");
2219
+ assert_constant_alias_target_eq!(context, "Bar::CONST", "Foo::CONST");
2220
+ }
2221
+
2222
+ #[test]
2223
+ fn resolving_circular_constant_aliases() {
2224
+ let mut context = GraphTest::new();
2225
+ context.index_uri("file:///foo.rb", {
2226
+ r"
2227
+ A = B
2228
+ B = C
2229
+ C = A
2230
+ "
2231
+ });
2232
+ context.resolve();
2233
+
2234
+ assert_no_diagnostics!(&context);
2235
+
2236
+ assert_constant_alias_target_eq!(context, "A", "B");
2237
+ assert_constant_alias_target_eq!(context, "B", "C");
2238
+ assert_constant_alias_target_eq!(context, "C", "A");
2239
+ }
2240
+
2241
+ #[test]
2242
+ fn resolving_circular_constant_aliases_cross_namespace() {
2243
+ let mut context = GraphTest::new();
2244
+ context.index_uri("file:///foo.rb", {
2245
+ r"
2246
+ module A
2247
+ X = B::Y
2248
+ end
2249
+ module B
2250
+ Y = A::X
2251
+ end
2252
+
2253
+ A::X::SOMETHING = 1
2254
+ "
2255
+ });
2256
+ context.resolve();
2257
+
2258
+ assert_no_diagnostics!(&context);
2259
+
2260
+ assert_declaration_exists!(context, "A::X");
2261
+ assert_declaration_exists!(context, "B::Y");
2262
+
2263
+ // SOMETHING can't be created because the circular alias can't resolve to a namespace
2264
+ assert_declaration_does_not_exist!(context, "A::X::SOMETHING");
2265
+ }
2266
+
2267
+ #[test]
2268
+ fn resolving_constant_alias_ping_pong() {
2269
+ let mut context = GraphTest::new();
2270
+ context.index_uri("file:///foo.rb", {
2271
+ r"
2272
+ module Left
2273
+ module Deep
2274
+ VALUE = 'left'
2275
+ end
2276
+ end
2277
+
2278
+ module Right
2279
+ module Deep
2280
+ VALUE = 'right'
2281
+ end
2282
+ end
2283
+
2284
+ Left::RIGHT_REF = Right
2285
+ Right::LEFT_REF = Left
2286
+
2287
+ Left::RIGHT_REF::Deep::VALUE
2288
+ Left::RIGHT_REF::LEFT_REF::Deep::VALUE
2289
+ "
2290
+ });
2291
+ context.resolve();
2292
+
2293
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2294
+
2295
+ assert_constant_alias_target_eq!(context, "Left::RIGHT_REF", "Right");
2296
+ assert_constant_alias_target_eq!(context, "Right::LEFT_REF", "Left");
2297
+
2298
+ // Left::RIGHT_REF::Deep::VALUE
2299
+ assert_constant_reference_to!(context, "Right::Deep", "file:///foo.rb:16:18-16:22");
2300
+ assert_constant_reference_to!(context, "Right::Deep::VALUE", "file:///foo.rb:16:24-16:29");
2301
+ // Left::RIGHT_REF::LEFT_REF::Deep::VALUE
2302
+ assert_constant_reference_to!(context, "Left::Deep", "file:///foo.rb:17:28-17:32");
2303
+ assert_constant_reference_to!(context, "Left::Deep::VALUE", "file:///foo.rb:17:34-17:39");
2304
+ }
2305
+
2306
+ #[test]
2307
+ fn resolving_constant_alias_self_referential() {
2308
+ let mut context = GraphTest::new();
2309
+ context.index_uri("file:///foo.rb", {
2310
+ r"
2311
+ module M
2312
+ SELF_REF = M
2313
+
2314
+ class Thing
2315
+ CONST = 1
2316
+ end
2317
+ end
2318
+
2319
+ M::SELF_REF::Thing::CONST
2320
+ M::SELF_REF::SELF_REF::Thing::CONST
2321
+ M::SELF_REF::SELF_REF::SELF_REF::Thing::CONST
2322
+ "
2323
+ });
2324
+ context.resolve();
2325
+
2326
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2327
+
2328
+ assert_constant_alias_target_eq!(context, "M::SELF_REF", "M");
2329
+
2330
+ // All 3 paths resolve to M::Thing::CONST
2331
+ assert_declaration_references_count_eq!(context, "M::Thing::CONST", 3);
2332
+ assert_declaration_references_count_eq!(context, "M::Thing", 3);
2333
+
2334
+ // M::SELF_REF::Thing::CONST
2335
+ assert_constant_reference_to!(context, "M::Thing", "file:///foo.rb:9:14-9:19");
2336
+ assert_constant_reference_to!(context, "M::Thing::CONST", "file:///foo.rb:9:21-9:26");
2337
+ // M::SELF_REF::SELF_REF::Thing::CONST
2338
+ assert_constant_reference_to!(context, "M::Thing", "file:///foo.rb:10:24-10:29");
2339
+ assert_constant_reference_to!(context, "M::Thing::CONST", "file:///foo.rb:10:31-10:36");
2340
+ // M::SELF_REF::SELF_REF::SELF_REF::Thing::CONST
2341
+ assert_constant_reference_to!(context, "M::Thing", "file:///foo.rb:11:34-11:39");
2342
+ assert_constant_reference_to!(context, "M::Thing::CONST", "file:///foo.rb:11:41-11:46");
2343
+ }
2344
+
2345
+ #[test]
2346
+ fn resolving_class_through_constant_alias() {
2347
+ let mut context = GraphTest::new();
2348
+ context.index_uri("file:///foo.rb", {
2349
+ r"
2350
+ module Outer
2351
+ class Inner
2352
+ end
2353
+ end
2354
+
2355
+ ALIAS = Outer
2356
+ Outer::NESTED = Outer::Inner
2357
+
2358
+ class ALIAS::NESTED
2359
+ ADDED_CONST = 1
2360
+ end
2361
+
2362
+ Outer::Inner::ADDED_CONST
2363
+ "
2364
+ });
2365
+ context.resolve();
2366
+
2367
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2368
+
2369
+ assert_constant_alias_target_eq!(context, "ALIAS", "Outer");
2370
+ assert_constant_alias_target_eq!(context, "Outer::NESTED", "Outer::Inner");
2371
+
2372
+ // ADDED_CONST should be in Outer::Inner (the resolved target)
2373
+ assert_declaration_exists!(context, "Outer::Inner::ADDED_CONST");
2374
+
2375
+ assert_declaration_references_count_eq!(context, "Outer::Inner::ADDED_CONST", 1);
2376
+ }
2377
+
2378
+ #[test]
2379
+ fn resolving_class_definition_through_constant_alias() {
2380
+ let mut context = GraphTest::new();
2381
+ context.index_uri("file:///foo.rb", {
2382
+ r"
2383
+ module Outer
2384
+ CONST = 1
2385
+ end
2386
+
2387
+ ALIAS = Outer
2388
+
2389
+ class ALIAS::NewClass
2390
+ CLASS_CONST = 2
2391
+ end
2392
+
2393
+ Outer::NewClass::CLASS_CONST
2394
+ ALIAS::NewClass::CLASS_CONST
2395
+ "
2396
+ });
2397
+ context.resolve();
2398
+
2399
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2400
+
2401
+ assert_constant_alias_target_eq!(context, "ALIAS", "Outer");
2402
+
2403
+ // NewClass should be declared under Outer, not ALIAS
2404
+ assert_declaration_exists!(context, "Outer::NewClass");
2405
+ assert_declaration_exists!(context, "Outer::NewClass::CLASS_CONST");
2406
+
2407
+ // Outer::NewClass::CLASS_CONST
2408
+ assert_constant_reference_to!(context, "Outer::NewClass", "file:///foo.rb:11:8-11:16");
2409
+ assert_constant_reference_to!(context, "Outer::NewClass::CLASS_CONST", "file:///foo.rb:11:18-11:29");
2410
+ // ALIAS::NewClass::CLASS_CONST
2411
+ assert_constant_reference_to!(context, "Outer::NewClass", "file:///foo.rb:12:8-12:16");
2412
+ assert_constant_reference_to!(context, "Outer::NewClass::CLASS_CONST", "file:///foo.rb:12:18-12:29");
2413
+ }
2414
+
2415
+ #[test]
2416
+ fn resolving_constant_alias_with_multiple_definitions() {
2417
+ let mut context = GraphTest::new();
2418
+ context.index_uri("file:///a.rb", {
2419
+ r"
2420
+ module A; end
2421
+ FOO = A
2422
+ "
2423
+ });
2424
+ context.index_uri("file:///b.rb", {
2425
+ r"
2426
+ module B; end
2427
+ FOO = B
2428
+ "
2429
+ });
2430
+ context.resolve();
2431
+
2432
+ assert_no_diagnostics!(&context);
2433
+
2434
+ // FOO should have 2 definitions pointing to different targets
2435
+ assert_declaration_definitions_count_eq!(context, "FOO", 2);
2436
+
2437
+ assert_alias_targets_contain!(context, "FOO", "A", "B");
2438
+ }
2439
+
2440
+ #[test]
2441
+ fn resolving_constant_alias_with_multiple_targets() {
2442
+ let mut context = GraphTest::new();
2443
+ context.index_uri("file:///a.rb", {
2444
+ r"
2445
+ module A
2446
+ CONST_A = 1
2447
+ end
2448
+ FOO = A
2449
+ "
2450
+ });
2451
+ context.index_uri("file:///b.rb", {
2452
+ r"
2453
+ module B
2454
+ CONST_B = 2
2455
+ end
2456
+ FOO = B
2457
+ "
2458
+ });
2459
+ context.index_uri("file:///usage.rb", {
2460
+ r"
2461
+ FOO::CONST_A
2462
+ FOO::CONST_B
2463
+ "
2464
+ });
2465
+ context.resolve();
2466
+
2467
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2468
+
2469
+ assert_constant_reference_to!(context, "A::CONST_A", "file:///usage.rb:1:6-1:13");
2470
+ assert_constant_reference_to!(context, "B::CONST_B", "file:///usage.rb:2:6-2:13");
2471
+ }
2472
+
2473
+ #[test]
2474
+ fn resolving_constant_alias_multi_target_with_circular() {
2475
+ let mut context = GraphTest::new();
2476
+ context.index_uri("file:///a.rb", {
2477
+ r"
2478
+ module A
2479
+ CONST = 1
2480
+ end
2481
+ ALIAS = A
2482
+ "
2483
+ });
2484
+ context.index_uri("file:///b.rb", "ALIAS = ALIAS");
2485
+ context.index_uri("file:///usage.rb", "ALIAS::CONST");
2486
+ context.resolve();
2487
+
2488
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2489
+
2490
+ // ALIAS should have two targets: A and ALIAS (self-reference)
2491
+ assert_alias_targets_contain!(context, "ALIAS", "A", "ALIAS");
2492
+
2493
+ // ALIAS::CONST should still resolve to A::CONST through the valid path
2494
+ assert_constant_reference_to!(context, "A::CONST", "file:///usage.rb:1:8-1:13");
2495
+ }
2496
+
2497
+ #[test]
2498
+ fn resolving_constant_reference_through_chained_aliases() {
2499
+ let mut context = GraphTest::new();
2500
+ context.index_uri("file:///defs.rb", {
2501
+ r"
2502
+ module Foo
2503
+ CONST = 1
2504
+ end
2505
+ ALIAS1 = Foo
2506
+ ALIAS2 = ALIAS1
2507
+ "
2508
+ });
2509
+ context.index_uri("file:///usage.rb", "ALIAS2::CONST");
2510
+ context.resolve();
2511
+
2512
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2513
+
2514
+ assert_constant_alias_target_eq!(context, "ALIAS1", "Foo");
2515
+ assert_constant_alias_target_eq!(context, "ALIAS2", "ALIAS1");
2516
+
2517
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///usage.rb:1:9-1:14");
2518
+ }
2519
+
2520
+ #[test]
2521
+ fn resolving_constant_reference_through_top_level_alias_target() {
2522
+ let mut context = GraphTest::new();
2523
+ context.index_uri("file:///defs.rb", {
2524
+ r"
2525
+ module Foo
2526
+ CONST = 1
2527
+ end
2528
+ ALIAS = ::Foo
2529
+ "
2530
+ });
2531
+ context.index_uri("file:///usage.rb", "ALIAS::CONST");
2532
+ context.resolve();
2533
+
2534
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2535
+
2536
+ assert_constant_reference_to!(context, "Foo::CONST", "file:///usage.rb:1:8-1:13");
2537
+ }
2538
+
2539
+ // Regression test: defining singleton method on alias triggers get_or_create_singleton_class
2540
+ #[test]
2541
+ fn resolving_singleton_method_on_alias_does_not_panic() {
2542
+ let mut context = GraphTest::new();
2543
+ context.index_uri("file:///foo.rb", {
2544
+ r"
2545
+ class Foo; end
2546
+ ALIAS = Foo
2547
+ def ALIAS.singleton_method; end
2548
+ "
2549
+ });
2550
+ context.resolve();
2551
+
2552
+ assert_no_diagnostics!(&context);
2553
+ }
2554
+
2555
+ #[test]
2556
+ fn resolving_instance_variable_on_alias_does_not_panic() {
2557
+ let mut context = GraphTest::new();
2558
+ context.index_uri("file:///foo.rb", {
2559
+ r"
2560
+ class Foo; end
2561
+ ALIAS = Foo
2562
+ def ALIAS.singleton_method
2563
+ @ivar = 123
2564
+ end
2565
+ "
2566
+ });
2567
+ context.resolve();
2568
+ assert_no_diagnostics!(&context);
2569
+ }
2570
+
2571
+ #[test]
2572
+ fn multi_target_alias_constant_added_to_primary_owner() {
2573
+ let mut context = GraphTest::new();
2574
+ context.index_uri("file:///modules.rb", {
2575
+ r"
2576
+ module Foo; end
2577
+ module Bar; end
2578
+ "
2579
+ });
2580
+ context.index_uri("file:///alias1.rb", {
2581
+ r"
2582
+ ALIAS ||= Foo
2583
+ "
2584
+ });
2585
+ context.index_uri("file:///alias2.rb", {
2586
+ r"
2587
+ ALIAS ||= Bar
2588
+ "
2589
+ });
2590
+ context.index_uri("file:///const.rb", {
2591
+ r"
2592
+ ALIAS::CONST = 123
2593
+ "
2594
+ });
2595
+ context.resolve();
2596
+
2597
+ assert_no_diagnostics!(&context);
2598
+
2599
+ assert_members_eq!(context, "Foo", ["CONST"]);
2600
+ assert_no_members!(context, "Bar");
2601
+ }
2602
+
2603
+ #[test]
2604
+ fn distinct_declarations_with_conflicting_string_ids() {
2605
+ let mut context = GraphTest::new();
2606
+ context.index_uri("file:///foo.rb", {
2607
+ r"
2608
+ class Foo
2609
+ def Array(); end
2610
+ class Array; end
2611
+ end
2612
+ "
2613
+ });
2614
+ context.resolve();
2615
+
2616
+ assert_no_diagnostics!(&context);
2617
+
2618
+ // Both entries exist as unique members
2619
+ assert_members_eq!(context, "Foo", ["Array", "Array()"]);
2620
+
2621
+ // Both declarations exist with unique IDs
2622
+ assert_declaration_exists!(context, "Foo::Array");
2623
+ assert_declaration_exists!(context, "Foo#Array()");
2624
+ }
2625
+
2626
+ #[test]
2627
+ fn fully_qualified_names_are_unique() {
2628
+ let mut context = GraphTest::new();
2629
+ context.index_uri("file:///foo.rb", {
2630
+ r"
2631
+ module Foo
2632
+ class Bar
2633
+ CONST = 1
2634
+ @class_ivar = 2
2635
+
2636
+ attr_reader :baz
2637
+ attr_writer :qux
2638
+ attr_accessor :zip
2639
+
2640
+ def instance_m
2641
+ @@class_var = 3
2642
+ end
2643
+
2644
+ def self.singleton_m
2645
+ $global_var = 4
2646
+ end
2647
+
2648
+ def Foo.another_singleton_m; end
2649
+
2650
+ class << self
2651
+ OTHER_CONST = 5
2652
+ @other_class_ivar = 6
2653
+ @@other_class_var = 7
2654
+
2655
+ def other_instance_m
2656
+ @my_class_var = 8
2657
+ end
2658
+
2659
+ def self.other_singleton_m
2660
+ $other_global_var = 9
2661
+ end
2662
+ end
2663
+ end
2664
+ end
2665
+ "
2666
+ });
2667
+ context.resolve();
2668
+
2669
+ assert_no_diagnostics!(&context);
2670
+
2671
+ // In the same order of appearence
2672
+ assert_declaration_exists!(context, "Foo");
2673
+ assert_declaration_exists!(context, "Foo::Bar");
2674
+ assert_declaration_exists!(context, "Foo::Bar::CONST");
2675
+ assert_declaration_exists!(context, "Foo::Bar::<Bar>#@class_ivar");
2676
+ assert_declaration_exists!(context, "Foo::Bar#baz()");
2677
+ // TODO: needs the fix for attributes
2678
+ // assert_declaration_exists!(context, "Foo::Bar#qux=()");
2679
+ assert_declaration_exists!(context, "Foo::Bar#zip()");
2680
+ // TODO: needs the fix for attributes
2681
+ // assert_declaration_exists!(context, "Foo::Bar#zip=()");
2682
+ assert_declaration_exists!(context, "Foo::Bar#instance_m()");
2683
+ assert_declaration_exists!(context, "Foo::Bar#@@class_var");
2684
+ assert_declaration_exists!(context, "Foo::Bar::<Bar>#singleton_m()");
2685
+ assert_declaration_exists!(context, "$global_var");
2686
+ assert_declaration_exists!(context, "Foo::<Foo>#another_singleton_m()");
2687
+ assert_declaration_exists!(context, "Foo::Bar::<Bar>::OTHER_CONST");
2688
+ assert_declaration_exists!(context, "Foo::Bar::<Bar>::<<Bar>>#@other_class_ivar");
2689
+ assert_declaration_exists!(context, "Foo::Bar#@@other_class_var");
2690
+ assert_declaration_exists!(context, "Foo::Bar::<Bar>#other_instance_m()");
2691
+ assert_declaration_exists!(context, "Foo::Bar::<Bar>#@my_class_var");
2692
+ assert_declaration_exists!(context, "Foo::Bar::<Bar>::<<Bar>>#other_singleton_m()");
2693
+ assert_declaration_exists!(context, "$other_global_var");
2694
+ }
2695
+
2696
+ #[test]
2697
+ fn test_nested_same_names() {
2698
+ let mut context = GraphTest::new();
2699
+ context.index_uri("file:///foo.rb", {
2700
+ r"
2701
+ module Foo; end
2702
+
2703
+ module Bar
2704
+ Foo
2705
+
2706
+ module Foo
2707
+ FOO = 42
2708
+ end
2709
+ end
2710
+ "
2711
+ });
2712
+ context.resolve();
2713
+
2714
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
2715
+
2716
+ // FIXME: this is wrong, the reference is not to `Bar::Foo`, but to `Foo`
2717
+ assert_constant_reference_to!(context, "Bar::Foo", "file:///foo.rb:4:3-4:6");
2718
+
2719
+ assert_ancestors_eq!(context, "Foo", &["Foo"]);
2720
+ assert_ancestors_eq!(context, "Bar::Foo", &["Bar::Foo"]);
2721
+
2722
+ assert_no_members!(context, "Foo");
2723
+ assert_members_eq!(context, "Bar::Foo", ["FOO"]);
2724
+ }
2725
+
2726
+ #[test]
2727
+ fn resolves_constant_with_ancestors_partial() {
2728
+ // B has Ancestors::Partial because its prepend is defined in another file.
2729
+ // X must wait for B's ancestors to resolve, then resolve to A::X.
2730
+ let mut context = GraphTest::new();
2731
+ context.index_uri("file:///1.rb", {
2732
+ r"
2733
+ module A
2734
+ X = 1
2735
+ end
2736
+ class B
2737
+ X = 2
2738
+ end
2739
+ class C < B
2740
+ X
2741
+ end
2742
+ "
2743
+ });
2744
+ context.index_uri("file:///2.rb", {
2745
+ r"
2746
+ class B
2747
+ prepend A
2748
+ end
2749
+ "
2750
+ });
2751
+ context.resolve();
2752
+
2753
+ assert_no_diagnostics!(&context);
2754
+
2755
+ assert_ancestors_eq!(context, "C", ["C", "A", "B", "Object", "Kernel", "BasicObject"]);
2756
+ assert_constant_reference_to!(context, "A::X", "file:///1.rb:8:3-8:4");
2757
+ }
2758
+
2759
+ #[test]
2760
+ fn resolves_constant_with_ancestor_partial() {
2761
+ // C has an Ancestor::Partial entry because O::A is defined in another file.
2762
+ // X must wait for O::A to resolve, then resolve to O::A::X.
2763
+ let mut context = GraphTest::new();
2764
+ context.index_uri("file:///1.rb", {
2765
+ r"
2766
+ class B
2767
+ X = 2
2768
+ end
2769
+ class C
2770
+ include B
2771
+ include O::A
2772
+ X
2773
+ end
2774
+ "
2775
+ });
2776
+ context.index_uri("file:///2.rb", {
2777
+ r"
2778
+ module O
2779
+ module A
2780
+ X = 1
2781
+ end
2782
+ end
2783
+ "
2784
+ });
2785
+ context.resolve();
2786
+
2787
+ assert_no_diagnostics!(&context);
2788
+
2789
+ assert_ancestors_eq!(context, "C", ["C", "O::A", "B", "Object", "Kernel", "BasicObject"]);
2790
+ assert_constant_reference_to!(context, "O::A::X", "file:///1.rb:7:3-7:4");
2791
+ }
2792
+
2793
+ #[test]
2794
+ fn incomplete_method_calls_automatically_trigger_singleton_creation() {
2795
+ let mut context = GraphTest::new();
2796
+ context.index_uri("file:///foo.rb", {
2797
+ r"
2798
+ class Foo
2799
+ end
2800
+
2801
+ Foo.
2802
+ "
2803
+ });
2804
+ context.resolve();
2805
+ assert_no_diagnostics!(&context, &[Rule::ParseError]);
2806
+
2807
+ assert_declaration_references_count_eq!(context, "Foo::<Foo>", 1);
2808
+ assert_ancestors_eq!(
2809
+ context,
2810
+ "Foo::<Foo>",
2811
+ [
2812
+ "Foo::<Foo>",
2813
+ "Object::<Object>",
2814
+ "BasicObject::<BasicObject>",
2815
+ "Class",
2816
+ "Module",
2817
+ "Object",
2818
+ "Kernel",
2819
+ "BasicObject"
2820
+ ]
2821
+ );
2822
+ }
2823
+
2824
+ #[test]
2825
+ fn singleton_class_calls_create_nested_singletons() {
2826
+ let mut context = GraphTest::new();
2827
+ context.index_uri("file:///foo.rb", {
2828
+ r"
2829
+ class Foo
2830
+ end
2831
+
2832
+ Foo.singleton_class.singleton_class.to_s
2833
+ "
2834
+ });
2835
+ context.resolve();
2836
+ assert_no_diagnostics!(&context);
2837
+
2838
+ assert_declaration_references_count_eq!(context, "Foo::<Foo>::<<Foo>>::<<<Foo>>>", 1);
2839
+ assert_ancestors_eq!(
2840
+ context,
2841
+ "Foo::<Foo>::<<Foo>>::<<<Foo>>>",
2842
+ [
2843
+ "Foo::<Foo>::<<Foo>>::<<<Foo>>>",
2844
+ "Object::<Object>::<<Object>>::<<<Object>>>",
2845
+ "BasicObject::<BasicObject>::<<BasicObject>>::<<<BasicObject>>>",
2846
+ "Class::<Class>::<<Class>>",
2847
+ "Module::<Module>::<<Module>>",
2848
+ "Object::<Object>::<<Object>>",
2849
+ "BasicObject::<BasicObject>::<<BasicObject>>",
2850
+ "Class::<Class>",
2851
+ "Module::<Module>",
2852
+ "Object::<Object>",
2853
+ "BasicObject::<BasicObject>",
2854
+ "Class",
2855
+ "Module",
2856
+ "Object",
2857
+ "Kernel",
2858
+ "BasicObject"
2859
+ ]
2860
+ );
2861
+ }
2862
+
2863
+ #[test]
2864
+ fn singleton_class_on_a_scoped_constant() {
2865
+ let mut context = GraphTest::new();
2866
+ context.index_uri("file:///foo.rb", {
2867
+ r"
2868
+ module Foo
2869
+ class Bar
2870
+ end
2871
+ end
2872
+
2873
+ Foo::Bar.singleton_class.to_s
2874
+ "
2875
+ });
2876
+ context.resolve();
2877
+ assert_no_diagnostics!(&context);
2878
+
2879
+ assert_declaration_references_count_eq!(context, "Foo::Bar::<Bar>::<<Bar>>", 1);
2880
+ assert_ancestors_eq!(
2881
+ context,
2882
+ "Foo::Bar::<Bar>::<<Bar>>",
2883
+ [
2884
+ "Foo::Bar::<Bar>::<<Bar>>",
2885
+ "Object::<Object>::<<Object>>",
2886
+ "BasicObject::<BasicObject>::<<BasicObject>>",
2887
+ "Class::<Class>",
2888
+ "Module::<Module>",
2889
+ "Object::<Object>",
2890
+ "BasicObject::<BasicObject>",
2891
+ "Class",
2892
+ "Module",
2893
+ "Object",
2894
+ "Kernel",
2895
+ "BasicObject"
2896
+ ]
2897
+ );
2898
+ }
2899
+
2900
+ #[test]
2901
+ fn singleton_class_on_a_self_call() {
2902
+ let mut context = GraphTest::new();
2903
+ context.index_uri("file:///foo.rb", {
2904
+ r"
2905
+ class Foo
2906
+ class << self
2907
+ def bar
2908
+ singleton_class.baz
2909
+ end
2910
+ end
2911
+ end
2912
+ "
2913
+ });
2914
+ context.resolve();
2915
+ assert_no_diagnostics!(&context);
2916
+
2917
+ assert_declaration_references_count_eq!(context, "Foo::<Foo>::<<Foo>>", 1);
2918
+ assert_ancestors_eq!(
2919
+ context,
2920
+ "Foo::<Foo>::<<Foo>>",
2921
+ [
2922
+ "Foo::<Foo>::<<Foo>>",
2923
+ "Object::<Object>::<<Object>>",
2924
+ "BasicObject::<BasicObject>::<<BasicObject>>",
2925
+ "Class::<Class>",
2926
+ "Module::<Module>",
2927
+ "Object::<Object>",
2928
+ "BasicObject::<BasicObject>",
2929
+ "Class",
2930
+ "Module",
2931
+ "Object",
2932
+ "Kernel",
2933
+ "BasicObject"
2934
+ ]
2935
+ );
2936
+ }
2937
+
2938
+ #[test]
2939
+ fn method_call_on_undefined_constant() {
2940
+ let mut context = GraphTest::new();
2941
+ context.index_uri("file:///foo.rb", {
2942
+ r"
2943
+ Foo.bar
2944
+ "
2945
+ });
2946
+ context.resolve();
2947
+
2948
+ assert_no_diagnostics!(&context);
2949
+ assert_declaration_does_not_exist!(context, "Foo::<Foo>");
2950
+ }
2951
+
2952
+ #[test]
2953
+ fn rbs_module_and_class_declarations() {
2954
+ let mut context = GraphTest::new();
2955
+ context.index_rbs_uri("file:///test.rbs", {
2956
+ r"
2957
+ module Foo
2958
+ end
2959
+
2960
+ class Bar
2961
+ end
2962
+ "
2963
+ });
2964
+ context.resolve();
2965
+
2966
+ assert_no_diagnostics!(&context);
2967
+
2968
+ assert_declaration_exists!(context, "Foo");
2969
+ assert_declaration_exists!(context, "Bar");
2970
+ }
2971
+
2972
+ #[test]
2973
+ fn rbs_nested_declarations() {
2974
+ let mut context = GraphTest::new();
2975
+ context.index_rbs_uri("file:///test.rbs", {
2976
+ r"
2977
+ module Foo
2978
+ module Bar
2979
+ end
2980
+
2981
+ class Baz
2982
+ class Qux
2983
+ end
2984
+ end
2985
+ end
2986
+ "
2987
+ });
2988
+ context.resolve();
2989
+
2990
+ assert_no_diagnostics!(&context);
2991
+
2992
+ assert_owner_eq!(context, "Foo::Bar", "Foo");
2993
+ assert_owner_eq!(context, "Foo::Baz", "Foo");
2994
+ assert_owner_eq!(context, "Foo::Baz::Qux", "Foo::Baz");
2995
+ assert_members_eq!(context, "Foo", ["Bar", "Baz"]);
2996
+ assert_members_eq!(context, "Foo::Baz", ["Qux"]);
2997
+ }
2998
+
2999
+ #[test]
3000
+ fn rbs_qualified_module_name() {
3001
+ let mut context = GraphTest::new();
3002
+ context.index_rbs_uri("file:///parents.rbs", {
3003
+ r"
3004
+ module Foo
3005
+ module Bar
3006
+ end
3007
+ end
3008
+ "
3009
+ });
3010
+ context.index_rbs_uri("file:///test.rbs", {
3011
+ r"
3012
+ module Foo::Bar::Baz
3013
+ end
3014
+ "
3015
+ });
3016
+ context.resolve();
3017
+
3018
+ assert_no_diagnostics!(&context);
3019
+
3020
+ assert_declaration_exists!(context, "Foo::Bar::Baz");
3021
+ }
3022
+
3023
+ #[test]
3024
+ fn rbs_qualified_name_inside_nested_module() {
3025
+ let mut context = GraphTest::new();
3026
+ context.index_rbs_uri("file:///foo.rbs", {
3027
+ r"
3028
+ module Outer
3029
+ module Foo
3030
+ end
3031
+ end
3032
+ "
3033
+ });
3034
+ context.index_rbs_uri("file:///test.rbs", {
3035
+ r"
3036
+ module Outer
3037
+ module Foo::Bar
3038
+ end
3039
+ end
3040
+ "
3041
+ });
3042
+ context.resolve();
3043
+
3044
+ assert_no_diagnostics!(&context);
3045
+
3046
+ assert_owner_eq!(context, "Outer::Foo::Bar", "Outer::Foo");
3047
+ }
3048
+
3049
+ #[test]
3050
+ fn rbs_superclass_resolution() {
3051
+ let mut context = GraphTest::new();
3052
+ context.index_rbs_uri("file:///test.rbs", {
3053
+ r"
3054
+ class Foo
3055
+ end
3056
+
3057
+ class Bar < Foo
3058
+ end
3059
+
3060
+ module Baz
3061
+ class Base
3062
+ end
3063
+
3064
+ class Child < Base
3065
+ end
3066
+ end
3067
+ "
3068
+ });
3069
+ context.resolve();
3070
+
3071
+ assert_no_diagnostics!(&context);
3072
+
3073
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Foo", "Object", "Kernel", "BasicObject"]);
3074
+ assert_ancestors_eq!(
3075
+ context,
3076
+ "Baz::Child",
3077
+ ["Baz::Child", "Baz::Base", "Object", "Kernel", "BasicObject"]
3078
+ );
3079
+ }
3080
+
3081
+ #[test]
3082
+ fn rbs_constant_declarations() {
3083
+ let mut context = GraphTest::new();
3084
+ context.index_rbs_uri("file:///test.rbs", {
3085
+ r"
3086
+ FOO: String
3087
+
3088
+ class Bar
3089
+ BAZ: Integer
3090
+ end
3091
+
3092
+ Bar::QUX: ::String
3093
+ "
3094
+ });
3095
+ context.resolve();
3096
+
3097
+ assert_no_diagnostics!(&context);
3098
+
3099
+ assert_declaration_exists!(context, "FOO");
3100
+ assert_declaration_kind_eq!(context, "FOO", "Constant");
3101
+ assert_owner_eq!(context, "FOO", "Object");
3102
+
3103
+ assert_declaration_exists!(context, "Bar::BAZ");
3104
+ assert_declaration_kind_eq!(context, "Bar::BAZ", "Constant");
3105
+ assert_owner_eq!(context, "Bar::BAZ", "Bar");
3106
+
3107
+ assert_declaration_exists!(context, "Bar::QUX");
3108
+ assert_declaration_kind_eq!(context, "Bar::QUX", "Constant");
3109
+ assert_owner_eq!(context, "Bar::QUX", "Bar");
3110
+ }
3111
+
3112
+ #[test]
3113
+ fn rbs_global_declaration() {
3114
+ let mut context = GraphTest::new();
3115
+ context.index_rbs_uri("file:///test.rbs", "$foo: String");
3116
+ context.resolve();
3117
+
3118
+ assert_no_diagnostics!(&context);
3119
+
3120
+ assert_members_eq!(
3121
+ context,
3122
+ "Object",
3123
+ ["$foo", "BasicObject", "Class", "Kernel", "Module", "Object"]
3124
+ );
3125
+ }
3126
+
3127
+ #[test]
3128
+ fn rbs_mixin_resolution() {
3129
+ let mut context = GraphTest::new();
3130
+ context.index_rbs_uri("file:///test.rbs", {
3131
+ r"
3132
+ module Bar
3133
+ end
3134
+
3135
+ module Baz
3136
+ end
3137
+
3138
+ class Foo
3139
+ include Bar
3140
+ include Baz
3141
+ end
3142
+ "
3143
+ });
3144
+ context.resolve();
3145
+
3146
+ assert_no_diagnostics!(&context);
3147
+
3148
+ assert_ancestors_eq!(context, "Foo", ["Foo", "Baz", "Bar", "Object", "Kernel", "BasicObject"]);
3149
+ }
3150
+
3151
+ #[test]
3152
+ fn rbs_method_alias_resolution() {
3153
+ let mut context = GraphTest::new();
3154
+ context.index_uri("file:///foo.rb", {
3155
+ r"
3156
+ class Foo
3157
+ def bar; end
3158
+ def self.class_method; end
3159
+ end
3160
+
3161
+ module Baz
3162
+ def original; end
3163
+ end
3164
+ "
3165
+ });
3166
+ context.index_rbs_uri("file:///test.rbs", {
3167
+ r"
3168
+ class Foo
3169
+ alias qux bar
3170
+ alias self.class_alias self.class_method
3171
+ end
3172
+
3173
+ module Baz
3174
+ alias copy original
3175
+ end
3176
+ "
3177
+ });
3178
+ context.resolve();
3179
+
3180
+ assert_no_diagnostics!(&context);
3181
+
3182
+ assert_members_eq!(context, "Foo", ["bar()", "qux()"]);
3183
+ assert_members_eq!(context, "Foo::<Foo>", ["class_alias()", "class_method()"]);
3184
+ assert_members_eq!(context, "Baz", ["copy()", "original()"]);
3185
+ }
3186
+
3187
+ #[test]
3188
+ fn resolving_meta_programming_class_reopened() {
3189
+ // It's often not possible to provide first-class support to meta-programming constructs, but we have to prevent
3190
+ // the implementation from crashing in cases like these.
3191
+ //
3192
+ // Here we use some meta-programming method call to define a class and then re-open it using the `class`
3193
+ // keyword. The first definition of Bar is considered a constant because we don't know `dynamic_class` returns a
3194
+ // new class. The second definition is a class.
3195
+ //
3196
+ // We need to ensure that the associated Declaration for Bar is transformed into a class if any of its
3197
+ // definitions represent one, otherwise we have no place to store the includes and ancestors
3198
+ let mut context = GraphTest::new();
3199
+ context.index_uri("file:///foo.rb", {
3200
+ r"
3201
+ module Baz; end
3202
+
3203
+ Bar = dynamic_class do
3204
+ end
3205
+
3206
+ class Bar
3207
+ include Baz
3208
+ end
3209
+ "
3210
+ });
3211
+
3212
+ context.resolve();
3213
+ assert_no_diagnostics!(&context);
3214
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Baz", "Object", "Kernel", "BasicObject"]);
3215
+ }
3216
+
3217
+ #[test]
3218
+ fn resolving_accessing_meta_programming_class() {
3219
+ let mut context = GraphTest::new();
3220
+ context.index_uri("file:///foo.rb", {
3221
+ r"
3222
+ Foo = Protobuf.some_dynamic_class
3223
+ Foo::Bar = Protobuf.some_other_dynamic_class
3224
+ "
3225
+ });
3226
+
3227
+ context.resolve();
3228
+ assert_no_diagnostics!(&context);
3229
+ }
3230
+
3231
+ #[test]
3232
+ fn inheriting_from_dynamic_class() {
3233
+ let mut context = GraphTest::new();
3234
+ context.index_uri("file:///foo.rb", {
3235
+ r"
3236
+ Foo = some_dynamic_class
3237
+ class Bar < Foo
3238
+ end
3239
+ "
3240
+ });
3241
+
3242
+ context.resolve();
3243
+ assert_no_diagnostics!(&context);
3244
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Object", "Kernel", "BasicObject"]);
3245
+ }
3246
+
3247
+ #[test]
3248
+ fn including_dynamic_module() {
3249
+ let mut context = GraphTest::new();
3250
+ context.index_uri("file:///foo.rb", {
3251
+ r"
3252
+ Foo = some_dynamic_module
3253
+ class Bar
3254
+ include Foo
3255
+ end
3256
+ "
3257
+ });
3258
+
3259
+ context.resolve();
3260
+ assert_no_diagnostics!(&context);
3261
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Object", "Kernel", "BasicObject"]);
3262
+ }
3263
+
3264
+ #[test]
3265
+ fn prepending_dynamic_module() {
3266
+ let mut context = GraphTest::new();
3267
+ context.index_uri("file:///foo.rb", {
3268
+ r"
3269
+ Foo = some_dynamic_module
3270
+ class Bar
3271
+ prepend Foo
3272
+ end
3273
+ "
3274
+ });
3275
+
3276
+ context.resolve();
3277
+ assert_no_diagnostics!(&context);
3278
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Object", "Kernel", "BasicObject"]);
3279
+ }
3280
+
3281
+ #[test]
3282
+ fn extending_dynamic_module() {
3283
+ let mut context = GraphTest::new();
3284
+ context.index_uri("file:///foo.rb", {
3285
+ r"
3286
+ Foo = some_dynamic_module
3287
+ class Bar
3288
+ extend Foo
3289
+
3290
+ class << self
3291
+ end
3292
+ end
3293
+ "
3294
+ });
3295
+
3296
+ context.resolve();
3297
+ assert_no_diagnostics!(&context);
3298
+ assert_ancestors_eq!(
3299
+ context,
3300
+ "Bar::<Bar>",
3301
+ [
3302
+ "Bar::<Bar>",
3303
+ "Object::<Object>",
3304
+ "BasicObject::<BasicObject>",
3305
+ "Class",
3306
+ "Module",
3307
+ "Object",
3308
+ "Kernel",
3309
+ "BasicObject"
3310
+ ]
3311
+ );
3312
+ }
3313
+
3314
+ #[test]
3315
+ fn non_promotable_constant_not_promoted_to_class_with_members() {
3316
+ let mut context = GraphTest::new();
3317
+ context.index_uri("file:///foo.rb", {
3318
+ r"
3319
+ FOO = 42
3320
+ class FOO
3321
+ def bar; end
3322
+ end
3323
+ "
3324
+ });
3325
+
3326
+ context.resolve();
3327
+ assert_no_diagnostics!(&context);
3328
+ assert_declaration_exists!(context, "FOO");
3329
+ }
3330
+
3331
+ #[test]
3332
+ fn non_promotable_constant_not_promoted_to_module() {
3333
+ let mut context = GraphTest::new();
3334
+ context.index_uri("file:///foo.rb", {
3335
+ r#"
3336
+ FOO = "hello"
3337
+ module FOO
3338
+ end
3339
+ "#
3340
+ });
3341
+
3342
+ context.resolve();
3343
+ assert_no_diagnostics!(&context);
3344
+ assert_declaration_kind_eq!(context, "FOO", "Constant");
3345
+ }
3346
+
3347
+ #[test]
3348
+ fn promotable_constant_is_promoted_to_class() {
3349
+ let mut context = GraphTest::new();
3350
+ context.index_uri("file:///foo.rb", {
3351
+ r"
3352
+ module Baz; end
3353
+
3354
+ Bar = some_call
3355
+
3356
+ class Bar
3357
+ include Baz
3358
+ end
3359
+ "
3360
+ });
3361
+
3362
+ context.resolve();
3363
+ assert_no_diagnostics!(&context);
3364
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Baz", "Object", "Kernel", "BasicObject"]);
3365
+ }
3366
+
3367
+ #[test]
3368
+ fn mixed_promotable_and_non_promotable_blocks_promotion() {
3369
+ // If the same constant has both a promotable and non-promotable definition,
3370
+ // promotion should be blocked
3371
+ let mut context = GraphTest::new();
3372
+ context.index_uri("file:///a.rb", "Foo = some_call");
3373
+ context.index_uri("file:///b.rb", "Foo = 42");
3374
+ context.index_uri("file:///c.rb", "class Foo; end");
3375
+
3376
+ context.resolve();
3377
+ assert_no_diagnostics!(&context);
3378
+ assert_declaration_kind_eq!(context, "Foo", "Constant");
3379
+ }
3380
+
3381
+ #[test]
3382
+ fn promotable_constant_promoted_to_module() {
3383
+ let mut context = GraphTest::new();
3384
+ context.index_uri("file:///foo.rb", {
3385
+ r"
3386
+ module Baz; end
3387
+
3388
+ Bar = some_call
3389
+
3390
+ module Bar
3391
+ include Baz
3392
+ end
3393
+ "
3394
+ });
3395
+
3396
+ context.resolve();
3397
+ assert_no_diagnostics!(&context);
3398
+ assert_ancestors_eq!(context, "Bar", ["Bar", "Baz"]);
3399
+ }
3400
+
3401
+ #[test]
3402
+ fn class_first_then_constant_stays_namespace() {
3403
+ let mut context = GraphTest::new();
3404
+ context.index_uri("file:///foo.rb", {
3405
+ r"
3406
+ class Foo; end
3407
+ Foo = some_call
3408
+ "
3409
+ });
3410
+
3411
+ context.resolve();
3412
+ assert_no_diagnostics!(&context);
3413
+ assert_declaration_kind_eq!(context, "Foo", "Class");
3414
+ }
3415
+
3416
+ #[test]
3417
+ fn promotable_constant_path_write() {
3418
+ let mut context = GraphTest::new();
3419
+ context.index_uri("file:///foo.rb", {
3420
+ r"
3421
+ module A; end
3422
+ A::B = some_factory_call
3423
+ class A::B; end
3424
+ "
3425
+ });
3426
+
3427
+ context.resolve();
3428
+ assert_no_diagnostics!(&context);
3429
+ assert_declaration_exists!(context, "A::B");
3430
+ }
3431
+
3432
+ #[test]
3433
+ fn ancestor_operations_on_meta_programming_class() {
3434
+ let mut context = GraphTest::new();
3435
+ context.index_uri("file:///foo.rb", {
3436
+ r"
3437
+ module Foo; end
3438
+ module Bar; end
3439
+
3440
+ Qux = dynamic_class do
3441
+ include Foo
3442
+ prepend Bar
3443
+ end
3444
+ "
3445
+ });
3446
+
3447
+ context.resolve();
3448
+ assert_no_diagnostics!(&context);
3449
+ }
3450
+
3451
+ #[test]
3452
+ fn method_call_on_promotable_constant() {
3453
+ let mut context = GraphTest::new();
3454
+ context.index_uri("file:///foo.rb", {
3455
+ r"
3456
+ Qux = some_factory_call
3457
+ Qux.foo
3458
+ "
3459
+ });
3460
+
3461
+ context.resolve();
3462
+ assert_no_diagnostics!(&context);
3463
+ assert_declaration_exists!(context, "Qux::<Qux>");
3464
+ }
3465
+
3466
+ #[test]
3467
+ fn singleton_method_on_non_promotable_constant_does_not_crash() {
3468
+ let mut context = GraphTest::new();
3469
+ context.index_uri("file:///foo.rb", {
3470
+ r"
3471
+ FOO = 42
3472
+ FOO.bar
3473
+ "
3474
+ });
3475
+
3476
+ context.resolve();
3477
+ assert_no_diagnostics!(&context);
3478
+ assert_declaration_does_not_exist!(context, "FOO::<FOO>");
3479
+ }
3480
+
3481
+ #[test]
3482
+ fn def_self_on_promotable_constant() {
3483
+ let mut context = GraphTest::new();
3484
+ context.index_uri("file:///foo.rb", {
3485
+ r"
3486
+ Qux = some_factory_call
3487
+ def Qux.foo; end
3488
+ "
3489
+ });
3490
+
3491
+ context.resolve();
3492
+ assert_no_diagnostics!(&context);
3493
+ assert_declaration_exists!(context, "Qux::<Qux>");
3494
+ }
3495
+
3496
+ #[test]
3497
+ fn promoted_constant_has_correct_ancestors() {
3498
+ // When a promotable constant is auto-promoted via singleton class access, we conservatively
3499
+ // promote to a module (not a class) since we don't know what the call returns.
3500
+ // Modules don't inherit from Object.
3501
+ let mut context = GraphTest::new();
3502
+ context.index_uri("file:///foo.rb", {
3503
+ r"
3504
+ Foo = some_factory_call
3505
+ Foo.bar
3506
+ "
3507
+ });
3508
+
3509
+ context.resolve();
3510
+ assert_no_diagnostics!(&context);
3511
+ assert_ancestors_eq!(context, "Foo", ["Foo"]);
3512
+ }
3513
+
3514
+ #[test]
3515
+ fn method_call_on_namespace_alias() {
3516
+ // When a method call occurs in a constant alias to a namespace, the singleton class has to be created for the
3517
+ // target namespace and not for the alias
3518
+ let mut context = GraphTest::new();
3519
+ context.index_uri("file:///foo.rb", {
3520
+ r"
3521
+ class Foo
3522
+ def self.bar; end
3523
+ end
3524
+
3525
+ ALIAS = Foo
3526
+ ALIAS.bar
3527
+ "
3528
+ });
3529
+
3530
+ context.resolve();
3531
+ assert_no_diagnostics!(&context);
3532
+ assert_declaration_exists!(context, "Foo::<Foo>");
3533
+ assert_declaration_does_not_exist!(context, "ALIAS::<ALIAS>");
3534
+ }
3535
+
3536
+ #[test]
3537
+ fn method_def_on_namespace_alias() {
3538
+ let mut context = GraphTest::new();
3539
+ context.index_uri("file:///foo.rb", {
3540
+ r"
3541
+ class Foo
3542
+ end
3543
+
3544
+ ALIAS = Foo
3545
+
3546
+ def ALIAS.bar
3547
+ end
3548
+ "
3549
+ });
3550
+
3551
+ context.resolve();
3552
+ assert_no_diagnostics!(&context);
3553
+ assert_declaration_exists!(context, "Foo::<Foo>");
3554
+ assert_declaration_exists!(context, "Foo::<Foo>#bar()");
3555
+ assert_declaration_does_not_exist!(context, "ALIAS::<ALIAS>");
3556
+ }
3557
+
3558
+ #[test]
3559
+ fn meta_programming_class_with_members() {
3560
+ let mut context = GraphTest::new();
3561
+ context.index_uri("file:///foo.rb", {
3562
+ r"
3563
+ Foo = dynamic_class do
3564
+ def bar; end
3565
+ end
3566
+ "
3567
+ });
3568
+
3569
+ context.resolve();
3570
+ assert_no_diagnostics!(&context);
3571
+ assert_declaration_exists!(context, "Foo");
3572
+ assert_declaration_does_not_exist!(context, "Foo#bar()");
3573
+ }
3574
+
3575
+ #[test]
3576
+ fn re_opening_constant_alias_as_class() {
3577
+ let mut context = GraphTest::new();
3578
+ context.index_uri("file:///alias.rb", {
3579
+ r"
3580
+ module Foo
3581
+ class Bar; end
3582
+ end
3583
+
3584
+ Baz = Foo::Bar
3585
+ "
3586
+ });
3587
+ context.index_uri("file:///reopen.rb", {
3588
+ r"
3589
+ CONST = 1
3590
+
3591
+ class Baz
3592
+ class Other
3593
+ CONST
3594
+ end
3595
+ end
3596
+ "
3597
+ });
3598
+ context.resolve();
3599
+
3600
+ assert_no_diagnostics!(&context);
3601
+ assert_declaration_exists!(context, "Baz");
3602
+ assert_constant_reference_to!(context, "CONST", "file:///reopen.rb:5:5-5:10");
3603
+ }
3604
+
3605
+ #[test]
3606
+ fn constant_alias_reopened_as_class_with_nested_inheritance() {
3607
+ let mut context = GraphTest::new();
3608
+ context.index_uri("file:///a.rb", {
3609
+ r"
3610
+ module Foo
3611
+ Bar = ::Object
3612
+ end
3613
+
3614
+ module Foo
3615
+ class Bar
3616
+ class Baz < Something
3617
+ end
3618
+ end
3619
+ end
3620
+ "
3621
+ });
3622
+ context.resolve();
3623
+
3624
+ assert_declaration_exists!(context, "Foo::Bar");
3625
+ }
3626
+
3627
+ #[test]
3628
+ fn superclass_through_alias() {
3629
+ let mut context = GraphTest::new();
3630
+ context.index_uri("file:///a.rb", {
3631
+ r"
3632
+ class Base; end
3633
+ AliasedBase = Base
3634
+ class Foo < AliasedBase; end
3635
+ "
3636
+ });
3637
+ context.resolve();
3638
+ assert_no_diagnostics!(&context);
3639
+ assert_ancestors_eq!(context, "Foo", ["Foo", "Base", "Object", "Kernel", "BasicObject"]);
3640
+ }
3641
+
3642
+ #[test]
3643
+ fn mixin_through_alias() {
3644
+ let mut context = GraphTest::new();
3645
+ context.index_uri("file:///a.rb", {
3646
+ r"
3647
+ module M; end
3648
+ AliasM = M
3649
+ class Foo
3650
+ include AliasM
3651
+ end
3652
+ "
3653
+ });
3654
+ context.resolve();
3655
+ assert_no_diagnostics!(&context);
3656
+ assert_ancestors_eq!(context, "Foo", ["Foo", "M", "Object", "Kernel", "BasicObject"]);
3657
+ }
3658
+
3659
+ #[test]
3660
+ fn self_method_inside_non_promotable_constant() {
3661
+ let mut context = GraphTest::new();
3662
+ context.index_uri("file:///a.rb", {
3663
+ r"
3664
+ CONST = 1
3665
+ module CONST
3666
+ def self.bar
3667
+ end
3668
+ end
3669
+ "
3670
+ });
3671
+ // Should not panic when a `def self.` method is inside a constant that can't be promoted to a namespace (e.g.,
3672
+ // `CONST = 1` is non-promotable).
3673
+ context.resolve();
3674
+ assert_declaration_exists!(context, "CONST");
3675
+ }
3676
+
3677
+ #[test]
3678
+ fn including_unresolved_alias() {
3679
+ let mut context = GraphTest::new();
3680
+ context.index_uri("file:///a.rb", {
3681
+ r"
3682
+ module Foo; end
3683
+ Foo::Bar = Bar
3684
+
3685
+ module Baz
3686
+ include Foo::Bar
3687
+ end
3688
+ "
3689
+ });
3690
+
3691
+ context.resolve();
3692
+ assert_ancestors_eq!(context, "Baz", ["Baz"]);
3693
+ }
3694
+
3695
+ #[test]
3696
+ fn prepending_unresolved_alias() {
3697
+ let mut context = GraphTest::new();
3698
+ context.index_uri("file:///a.rb", {
3699
+ r"
3700
+ module Foo; end
3701
+ Foo::Bar = Bar
3702
+
3703
+ module Baz
3704
+ prepend Foo::Bar
3705
+ end
3706
+ "
3707
+ });
3708
+
3709
+ context.resolve();
3710
+ assert_ancestors_eq!(context, "Baz", ["Baz"]);
3711
+ }
3712
+
3713
+ #[test]
3714
+ fn inheriting_unresolved_alias() {
3715
+ let mut context = GraphTest::new();
3716
+ context.index_uri("file:///a.rb", {
3717
+ r"
3718
+ module Foo; end
3719
+ Foo::Bar = Bar
3720
+
3721
+ class Baz < Foo::Bar
3722
+ end
3723
+ "
3724
+ });
3725
+
3726
+ context.resolve();
3727
+ assert_ancestors_eq!(context, "Baz", ["Baz", "Object", "Kernel", "BasicObject"]);
3728
+ }
3729
+
3730
+ #[test]
3731
+ fn re_opening_unresolved_alias() {
3732
+ let mut context = GraphTest::new();
3733
+ context.index_uri("file:///a.rb", {
3734
+ r"
3735
+ module Foo; end
3736
+ Foo::Bar = Bar
3737
+
3738
+ module Foo::Bar
3739
+ CONST = 123
3740
+ @class_ivar = 123
3741
+ @@class_var = 789
3742
+
3743
+ attr_reader :some_attr
3744
+
3745
+ def self.class_method; end
3746
+
3747
+ def initialize
3748
+ @instance_ivar = 456
3749
+ end
3750
+ end
3751
+ "
3752
+ });
3753
+
3754
+ context.resolve();
3755
+ assert_declaration_does_not_exist!(context, "Foo::Bar::CONST");
3756
+ assert_declaration_does_not_exist!(context, "Foo::Bar::<Bar>#@class_ivar");
3757
+ assert_declaration_does_not_exist!(context, "Foo::Bar#@instance_ivar");
3758
+ assert_declaration_does_not_exist!(context, "Foo::Bar#@@class_var");
3759
+ assert_declaration_does_not_exist!(context, "Foo::Bar#some_attr()");
3760
+ assert_declaration_does_not_exist!(context, "Foo::Bar::<Bar>#class_method()");
3761
+ assert_declaration_does_not_exist!(context, "Foo::Bar#initialize()");
3762
+ }
3763
+
3764
+ #[test]
3765
+ fn re_opening_namespace_alias() {
3766
+ let mut context = GraphTest::new();
3767
+ context.index_uri("file:///a.rb", {
3768
+ r"
3769
+ module Foo; end
3770
+ ALIAS = Foo
3771
+
3772
+ module ALIAS
3773
+ CONST = 123
3774
+ @class_ivar = 123
3775
+ @@class_var = 789
3776
+
3777
+ attr_reader :some_attr
3778
+
3779
+ def self.class_method; end
3780
+
3781
+ def initialize
3782
+ @instance_ivar = 456
3783
+ end
3784
+
3785
+ def bar; end
3786
+ alias new_bar bar
3787
+ end
3788
+ "
3789
+ });
3790
+
3791
+ context.resolve();
3792
+ assert_declaration_exists!(context, "Foo::CONST");
3793
+ assert_declaration_exists!(context, "Foo::<Foo>#@class_ivar");
3794
+ assert_declaration_exists!(context, "Foo#@instance_ivar");
3795
+ assert_declaration_exists!(context, "Foo#@@class_var");
3796
+ assert_declaration_exists!(context, "Foo#some_attr()");
3797
+ assert_declaration_exists!(context, "Foo::<Foo>#class_method()");
3798
+ assert_declaration_exists!(context, "Foo#initialize()");
3799
+ assert_declaration_exists!(context, "Foo#new_bar()");
3800
+ }
3801
+
3802
+ #[test]
3803
+ fn defining_constant_in_promotable_constant() {
3804
+ let mut context = GraphTest::new();
3805
+ context.index_uri("file:///a.rb", {
3806
+ r"
3807
+ Foo = dynamic
3808
+ Foo::Bar = dynamic
3809
+ Foo::Bar::Baz = 123
3810
+ "
3811
+ });
3812
+
3813
+ context.resolve();
3814
+ assert_declaration_kind_eq!(context, "Foo", "Module");
3815
+ assert_declaration_kind_eq!(context, "Foo::Bar", "Module");
3816
+ assert_declaration_kind_eq!(context, "Foo::Bar::Baz", "Constant");
3817
+ }
3818
+
3819
+ #[test]
3820
+ fn singleton_class_block_for_promotable_constant() {
3821
+ let mut context = GraphTest::new();
3822
+ context.index_uri("file:///a.rb", {
3823
+ r"
3824
+ Foo = dynamic
3825
+
3826
+ class << Foo
3827
+ def bar; end
3828
+ end
3829
+ "
3830
+ });
3831
+
3832
+ context.resolve();
3833
+ assert_declaration_kind_eq!(context, "Foo", "Module");
3834
+ assert_declaration_exists!(context, "Foo::<Foo>#bar()");
3835
+ }
3836
+
3837
+ #[test]
3838
+ fn singleton_class_block_for_non_promotable_constant() {
3839
+ let mut context = GraphTest::new();
3840
+ context.index_uri("file:///a.rb", {
3841
+ r"
3842
+ Foo = 1
3843
+
3844
+ class << Foo
3845
+ def bar; end
3846
+ end
3847
+ "
3848
+ });
3849
+
3850
+ context.resolve();
3851
+ assert_declaration_kind_eq!(context, "Foo", "Constant");
3852
+ assert_declaration_does_not_exist!(context, "Foo::<Foo>");
3853
+ assert_declaration_does_not_exist!(context, "Foo::<Foo>#bar()");
3854
+ }
3855
+
3856
+ #[test]
3857
+ fn qualified_name_inside_nesting_resolves_to_top_level() {
3858
+ let mut context = GraphTest::new();
3859
+ context.index_uri("file:///foo.rb", {
3860
+ r"
3861
+ module Foo
3862
+ class Bar::Baz
3863
+ def qux; end
3864
+ end
3865
+ end
3866
+
3867
+ module Bar
3868
+ end
3869
+ "
3870
+ });
3871
+ context.resolve();
3872
+
3873
+ assert_no_diagnostics!(&context);
3874
+ assert_declaration_kind_eq!(context, "Bar", "Module");
3875
+ assert_members_eq!(context, "Bar", vec!["Baz"]);
3876
+ assert_declaration_exists!(context, "Bar::Baz");
3877
+ assert_members_eq!(context, "Bar::Baz", vec!["qux()"]);
3878
+ assert_declaration_does_not_exist!(context, "Foo::Bar");
3879
+ }
3880
+
3881
+ #[test]
3882
+ fn singleton_ancestor_chain_cascades_through_intermediate_class() {
3883
+ let mut context = GraphTest::new();
3884
+ context.index_uri(
3885
+ "file:///foo.rb",
3886
+ r"
3887
+ class Foo
3888
+ def self.foo; end
3889
+ end
3890
+ class Bar < Foo
3891
+ end
3892
+ class Baz < Bar
3893
+ def self.baz; end
3894
+ end
3895
+ ",
3896
+ );
3897
+ context.resolve();
3898
+
3899
+ assert_ancestors_eq!(
3900
+ context,
3901
+ "Baz::<Baz>",
3902
+ [
3903
+ "Baz::<Baz>",
3904
+ "Bar::<Bar>",
3905
+ "Foo::<Foo>",
3906
+ "Object::<Object>",
3907
+ "BasicObject::<BasicObject>",
3908
+ "Class",
3909
+ "Module",
3910
+ "Object",
3911
+ "Kernel",
3912
+ "BasicObject"
3913
+ ]
3914
+ );
3915
+ }
3916
+
3917
+ #[test]
3918
+ fn ancestors_with_missing_core() {
3919
+ let mut context = GraphTest::new();
3920
+ context.index_uri(
3921
+ "file:///foo.rb",
3922
+ "
3923
+ module Bar; end
3924
+
3925
+ class Foo
3926
+ include Bar
3927
+ end
3928
+ ",
3929
+ );
3930
+ context.resolve();
3931
+
3932
+ assert_ancestors_eq!(context, "Foo", ["Foo", "Bar", "Object", "Kernel", "BasicObject"]);
3933
+ assert_descendants!(context, "Bar", ["Foo"]);
3934
+ }
3935
+
3936
+ #[test]
3937
+ fn ancestor_patches_to_object_are_correctly_processed() {
3938
+ let mut context = GraphTest::new();
3939
+ context.index_uri(
3940
+ "file:///foo.rb",
3941
+ "
3942
+ module Foo; end
3943
+
3944
+ module Kernel
3945
+ include Foo
3946
+ end
3947
+ ",
3948
+ );
3949
+ context.resolve();
3950
+
3951
+ assert_ancestors_eq!(context, "Object", ["Object", "Kernel", "Foo", "BasicObject"]);
3952
+ }
3953
+
3954
+ #[test]
3955
+ fn basic_object_ancestors() {
3956
+ let mut context = GraphTest::new();
3957
+ context.index_uri(
3958
+ "file:///foo.rb",
3959
+ "
3960
+ class Foo < BasicObject
3961
+ end
3962
+ ",
3963
+ );
3964
+ context.resolve();
3965
+
3966
+ assert_ancestors_eq!(context, "Foo", ["Foo", "BasicObject"]);
3967
+ }
3968
+
3969
+ #[test]
3970
+ fn basic_object_ancestors_including_kernel() {
3971
+ let mut context = GraphTest::new();
3972
+ context.index_uri(
3973
+ "file:///foo.rb",
3974
+ "
3975
+ class Foo < BasicObject
3976
+ include Kernel
3977
+ end
3978
+ ",
3979
+ );
3980
+ context.resolve();
3981
+
3982
+ assert_ancestors_eq!(context, "Foo", ["Foo", "Kernel", "BasicObject"]);
3983
+ }
3984
+
3985
+ #[test]
3986
+ fn constant_resolution_inside_basic_object() {
3987
+ let mut context = GraphTest::new();
3988
+ context.index_uri("file:///foo.rb", {
3989
+ r"
3990
+ class String; end
3991
+
3992
+ class Foo < BasicObject
3993
+ String
3994
+ end
3995
+ "
3996
+ });
3997
+ context.resolve();
3998
+
3999
+ assert_no_diagnostics!(&context);
4000
+ assert_constant_reference_unresolved!(context, "String");
4001
+ }
4002
+
4003
+ #[test]
4004
+ fn top_level_scope_searches_object_ancestors() {
4005
+ let mut context = GraphTest::new();
4006
+ context.index_uri("file:///foo.rb", {
4007
+ r"
4008
+ module Kernel
4009
+ FOUND_ME = true
4010
+ end
4011
+
4012
+ class Object
4013
+ include Kernel
4014
+ end
4015
+
4016
+ class Foo
4017
+ ::FOUND_ME
4018
+ end
4019
+ "
4020
+ });
4021
+ context.resolve();
4022
+
4023
+ assert_no_diagnostics!(&context);
4024
+ assert_constant_reference_to!(context, "Kernel::FOUND_ME", "file:///foo.rb:10:5-10:13");
4025
+ }
4026
+
4027
+ #[test]
4028
+ fn top_level_script_constant_resolution_searches_object_ancestors() {
4029
+ let mut context = GraphTest::new();
4030
+ context.index_uri("file:///foo.rb", {
4031
+ r"
4032
+ module Kernel
4033
+ FOUND_ME = true
4034
+ end
4035
+
4036
+ class Object
4037
+ include Kernel
4038
+ end
4039
+
4040
+ FOUND_ME
4041
+ "
4042
+ });
4043
+ context.resolve();
4044
+
4045
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
4046
+ assert_constant_reference_to!(context, "Kernel::FOUND_ME", "file:///foo.rb:9:1-9:9");
4047
+ }
4048
+
4049
+ #[test]
4050
+ fn module_own_ancestors_take_priority_over_object_fallback() {
4051
+ let mut context = GraphTest::new();
4052
+ context.index_uri("file:///foo.rb", {
4053
+ r"
4054
+ module MyConstants
4055
+ CONST = 'mine'
4056
+ end
4057
+
4058
+ module Kernel
4059
+ CONST = 'kernel'
4060
+ end
4061
+
4062
+ class Object
4063
+ include Kernel
4064
+ end
4065
+
4066
+ module Foo
4067
+ include MyConstants
4068
+ CONST
4069
+ end
4070
+ "
4071
+ });
4072
+ context.resolve();
4073
+
4074
+ assert_no_diagnostics!(&context);
4075
+ assert_constant_reference_to!(context, "MyConstants::CONST", "file:///foo.rb:15:3-15:8");
4076
+ }
4077
+
4078
+ #[test]
4079
+ fn object_inherited_constant_inside_module() {
4080
+ let mut context = GraphTest::new();
4081
+ context.index_uri("file:///foo.rb", {
4082
+ r"
4083
+ module Kernel
4084
+ FOUND_ME = true
4085
+ end
4086
+
4087
+ class Object
4088
+ include Kernel
4089
+ end
4090
+
4091
+ module Foo
4092
+ # This is valid because of Object inheritance
4093
+ FOUND_ME
4094
+ end
4095
+
4096
+ Foo::FOUND_ME # this is not
4097
+ "
4098
+ });
4099
+ context.resolve();
4100
+
4101
+ assert_no_diagnostics!(&context, &[Rule::ParseWarning]);
4102
+ assert_constant_reference_to!(context, "Kernel::FOUND_ME", "file:///foo.rb:11:3-11:11");
4103
+ assert_constant_reference_unresolved!(context, "FOUND_ME", "file:///foo.rb:14:6-14:14");
4104
+ }
4105
+
4106
+ #[test]
4107
+ fn extend_creates_singleton_class() {
4108
+ let mut context = GraphTest::new();
4109
+ context.index_uri(
4110
+ "file:///foo.rb",
4111
+ "
4112
+ module Bar; end
4113
+
4114
+ class Foo
4115
+ extend Bar
4116
+ end
4117
+ ",
4118
+ );
4119
+ context.resolve();
4120
+
4121
+ assert_declaration_exists!(context, "Foo::<Foo>");
4122
+ assert_ancestors_eq!(
4123
+ context,
4124
+ "Foo::<Foo>",
4125
+ [
4126
+ "Foo::<Foo>",
4127
+ "Bar",
4128
+ "Object::<Object>",
4129
+ "BasicObject::<BasicObject>",
4130
+ "Class",
4131
+ "Module",
4132
+ "Object",
4133
+ "Kernel",
4134
+ "BasicObject"
4135
+ ]
4136
+ );
4137
+ }
4138
+
4139
+ #[test]
4140
+ fn extend_creates_singleton_class_with_existing_singleton_method() {
4141
+ let mut context = GraphTest::new();
4142
+ context.index_uri(
4143
+ "file:///foo.rb",
4144
+ "
4145
+ module Bar; end
4146
+
4147
+ class Foo
4148
+ extend Bar
4149
+
4150
+ def self.baz; end
4151
+ end
4152
+ ",
4153
+ );
4154
+ context.resolve();
4155
+
4156
+ assert_declaration_exists!(context, "Foo::<Foo>");
4157
+ assert_ancestors_eq!(
4158
+ context,
4159
+ "Foo::<Foo>",
4160
+ [
4161
+ "Foo::<Foo>",
4162
+ "Bar",
4163
+ "Object::<Object>",
4164
+ "BasicObject::<BasicObject>",
4165
+ "Class",
4166
+ "Module",
4167
+ "Object",
4168
+ "Kernel",
4169
+ "BasicObject"
4170
+ ]
4171
+ );
4172
+ }
4173
+
4174
+ #[test]
4175
+ fn extend_creates_singleton_class_on_module() {
4176
+ let mut context = GraphTest::new();
4177
+ context.index_uri(
4178
+ "file:///foo.rb",
4179
+ "
4180
+ module Bar; end
4181
+
4182
+ module Foo
4183
+ extend Bar
4184
+ end
4185
+ ",
4186
+ );
4187
+ context.resolve();
4188
+
4189
+ assert_declaration_exists!(context, "Foo::<Foo>");
4190
+ assert_ancestors_eq!(
4191
+ context,
4192
+ "Foo::<Foo>",
4193
+ ["Foo::<Foo>", "Bar", "Module", "Object", "Kernel", "BasicObject"]
4194
+ );
4195
+ }
4196
+
4197
+ #[test]
4198
+ fn ancestors_for_unresolved_parent_class() {
4199
+ let mut context = GraphTest::new();
4200
+ context.index_uri(
4201
+ "file:///foo.rb",
4202
+ "
4203
+ class Foo < Bar; end
4204
+ ",
4205
+ );
4206
+ context.resolve();
4207
+
4208
+ assert_ancestors_eq!(
4209
+ context,
4210
+ "Foo",
4211
+ ["Foo", Partial("Bar"), "Object", "Kernel", "BasicObject"]
4212
+ );
4213
+ assert!(matches!(
4214
+ context
4215
+ .graph()
4216
+ .declarations()
4217
+ .get(&DeclarationId::from("Foo"))
4218
+ .unwrap()
4219
+ .as_namespace()
4220
+ .unwrap()
4221
+ .ancestors(),
4222
+ Ancestors::Partial(_)
4223
+ ));
4224
+ }
4225
+
4226
+ #[test]
4227
+ fn singleton_class_created_in_remaining_definitions_has_linearized_ancestors() {
4228
+ let mut context = GraphTest::new();
4229
+ context.index_uri(
4230
+ "file:///foo.rb",
4231
+ r"
4232
+ class Foo
4233
+ @var = 1
4234
+ end
4235
+ ",
4236
+ );
4237
+ context.resolve();
4238
+
4239
+ assert_no_diagnostics!(&context);
4240
+ assert_ancestors_eq!(
4241
+ context,
4242
+ "Foo::<Foo>",
4243
+ [
4244
+ "Foo::<Foo>",
4245
+ "Object::<Object>",
4246
+ "BasicObject::<BasicObject>",
4247
+ "Class",
4248
+ "Module",
4249
+ "Object",
4250
+ "Kernel",
4251
+ "BasicObject"
4252
+ ]
4253
+ );
4254
+ }
4255
+
4256
+ mod todo_tests {
4257
+ use super::*;
4258
+
4259
+ #[test]
4260
+ fn resolution_does_not_loop_infinitely_on_non_existing_constants() {
4261
+ let mut context = GraphTest::new();
4262
+ context.index_uri("file:///foo.rb", {
4263
+ r"
4264
+ class Foo::Bar
4265
+ class Baz
4266
+ end
4267
+ end
4268
+ "
4269
+ });
4270
+ context.resolve();
4271
+
4272
+ assert_no_diagnostics!(&context);
4273
+
4274
+ assert_declaration_kind_eq!(context, "Foo", "<TODO>");
4275
+
4276
+ assert_members_eq!(
4277
+ context,
4278
+ "Object",
4279
+ vec!["BasicObject", "Class", "Foo", "Kernel", "Module", "Object"]
4280
+ );
4281
+ assert_members_eq!(context, "Foo", vec!["Bar"]);
4282
+ assert_members_eq!(context, "Foo::Bar", vec!["Baz"]);
4283
+ assert_no_members!(context, "Foo::Bar::Baz");
4284
+ }
4285
+
4286
+ #[test]
4287
+ fn resolve_missing_declaration_to_todo() {
4288
+ let mut context = GraphTest::new();
4289
+ context.index_uri("file:///foo.rb", {
4290
+ r"
4291
+ class Foo::Bar
4292
+ include Foo::Baz
4293
+
4294
+ def bar; end
4295
+ end
4296
+
4297
+ module Foo::Baz
4298
+ def baz; end
4299
+ end
4300
+ "
4301
+ });
4302
+ context.resolve();
4303
+
4304
+ assert_no_diagnostics!(&context);
4305
+
4306
+ assert_declaration_kind_eq!(context, "Foo", "<TODO>");
4307
+
4308
+ assert_members_eq!(
4309
+ context,
4310
+ "Object",
4311
+ vec!["BasicObject", "Class", "Foo", "Kernel", "Module", "Object"]
4312
+ );
4313
+ assert_members_eq!(context, "Foo", vec!["Bar", "Baz"]);
4314
+ assert_members_eq!(context, "Foo::Bar", vec!["bar()"]);
4315
+ assert_members_eq!(context, "Foo::Baz", vec!["baz()"]);
4316
+ }
4317
+
4318
+ #[test]
4319
+ fn qualified_name_inside_nesting_resolves_when_discovered_incrementally() {
4320
+ let mut context = GraphTest::new();
4321
+ context.index_uri("file:///baz.rb", {
4322
+ r"
4323
+ module Foo
4324
+ class Bar::Baz
4325
+ def qux; end
4326
+ end
4327
+ end
4328
+ "
4329
+ });
4330
+ context.resolve();
4331
+
4332
+ // Bar is unknown — a Todo is created at the top level, not "Foo::Bar"
4333
+ assert_declaration_kind_eq!(context, "Bar", "<TODO>");
4334
+ assert_declaration_does_not_exist!(context, "Foo::Bar");
4335
+
4336
+ context.index_uri("file:///bar.rb", {
4337
+ r"
4338
+ module Bar
4339
+ end
4340
+ "
4341
+ });
4342
+ context.resolve();
4343
+
4344
+ // After discovering top-level Bar, the Todo should be promoted and Baz re-homed.
4345
+ assert_no_diagnostics!(&context);
4346
+ assert_declaration_kind_eq!(context, "Bar", "Module");
4347
+ assert_members_eq!(context, "Bar", vec!["Baz"]);
4348
+ assert_members_eq!(context, "Bar::Baz", vec!["qux()"]);
4349
+ assert_declaration_does_not_exist!(context, "Foo::Bar");
4350
+ }
4351
+
4352
+ #[test]
4353
+ fn promoted_to_real_namespace() {
4354
+ let mut context = GraphTest::new();
4355
+ context.index_uri("file:///foo.rb", {
4356
+ r"
4357
+ class Foo::Bar
4358
+ def bar; end
4359
+ end
4360
+
4361
+ class Foo
4362
+ def foo; end
4363
+ end
4364
+ "
4365
+ });
4366
+ context.resolve();
4367
+
4368
+ assert_no_diagnostics!(&context);
4369
+
4370
+ // Foo was initially created as a Todo (from class Foo::Bar), then promoted to Class
4371
+ assert_declaration_kind_eq!(context, "Foo", "Class");
4372
+
4373
+ assert_members_eq!(
4374
+ context,
4375
+ "Object",
4376
+ vec!["BasicObject", "Class", "Foo", "Kernel", "Module", "Object"]
4377
+ );
4378
+ assert_members_eq!(context, "Foo", vec!["Bar", "foo()"]);
4379
+ assert_members_eq!(context, "Foo::Bar", vec!["bar()"]);
4380
+ }
4381
+
4382
+ #[test]
4383
+ fn promoted_to_real_namespace_incrementally() {
4384
+ let mut context = GraphTest::new();
4385
+ context.index_uri("file:///bar.rb", {
4386
+ r"
4387
+ class Foo::Bar
4388
+ def bar; end
4389
+ end
4390
+ "
4391
+ });
4392
+ context.resolve();
4393
+
4394
+ assert_no_diagnostics!(&context);
4395
+ assert_declaration_kind_eq!(context, "Foo", "<TODO>");
4396
+
4397
+ context.index_uri("file:///foo.rb", {
4398
+ r"
4399
+ class Foo
4400
+ def foo; end
4401
+ end
4402
+ "
4403
+ });
4404
+ context.resolve();
4405
+
4406
+ assert_no_diagnostics!(&context);
4407
+
4408
+ // Foo was promoted from Todo to Class after the second resolution
4409
+ assert_declaration_kind_eq!(context, "Foo", "Class");
4410
+
4411
+ assert_members_eq!(
4412
+ context,
4413
+ "Object",
4414
+ vec!["BasicObject", "Class", "Foo", "Kernel", "Module", "Object"]
4415
+ );
4416
+ assert_members_eq!(context, "Foo", vec!["Bar", "foo()"]);
4417
+ assert_members_eq!(context, "Foo::Bar", vec!["bar()"]);
4418
+ }
4419
+
4420
+ #[test]
4421
+ fn two_levels_unknown() {
4422
+ // class A::B::C — neither A nor B exist. Both should become Todos, C is a Class.
4423
+ let mut context = GraphTest::new();
4424
+ context.index_uri("file:///a.rb", {
4425
+ r"
4426
+ class A::B::C
4427
+ def foo; end
4428
+ end
4429
+ "
4430
+ });
4431
+ context.resolve();
4432
+
4433
+ assert_declaration_kind_eq!(context, "A", "<TODO>");
4434
+ assert_declaration_kind_eq!(context, "A::B", "<TODO>");
4435
+ assert_declaration_kind_eq!(context, "A::B::C", "Class");
4436
+ assert_members_eq!(
4437
+ context,
4438
+ "Object",
4439
+ vec!["A", "BasicObject", "Class", "Kernel", "Module", "Object"]
4440
+ );
4441
+ assert_members_eq!(context, "A", vec!["B"]);
4442
+ assert_members_eq!(context, "A::B", vec!["C"]);
4443
+ assert_members_eq!(context, "A::B::C", vec!["foo()"]);
4444
+ }
4445
+
4446
+ #[test]
4447
+ fn three_levels_unknown() {
4448
+ // class A::B::C::D — A, B, C are all unknown. Tests recursion beyond depth 2.
4449
+ let mut context = GraphTest::new();
4450
+ context.index_uri("file:///a.rb", {
4451
+ r"
4452
+ class A::B::C::D
4453
+ def foo; end
4454
+ end
4455
+ "
4456
+ });
4457
+ context.resolve();
4458
+
4459
+ assert_declaration_kind_eq!(context, "A", "<TODO>");
4460
+ assert_declaration_kind_eq!(context, "A::B", "<TODO>");
4461
+ assert_declaration_kind_eq!(context, "A::B::C", "<TODO>");
4462
+ assert_declaration_kind_eq!(context, "A::B::C::D", "Class");
4463
+ assert_members_eq!(
4464
+ context,
4465
+ "Object",
4466
+ vec!["A", "BasicObject", "Class", "Kernel", "Module", "Object"]
4467
+ );
4468
+ assert_members_eq!(context, "A", vec!["B"]);
4469
+ assert_members_eq!(context, "A::B", vec!["C"]);
4470
+ assert_members_eq!(context, "A::B::C", vec!["D"]);
4471
+ assert_members_eq!(context, "A::B::C::D", vec!["foo()"]);
4472
+ }
4473
+
4474
+ #[test]
4475
+ fn partially_unresolvable() {
4476
+ // A exists but B doesn't — A resolves to a real Module, B becomes a Todo under A.
4477
+ let mut context = GraphTest::new();
4478
+ context.index_uri("file:///a.rb", {
4479
+ r"
4480
+ module A; end
4481
+ class A::B::C
4482
+ def foo; end
4483
+ end
4484
+ "
4485
+ });
4486
+ context.resolve();
4487
+
4488
+ assert_declaration_kind_eq!(context, "A", "Module");
4489
+ assert_declaration_kind_eq!(context, "A::B", "<TODO>");
4490
+ assert_declaration_kind_eq!(context, "A::B::C", "Class");
4491
+ assert_members_eq!(context, "A", vec!["B"]);
4492
+ assert_members_eq!(context, "A::B", vec!["C"]);
4493
+ assert_members_eq!(context, "A::B::C", vec!["foo()"]);
4494
+ }
4495
+
4496
+ #[test]
4497
+ fn shared_by_sibling_classes() {
4498
+ // Two classes share the same unknown parent chain. The Todos for A and B should
4499
+ // be created once and reused, with both C and D as members of B.
4500
+ let mut context = GraphTest::new();
4501
+ context.index_uri("file:///a.rb", {
4502
+ r"
4503
+ class A::B::C
4504
+ def c_method; end
4505
+ end
4506
+
4507
+ class A::B::D
4508
+ def d_method; end
4509
+ end
4510
+ "
4511
+ });
4512
+ context.resolve();
4513
+
4514
+ assert_declaration_kind_eq!(context, "A", "<TODO>");
4515
+ assert_declaration_kind_eq!(context, "A::B", "<TODO>");
4516
+ assert_declaration_kind_eq!(context, "A::B::C", "Class");
4517
+ assert_declaration_kind_eq!(context, "A::B::D", "Class");
4518
+ assert_members_eq!(
4519
+ context,
4520
+ "Object",
4521
+ vec!["A", "BasicObject", "Class", "Kernel", "Module", "Object"]
4522
+ );
4523
+ assert_members_eq!(context, "A", vec!["B"]);
4524
+ assert_members_eq!(context, "A::B", vec!["C", "D"]);
4525
+ assert_members_eq!(context, "A::B::C", vec!["c_method()"]);
4526
+ assert_members_eq!(context, "A::B::D", vec!["d_method()"]);
4527
+ }
4528
+
4529
+ #[test]
4530
+ fn promoted_incrementally() {
4531
+ // Index class A::B::C first (creates Todos), then provide real definitions.
4532
+ // All Todos should be promoted to real namespaces.
4533
+ //
4534
+ // Note: we don't have true incremental resolution yet — each resolve() call
4535
+ // clears all declarations and re-resolves from scratch. This test verifies that
4536
+ // the promotion works when both files are present during the second resolution pass,
4537
+ // not that Todos are surgically updated in place.
4538
+ let mut context = GraphTest::new();
4539
+ context.index_uri("file:///c.rb", {
4540
+ r"
4541
+ class A::B::C
4542
+ def foo; end
4543
+ end
4544
+ "
4545
+ });
4546
+ context.resolve();
4547
+
4548
+ assert_declaration_kind_eq!(context, "A", "<TODO>");
4549
+ assert_declaration_kind_eq!(context, "A::B", "<TODO>");
4550
+ assert_declaration_kind_eq!(context, "A::B::C", "Class");
4551
+
4552
+ context.index_uri("file:///a.rb", {
4553
+ r"
4554
+ module A
4555
+ module B
4556
+ end
4557
+ end
4558
+ "
4559
+ });
4560
+ context.resolve();
4561
+
4562
+ // Todos should be promoted
4563
+ assert_declaration_kind_eq!(context, "A", "Module");
4564
+ assert_declaration_kind_eq!(context, "A::B", "Module");
4565
+ assert_declaration_kind_eq!(context, "A::B::C", "Class");
4566
+ assert_members_eq!(context, "A", vec!["B"]);
4567
+ assert_members_eq!(context, "A::B", vec!["C"]);
4568
+ assert_members_eq!(context, "A::B::C", vec!["foo()"]);
4569
+ }
4570
+
4571
+ #[test]
4572
+ fn with_self_method_and_ivar() {
4573
+ // def self.foo with @x inside a multi-level compact class — the SelfReceiver
4574
+ // on the method must find C's declaration to create the singleton class and ivar.
4575
+ let mut context = GraphTest::new();
4576
+ context.index_uri("file:///a.rb", {
4577
+ r"
4578
+ class A::B::C
4579
+ def self.foo
4580
+ @x = 1
4581
+ end
4582
+ end
4583
+ "
4584
+ });
4585
+ context.resolve();
4586
+
4587
+ assert_declaration_kind_eq!(context, "A", "<TODO>");
4588
+ assert_declaration_kind_eq!(context, "A::B", "<TODO>");
4589
+ assert_declaration_kind_eq!(context, "A::B::C", "Class");
4590
+ assert_declaration_exists!(context, "A::B::C::<C>#foo()");
4591
+ assert_declaration_exists!(context, "A::B::C::<C>#@x");
4592
+ }
4593
+
4594
+ #[test]
4595
+ fn nested_inside_module_with_separate_intermediate() {
4596
+ // Compact namespace nested inside a module, where the intermediate namespace
4597
+ // is defined separately. Bar::Baz should become a Todo since only Bar exists.
4598
+ let mut context = GraphTest::new();
4599
+ context.index_uri("file:///a.rb", {
4600
+ r"
4601
+ module Foo
4602
+ class Bar::Baz::Qux
4603
+ end
4604
+ end
4605
+
4606
+ module Bar; end
4607
+ "
4608
+ });
4609
+ context.resolve();
4610
+
4611
+ assert_declaration_kind_eq!(context, "Foo", "Module");
4612
+ assert_declaration_kind_eq!(context, "Bar", "Module");
4613
+ assert_declaration_kind_eq!(context, "Bar::Baz", "<TODO>");
4614
+ assert_declaration_kind_eq!(context, "Bar::Baz::Qux", "Class");
4615
+ assert_members_eq!(context, "Bar", vec!["Baz"]);
4616
+ assert_members_eq!(context, "Bar::Baz", vec!["Qux"]);
4617
+ }
4618
+
4619
+ #[test]
4620
+ fn no_todo_when_parent_is_reachable_through_include() {
4621
+ // Baz::Qux inside Foo, where Baz comes from included Bar module.
4622
+ // Baz::Qux should resolve through inheritance to Bar::Baz::Qux, not create
4623
+ // a top-level Baz Todo.
4624
+ let mut context = GraphTest::new();
4625
+ context.index_uri("file:///file1.rb", {
4626
+ r"
4627
+ module Foo
4628
+ include Bar
4629
+
4630
+ class Baz::Qux; end
4631
+ end
4632
+ "
4633
+ });
4634
+ context.index_uri("file:///file2.rb", {
4635
+ r"
4636
+ module Bar
4637
+ module Baz; end
4638
+ end
4639
+ "
4640
+ });
4641
+ context.resolve();
4642
+
4643
+ assert_declaration_exists!(context, "Bar::Baz");
4644
+ assert_declaration_exists!(context, "Bar::Baz::Qux");
4645
+ assert_members_eq!(context, "Bar::Baz", vec!["Qux"]);
4646
+ assert_declaration_does_not_exist!(context, "Foo::Baz");
4647
+ // No spurious top-level Baz Todo should be created
4648
+ assert_declaration_does_not_exist!(context, "Baz");
4649
+ // Baz::Qux should NOT exist at top level
4650
+ assert_declaration_does_not_exist!(context, "Baz::Qux");
4651
+ }
4652
+
4653
+ #[test]
4654
+ fn intermediate_todo_on_constant_alias() {
4655
+ let mut context = GraphTest::new();
4656
+ context.index_uri("file:///alias.rb", {
4657
+ r"
4658
+ module Bar; end
4659
+ module Foo; end
4660
+ Foo::Bar = Bar
4661
+ "
4662
+ });
4663
+ context.index_uri("file:///qux.rb", {
4664
+ r"
4665
+ class Foo::Bar::Baz::Qux
4666
+ end
4667
+ "
4668
+ });
4669
+
4670
+ context.resolve();
4671
+ assert_no_diagnostics!(&context);
4672
+
4673
+ assert_declaration_kind_eq!(context, "Foo", "Module");
4674
+ assert_declaration_kind_eq!(context, "Bar", "Module");
4675
+ assert_declaration_kind_eq!(context, "Foo::Bar", "ConstantAlias");
4676
+ assert_declaration_kind_eq!(context, "Bar::Baz", "<TODO>");
4677
+ assert_declaration_kind_eq!(context, "Bar::Baz::Qux", "Class");
4678
+ }
4679
+
4680
+ #[test]
4681
+ fn rbs_method_definition() {
4682
+ let mut context = GraphTest::new();
4683
+ context.index_rbs_uri("file:///foo.rbs", {
4684
+ r"
4685
+ class Foo
4686
+ def foo: () -> void
4687
+
4688
+ def self.bar: () -> void
4689
+
4690
+ def self?.baz: () -> void
4691
+ end
4692
+ "
4693
+ });
4694
+ context.resolve();
4695
+
4696
+ assert_no_diagnostics!(&context);
4697
+
4698
+ assert_members_eq!(context, "Foo", ["baz()", "foo()"]);
4699
+ assert_members_eq!(context, "Foo::<Foo>", ["bar()", "baz()"]);
4700
+ }
4701
+ }