rubydex 0.1.0.beta12-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +23 -0
  3. data/README.md +125 -0
  4. data/THIRD_PARTY_LICENSES.html +4562 -0
  5. data/exe/rdx +47 -0
  6. data/ext/rubydex/declaration.c +453 -0
  7. data/ext/rubydex/declaration.h +23 -0
  8. data/ext/rubydex/definition.c +284 -0
  9. data/ext/rubydex/definition.h +28 -0
  10. data/ext/rubydex/diagnostic.c +6 -0
  11. data/ext/rubydex/diagnostic.h +11 -0
  12. data/ext/rubydex/document.c +97 -0
  13. data/ext/rubydex/document.h +10 -0
  14. data/ext/rubydex/extconf.rb +138 -0
  15. data/ext/rubydex/graph.c +681 -0
  16. data/ext/rubydex/graph.h +10 -0
  17. data/ext/rubydex/handle.h +44 -0
  18. data/ext/rubydex/location.c +22 -0
  19. data/ext/rubydex/location.h +15 -0
  20. data/ext/rubydex/reference.c +123 -0
  21. data/ext/rubydex/reference.h +15 -0
  22. data/ext/rubydex/rubydex.c +22 -0
  23. data/ext/rubydex/utils.c +108 -0
  24. data/ext/rubydex/utils.h +34 -0
  25. data/lib/rubydex/3.2/rubydex.so +0 -0
  26. data/lib/rubydex/3.3/rubydex.so +0 -0
  27. data/lib/rubydex/3.4/rubydex.so +0 -0
  28. data/lib/rubydex/4.0/rubydex.so +0 -0
  29. data/lib/rubydex/comment.rb +17 -0
  30. data/lib/rubydex/diagnostic.rb +21 -0
  31. data/lib/rubydex/failures.rb +15 -0
  32. data/lib/rubydex/graph.rb +98 -0
  33. data/lib/rubydex/keyword.rb +17 -0
  34. data/lib/rubydex/keyword_parameter.rb +13 -0
  35. data/lib/rubydex/librubydex_sys.so +0 -0
  36. data/lib/rubydex/location.rb +90 -0
  37. data/lib/rubydex/mixin.rb +22 -0
  38. data/lib/rubydex/version.rb +5 -0
  39. data/lib/rubydex.rb +23 -0
  40. data/rbi/rubydex.rbi +422 -0
  41. data/rust/Cargo.lock +1851 -0
  42. data/rust/Cargo.toml +29 -0
  43. data/rust/about.hbs +78 -0
  44. data/rust/about.toml +10 -0
  45. data/rust/rubydex/Cargo.toml +42 -0
  46. data/rust/rubydex/src/compile_assertions.rs +13 -0
  47. data/rust/rubydex/src/diagnostic.rs +110 -0
  48. data/rust/rubydex/src/errors.rs +28 -0
  49. data/rust/rubydex/src/indexing/local_graph.rs +224 -0
  50. data/rust/rubydex/src/indexing/rbs_indexer.rs +1551 -0
  51. data/rust/rubydex/src/indexing/ruby_indexer.rs +2329 -0
  52. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +4962 -0
  53. data/rust/rubydex/src/indexing.rs +210 -0
  54. data/rust/rubydex/src/integrity.rs +279 -0
  55. data/rust/rubydex/src/job_queue.rs +205 -0
  56. data/rust/rubydex/src/lib.rs +17 -0
  57. data/rust/rubydex/src/listing.rs +371 -0
  58. data/rust/rubydex/src/main.rs +160 -0
  59. data/rust/rubydex/src/model/built_in.rs +83 -0
  60. data/rust/rubydex/src/model/comment.rs +24 -0
  61. data/rust/rubydex/src/model/declaration.rs +671 -0
  62. data/rust/rubydex/src/model/definitions.rs +1682 -0
  63. data/rust/rubydex/src/model/document.rs +222 -0
  64. data/rust/rubydex/src/model/encoding.rs +22 -0
  65. data/rust/rubydex/src/model/graph.rs +3754 -0
  66. data/rust/rubydex/src/model/id.rs +110 -0
  67. data/rust/rubydex/src/model/identity_maps.rs +58 -0
  68. data/rust/rubydex/src/model/ids.rs +60 -0
  69. data/rust/rubydex/src/model/keywords.rs +256 -0
  70. data/rust/rubydex/src/model/name.rs +298 -0
  71. data/rust/rubydex/src/model/references.rs +111 -0
  72. data/rust/rubydex/src/model/string_ref.rs +50 -0
  73. data/rust/rubydex/src/model/visibility.rs +41 -0
  74. data/rust/rubydex/src/model.rs +15 -0
  75. data/rust/rubydex/src/offset.rs +147 -0
  76. data/rust/rubydex/src/position.rs +6 -0
  77. data/rust/rubydex/src/query.rs +1841 -0
  78. data/rust/rubydex/src/resolution.rs +6517 -0
  79. data/rust/rubydex/src/stats/memory.rs +71 -0
  80. data/rust/rubydex/src/stats/orphan_report.rs +264 -0
  81. data/rust/rubydex/src/stats/timer.rs +127 -0
  82. data/rust/rubydex/src/stats.rs +11 -0
  83. data/rust/rubydex/src/test_utils/context.rs +226 -0
  84. data/rust/rubydex/src/test_utils/graph_test.rs +730 -0
  85. data/rust/rubydex/src/test_utils/local_graph_test.rs +602 -0
  86. data/rust/rubydex/src/test_utils.rs +52 -0
  87. data/rust/rubydex/src/visualization/dot.rs +192 -0
  88. data/rust/rubydex/src/visualization.rs +6 -0
  89. data/rust/rubydex/tests/cli.rs +185 -0
  90. data/rust/rubydex-mcp/Cargo.toml +28 -0
  91. data/rust/rubydex-mcp/src/main.rs +48 -0
  92. data/rust/rubydex-mcp/src/server.rs +1145 -0
  93. data/rust/rubydex-mcp/src/tools.rs +49 -0
  94. data/rust/rubydex-mcp/tests/mcp.rs +302 -0
  95. data/rust/rubydex-sys/Cargo.toml +20 -0
  96. data/rust/rubydex-sys/build.rs +14 -0
  97. data/rust/rubydex-sys/cbindgen.toml +12 -0
  98. data/rust/rubydex-sys/src/declaration_api.rs +485 -0
  99. data/rust/rubydex-sys/src/definition_api.rs +443 -0
  100. data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
  101. data/rust/rubydex-sys/src/document_api.rs +85 -0
  102. data/rust/rubydex-sys/src/graph_api.rs +948 -0
  103. data/rust/rubydex-sys/src/lib.rs +79 -0
  104. data/rust/rubydex-sys/src/location_api.rs +79 -0
  105. data/rust/rubydex-sys/src/name_api.rs +135 -0
  106. data/rust/rubydex-sys/src/reference_api.rs +267 -0
  107. data/rust/rubydex-sys/src/utils.rs +70 -0
  108. data/rust/rustfmt.toml +2 -0
  109. metadata +159 -0
@@ -0,0 +1,4962 @@
1
+ use crate::{
2
+ assert_def_comments_eq, assert_def_mixins_eq, assert_def_name_eq, assert_def_name_offset_eq, assert_def_str_eq,
3
+ assert_def_superclass_ref_eq, assert_definition_at, assert_dependents, assert_local_diagnostics_eq,
4
+ assert_method_has_receiver, assert_name_path_eq, assert_no_local_diagnostics, assert_string_eq,
5
+ model::{
6
+ definitions::{Definition, Parameter, Receiver, Signatures},
7
+ ids::{StringId, UriId},
8
+ visibility::Visibility,
9
+ },
10
+ test_utils::LocalGraphTest,
11
+ };
12
+
13
+ /// Asserts that a method has a simple (non-overloaded) signature, then runs a closure with it.
14
+ ///
15
+ /// Usage:
16
+ /// - `assert_simple_signature!(def, |params| { assert_eq!(params.len(), 2); })`
17
+ macro_rules! assert_simple_signature {
18
+ ($def:expr, |$params:ident| $body:block) => {
19
+ match $def.signatures() {
20
+ Signatures::Simple($params) => $body,
21
+ other => panic!("expected Simple signature, got {:?}", other),
22
+ }
23
+ };
24
+ }
25
+
26
+ // Reference assertions
27
+
28
+ macro_rules! assert_constant_references_eq {
29
+ ($context:expr, $expected_names:expr) => {{
30
+ let mut actual_references = $context
31
+ .graph()
32
+ .constant_references()
33
+ .values()
34
+ .map(|r| {
35
+ let name = $context.graph().names().get(r.name_id()).unwrap();
36
+ (
37
+ r.offset().start(),
38
+ $context.graph().strings().get(name.str()).unwrap().as_str(),
39
+ )
40
+ })
41
+ .collect::<Vec<_>>();
42
+
43
+ actual_references.sort();
44
+
45
+ let actual_names = actual_references.iter().map(|(_, name)| *name).collect::<Vec<_>>();
46
+
47
+ assert_eq!(
48
+ $expected_names,
49
+ actual_names.as_slice(),
50
+ "constant references mismatch: expected `{:?}`, got `{:?}`",
51
+ $expected_names,
52
+ actual_names
53
+ );
54
+ }};
55
+ }
56
+
57
+ macro_rules! assert_method_references_eq {
58
+ ($context:expr, $expected_names:expr) => {{
59
+ let mut actual_references = $context
60
+ .graph()
61
+ .method_references()
62
+ .values()
63
+ .map(|m| {
64
+ (
65
+ m.offset().start(),
66
+ $context.graph().strings().get(m.str()).unwrap().as_str(),
67
+ )
68
+ })
69
+ .collect::<Vec<_>>();
70
+
71
+ actual_references.sort();
72
+
73
+ let actual_names = actual_references
74
+ .iter()
75
+ .map(|(_offset, name)| *name)
76
+ .collect::<Vec<_>>();
77
+
78
+ assert_eq!(
79
+ $expected_names,
80
+ actual_names.as_slice(),
81
+ "method references mismatch: expected `{:?}`, got `{:?}`",
82
+ $expected_names,
83
+ actual_names
84
+ );
85
+ }};
86
+ }
87
+
88
+ macro_rules! assert_promotable {
89
+ ($def:expr) => {{
90
+ assert!(
91
+ $def.flags().is_promotable(),
92
+ "expected definition to be promotable, but it was not"
93
+ );
94
+ }};
95
+ }
96
+
97
+ macro_rules! assert_not_promotable {
98
+ ($def:expr) => {{
99
+ assert!(
100
+ !$def.flags().is_promotable(),
101
+ "expected definition to not be promotable, but it was"
102
+ );
103
+ }};
104
+ }
105
+
106
+ fn index_source(source: &str) -> LocalGraphTest {
107
+ LocalGraphTest::new("file:///foo.rb", source)
108
+ }
109
+
110
+ #[test]
111
+ fn index_source_with_errors() {
112
+ let context = index_source({
113
+ "
114
+ class Foo
115
+ "
116
+ });
117
+
118
+ assert_local_diagnostics_eq!(
119
+ &context,
120
+ [
121
+ "parse-error: expected an `end` to close the `class` statement (1:1-1:6)",
122
+ "parse-error: unexpected end-of-input, assuming it is closing the parent top level context (1:10-2:1)"
123
+ ]
124
+ );
125
+
126
+ // We still index the definition, even though it has errors
127
+ assert_eq!(context.graph().definitions().len(), 1);
128
+ assert_definition_at!(&context, "1:1-2:1", Class, |def| {
129
+ assert_def_name_eq!(&context, def, "Foo");
130
+ });
131
+ }
132
+
133
+ #[test]
134
+ fn index_source_with_warnings() {
135
+ let context = index_source({
136
+ "
137
+ foo = 42
138
+ "
139
+ });
140
+
141
+ assert_local_diagnostics_eq!(
142
+ &context,
143
+ ["parse-warning: assigned but unused variable - foo (1:1-1:4)"]
144
+ );
145
+ }
146
+
147
+ #[test]
148
+ fn index_alias_method_ignores_method_nesting() {
149
+ let context = index_source({
150
+ "
151
+ class Foo
152
+ def bar
153
+ alias_method :new_to_s, :to_s
154
+ end
155
+ end
156
+ "
157
+ });
158
+
159
+ assert_no_local_diagnostics!(&context);
160
+
161
+ assert_definition_at!(&context, "1:1-5:4", Class, |foo| {
162
+ assert_definition_at!(&context, "3:5-3:34", MethodAlias, |alias_method| {
163
+ assert_eq!(foo.id(), alias_method.lexical_nesting_id().unwrap());
164
+ });
165
+ });
166
+ }
167
+
168
+ #[test]
169
+ fn index_alias_ignores_method_nesting() {
170
+ let context = index_source({
171
+ "
172
+ class Foo
173
+ def bar
174
+ alias new_to_s to_s
175
+ end
176
+ end
177
+ "
178
+ });
179
+
180
+ assert_no_local_diagnostics!(&context);
181
+
182
+ assert_definition_at!(&context, "1:1-5:4", Class, |foo| {
183
+ assert_definition_at!(&context, "3:5-3:24", MethodAlias, |alias_method| {
184
+ assert!(alias_method.receiver().is_none());
185
+ assert_eq!(foo.id(), alias_method.lexical_nesting_id().unwrap());
186
+ });
187
+ });
188
+ }
189
+
190
+ #[test]
191
+ fn index_includes_at_top_level() {
192
+ let context = index_source({
193
+ "
194
+ include Bar, Baz
195
+ include Qux
196
+ "
197
+ });
198
+
199
+ assert_no_local_diagnostics!(&context);
200
+
201
+ // FIXME: This should be indexed
202
+ assert_eq!(context.graph().definitions().len(), 0);
203
+ }
204
+
205
+ #[test]
206
+ fn index_includes_in_classes() {
207
+ let context = index_source({
208
+ "
209
+ class Foo
210
+ include Bar, Baz
211
+ include Qux
212
+ end
213
+ "
214
+ });
215
+
216
+ assert_no_local_diagnostics!(&context);
217
+
218
+ assert_definition_at!(&context, "1:1-4:4", Class, |def| {
219
+ assert_def_mixins_eq!(&context, def, Include, ["Baz", "Bar", "Qux"]);
220
+ });
221
+ }
222
+
223
+ #[test]
224
+ fn index_includes_in_modules() {
225
+ let context = index_source({
226
+ "
227
+ module Foo
228
+ include Bar, Baz
229
+ include Qux
230
+ end
231
+ "
232
+ });
233
+
234
+ assert_no_local_diagnostics!(&context);
235
+
236
+ assert_definition_at!(&context, "1:1-4:4", Module, |def| {
237
+ assert_def_mixins_eq!(&context, def, Include, ["Baz", "Bar", "Qux"]);
238
+ });
239
+ }
240
+
241
+ #[test]
242
+ fn index_prepends_at_top_level() {
243
+ let context = index_source({
244
+ "
245
+ prepend Bar, Baz
246
+ prepend Qux
247
+ "
248
+ });
249
+
250
+ assert_no_local_diagnostics!(&context);
251
+
252
+ // FIXME: This should be indexed
253
+ assert_eq!(context.graph().definitions().len(), 0);
254
+ }
255
+
256
+ #[test]
257
+ fn index_prepends_in_classes() {
258
+ let context = index_source({
259
+ "
260
+ class Foo
261
+ prepend Bar, Baz
262
+ prepend Qux
263
+ end
264
+ "
265
+ });
266
+
267
+ assert_no_local_diagnostics!(&context);
268
+
269
+ assert_definition_at!(&context, "1:1-4:4", Class, |def| {
270
+ assert_def_mixins_eq!(&context, def, Prepend, ["Baz", "Bar", "Qux"]);
271
+ });
272
+ }
273
+
274
+ #[test]
275
+ fn index_prepends_in_modules() {
276
+ let context = index_source({
277
+ "
278
+ module Foo
279
+ prepend Bar, Baz
280
+ prepend Qux
281
+ end
282
+ "
283
+ });
284
+
285
+ assert_no_local_diagnostics!(&context);
286
+
287
+ assert_definition_at!(&context, "1:1-4:4", Module, |def| {
288
+ assert_def_mixins_eq!(&context, def, Prepend, ["Baz", "Bar", "Qux"]);
289
+ });
290
+ }
291
+
292
+ #[test]
293
+ fn index_extends_in_class() {
294
+ let context = index_source({
295
+ "
296
+ class Foo
297
+ extend Bar
298
+ extend Baz
299
+ end
300
+ "
301
+ });
302
+
303
+ assert_no_local_diagnostics!(&context);
304
+
305
+ assert_definition_at!(&context, "1:1-4:4", Class, |class_def| {
306
+ assert_def_mixins_eq!(&context, class_def, Extend, ["Bar", "Baz"]);
307
+ });
308
+ }
309
+
310
+ #[test]
311
+ fn index_mixins_self() {
312
+ let context = index_source({
313
+ "
314
+ module Foo
315
+ include self
316
+ prepend self
317
+ extend self
318
+ end
319
+ "
320
+ });
321
+
322
+ assert_no_local_diagnostics!(&context);
323
+
324
+ assert_definition_at!(&context, "1:1-5:4", Module, |def| {
325
+ assert_def_mixins_eq!(&context, def, Include, ["Foo"]);
326
+ assert_def_mixins_eq!(&context, def, Prepend, ["Foo"]);
327
+ assert_def_mixins_eq!(&context, def, Extend, ["Foo"]);
328
+ });
329
+ }
330
+
331
+ #[test]
332
+ fn index_mixins_with_dynamic_constants() {
333
+ let context = index_source({
334
+ "
335
+ include foo::Bar
336
+ prepend foo::Baz
337
+ extend foo::Qux
338
+
339
+ include foo
340
+ prepend 123
341
+ extend 'x'
342
+ "
343
+ });
344
+
345
+ assert_local_diagnostics_eq!(
346
+ &context,
347
+ [
348
+ "dynamic-constant-reference: Dynamic constant reference (1:9-1:12)",
349
+ "dynamic-ancestor: Dynamic mixin argument (1:9-1:17)",
350
+ "dynamic-constant-reference: Dynamic constant reference (2:9-2:12)",
351
+ "dynamic-ancestor: Dynamic mixin argument (2:9-2:17)",
352
+ "dynamic-constant-reference: Dynamic constant reference (3:8-3:11)",
353
+ "dynamic-ancestor: Dynamic mixin argument (3:8-3:16)",
354
+ "dynamic-ancestor: Dynamic mixin argument (5:9-5:12)",
355
+ "dynamic-ancestor: Dynamic mixin argument (6:9-6:12)",
356
+ "dynamic-ancestor: Dynamic mixin argument (7:8-7:11)"
357
+ ]
358
+ );
359
+ assert!(context.graph().definitions().is_empty());
360
+ }
361
+
362
+ #[test]
363
+ fn index_mixins_self_at_top_level() {
364
+ let context = index_source({
365
+ "
366
+ include self
367
+ prepend self
368
+ extend self
369
+ "
370
+ });
371
+
372
+ assert_local_diagnostics_eq!(
373
+ &context,
374
+ [
375
+ "top-level-mixin-self: Top level mixin self (1:9-1:13)",
376
+ "top-level-mixin-self: Top level mixin self (2:9-2:13)",
377
+ "top-level-mixin-self: Top level mixin self (3:8-3:12)"
378
+ ]
379
+ );
380
+
381
+ assert_eq!(context.graph().definitions().len(), 0);
382
+ }
383
+
384
+ #[test]
385
+ fn index_alias_methods_nested() {
386
+ let context = index_source({
387
+ "
388
+ class Foo
389
+ alias foo bar
390
+ alias :baz :qux
391
+ end
392
+ "
393
+ });
394
+
395
+ assert_no_local_diagnostics!(&context);
396
+
397
+ assert_definition_at!(&context, "1:1-4:4", Class, |foo_class_def| {
398
+ assert_definition_at!(&context, "2:3-2:16", MethodAlias, |def| {
399
+ let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
400
+ let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
401
+ assert_eq!(new_name.as_str(), "foo()");
402
+ assert_eq!(old_name.as_str(), "bar()");
403
+ assert!(def.receiver().is_none());
404
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
405
+ });
406
+
407
+ assert_definition_at!(&context, "3:3-3:18", MethodAlias, |def| {
408
+ let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
409
+ let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
410
+ assert_eq!(new_name.as_str(), "baz()");
411
+ assert_eq!(old_name.as_str(), "qux()");
412
+ assert!(def.receiver().is_none());
413
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
414
+ });
415
+ });
416
+ }
417
+
418
+ #[test]
419
+ fn index_alias_methods_top_level() {
420
+ let context = index_source({
421
+ "
422
+ alias foo bar
423
+ alias :baz :qux
424
+ "
425
+ });
426
+
427
+ assert_no_local_diagnostics!(&context);
428
+
429
+ assert_definition_at!(&context, "1:1-1:14", MethodAlias, |def| {
430
+ let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
431
+ let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
432
+ assert_eq!(new_name.as_str(), "foo()");
433
+ assert_eq!(old_name.as_str(), "bar()");
434
+ assert!(def.receiver().is_none());
435
+ assert!(def.lexical_nesting_id().is_none());
436
+ });
437
+
438
+ assert_definition_at!(&context, "2:1-2:16", MethodAlias, |def| {
439
+ let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
440
+ let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
441
+ assert_eq!(new_name.as_str(), "baz()");
442
+ assert_eq!(old_name.as_str(), "qux()");
443
+
444
+ assert!(def.lexical_nesting_id().is_none());
445
+ });
446
+ }
447
+
448
+ #[test]
449
+ fn index_module_alias_method() {
450
+ let context = index_source({
451
+ r#"
452
+ alias_method :foo_symbol, :bar_symbol
453
+ alias_method "foo_string", "bar_string"
454
+
455
+ class Foo
456
+ alias_method :baz, :qux
457
+ end
458
+
459
+ alias_method :baz, ignored
460
+ alias_method ignored, :qux
461
+ alias_method ignored, ignored
462
+ "#
463
+ });
464
+
465
+ assert_no_local_diagnostics!(&context);
466
+
467
+ assert_definition_at!(&context, "1:1-1:38", MethodAlias, |def| {
468
+ let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
469
+ let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
470
+ assert_eq!(new_name.as_str(), "foo_symbol()");
471
+ assert_eq!(old_name.as_str(), "bar_symbol()");
472
+ assert!(def.receiver().is_none());
473
+ assert!(def.lexical_nesting_id().is_none());
474
+ });
475
+
476
+ assert_definition_at!(&context, "2:1-2:40", MethodAlias, |def| {
477
+ let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
478
+ let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
479
+ assert_eq!(new_name.as_str(), "foo_string()");
480
+ assert_eq!(old_name.as_str(), "bar_string()");
481
+ assert!(def.receiver().is_none());
482
+ assert!(def.lexical_nesting_id().is_none());
483
+ });
484
+
485
+ assert_definition_at!(&context, "4:1-6:4", Class, |foo_class_def| {
486
+ assert_definition_at!(&context, "5:3-5:26", MethodAlias, |def| {
487
+ let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
488
+ let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
489
+ assert_eq!(new_name.as_str(), "baz()");
490
+ assert_eq!(old_name.as_str(), "qux()");
491
+ assert!(def.receiver().is_none());
492
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
493
+ });
494
+ });
495
+ }
496
+
497
+ #[test]
498
+ fn index_alias_method_with_self_receiver_maps_to_none() {
499
+ let context = index_source({
500
+ "
501
+ class Foo
502
+ self.alias_method :bar, :baz
503
+ end
504
+ "
505
+ });
506
+
507
+ assert_no_local_diagnostics!(&context);
508
+
509
+ assert_definition_at!(&context, "2:3-2:31", MethodAlias, |def| {
510
+ assert!(def.receiver().is_none());
511
+ });
512
+ }
513
+
514
+ #[test]
515
+ fn index_alias_method_with_constant_receiver() {
516
+ let context = index_source({
517
+ "
518
+ class Foo; end
519
+ Foo.alias_method :bar, :baz
520
+ "
521
+ });
522
+
523
+ assert_no_local_diagnostics!(&context);
524
+
525
+ assert_definition_at!(&context, "2:1-2:28", MethodAlias, |def| {
526
+ assert_string_eq!(&context, def.new_name_str_id(), "bar()");
527
+ assert_string_eq!(&context, def.old_name_str_id(), "baz()");
528
+ assert_method_has_receiver!(&context, def, "Foo");
529
+ });
530
+ }
531
+
532
+ #[test]
533
+ fn index_alias_method_in_singleton_class_has_no_receiver() {
534
+ let context = index_source({
535
+ "
536
+ class Foo
537
+ def self.find; end
538
+
539
+ class << self
540
+ alias_method :find_old, :find
541
+ end
542
+ end
543
+ "
544
+ });
545
+
546
+ assert_no_local_diagnostics!(&context);
547
+
548
+ assert_definition_at!(&context, "1:1-7:4", Class, |_foo| {
549
+ assert_definition_at!(&context, "4:3-6:6", SingletonClass, |singleton| {
550
+ assert_definition_at!(&context, "5:5-5:34", MethodAlias, |def| {
551
+ assert_string_eq!(&context, def.new_name_str_id(), "find_old()");
552
+ assert_string_eq!(&context, def.old_name_str_id(), "find()");
553
+ assert!(def.receiver().is_none());
554
+ assert_eq!(singleton.id(), def.lexical_nesting_id().unwrap());
555
+ });
556
+ });
557
+ });
558
+ }
559
+
560
+ #[test]
561
+ fn index_alias_keyword_in_singleton_class_has_no_receiver() {
562
+ // Same as above: `alias` inside `class << self` has no receiver.
563
+ let context = index_source({
564
+ "
565
+ class Foo
566
+ def self.find; end
567
+
568
+ class << self
569
+ alias find_old find
570
+ end
571
+ end
572
+ "
573
+ });
574
+
575
+ assert_no_local_diagnostics!(&context);
576
+
577
+ assert_definition_at!(&context, "4:3-6:6", SingletonClass, |singleton| {
578
+ assert_definition_at!(&context, "5:5-5:24", MethodAlias, |def| {
579
+ assert!(def.receiver().is_none());
580
+ assert_eq!(singleton.id(), def.lexical_nesting_id().unwrap());
581
+ });
582
+ });
583
+ }
584
+
585
+ #[test]
586
+ fn index_alias_method_with_nested_constant_receiver() {
587
+ let context = index_source({
588
+ "
589
+ module A
590
+ class B
591
+ def original; end
592
+ end
593
+ end
594
+
595
+ A::B.alias_method :new_name, :original
596
+ "
597
+ });
598
+
599
+ assert_no_local_diagnostics!(&context);
600
+
601
+ assert_definition_at!(&context, "7:1-7:39", MethodAlias, |def| {
602
+ assert_string_eq!(&context, def.new_name_str_id(), "new_name()");
603
+ assert_method_has_receiver!(&context, def, "B");
604
+ assert!(def.lexical_nesting_id().is_none());
605
+ });
606
+ }
607
+
608
+ #[test]
609
+ fn index_alias_method_with_dynamic_receiver_not_indexed() {
610
+ let context = index_source({
611
+ "
612
+ class Foo
613
+ def original; end
614
+ end
615
+
616
+ foo.alias_method :new_name, :original
617
+ "
618
+ });
619
+
620
+ assert_no_local_diagnostics!(&context);
621
+
622
+ let alias_count = context
623
+ .graph()
624
+ .definitions()
625
+ .values()
626
+ .filter(|def| matches!(def, Definition::MethodAlias(_)))
627
+ .count();
628
+ assert_eq!(0, alias_count);
629
+ }
630
+
631
+ #[test]
632
+ fn index_alias_global_variables() {
633
+ let context = index_source({
634
+ "
635
+ alias $foo $bar
636
+
637
+ class Foo
638
+ alias $baz $qux
639
+ end
640
+ "
641
+ });
642
+
643
+ assert_no_local_diagnostics!(&context);
644
+
645
+ assert_definition_at!(&context, "1:1-1:16", GlobalVariableAlias, |def| {
646
+ let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
647
+ let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
648
+ assert_eq!(new_name.as_str(), "$foo");
649
+ assert_eq!(old_name.as_str(), "$bar");
650
+
651
+ assert!(def.lexical_nesting_id().is_none());
652
+ });
653
+
654
+ assert_definition_at!(&context, "3:1-5:4", Class, |foo_class_def| {
655
+ assert_definition_at!(&context, "4:3-4:18", GlobalVariableAlias, |def| {
656
+ let new_name = context.graph().strings().get(def.new_name_str_id()).unwrap();
657
+ let old_name = context.graph().strings().get(def.old_name_str_id()).unwrap();
658
+ assert_eq!(new_name.as_str(), "$baz");
659
+ assert_eq!(old_name.as_str(), "$qux");
660
+
661
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
662
+ });
663
+ });
664
+ }
665
+
666
+ #[test]
667
+ fn index_module_new() {
668
+ let context = index_source({
669
+ "
670
+ module Foo
671
+ Bar = Module.new do
672
+ include Baz
673
+
674
+ def qux
675
+ @var = 123
676
+ end
677
+ attr_reader :hello
678
+ end
679
+ end
680
+ "
681
+ });
682
+ assert_no_local_diagnostics!(&context);
683
+
684
+ assert_definition_at!(&context, "1:1-10:4", Module, |foo| {
685
+ assert_definition_at!(&context, "2:3-9:6", Module, |bar| {
686
+ assert_definition_at!(&context, "5:5-7:8", Method, |qux| {
687
+ assert_definition_at!(&context, "6:7-6:11", InstanceVariable, |var| {
688
+ assert_definition_at!(&context, "8:18-8:23", AttrReader, |hello| {
689
+ assert_def_name_eq!(&context, bar, "Bar");
690
+ assert_eq!(foo.id(), bar.lexical_nesting_id().unwrap());
691
+ assert_eq!(foo.members()[0], bar.id());
692
+
693
+ assert_eq!(bar.members()[0], qux.id());
694
+ assert_eq!(bar.members()[1], var.id());
695
+ assert_eq!(bar.members()[2], hello.id());
696
+
697
+ // We expect the `Baz` constant name to NOT be associated with `Bar` because `Module.new` does not
698
+ // produce a new lexical scope
699
+ let include = bar.mixins().first().unwrap();
700
+ let name = context
701
+ .graph()
702
+ .names()
703
+ .get(
704
+ context
705
+ .graph()
706
+ .constant_references()
707
+ .get(include.constant_reference_id())
708
+ .unwrap()
709
+ .name_id(),
710
+ )
711
+ .unwrap();
712
+
713
+ assert_eq!(StringId::from("Baz"), *name.str());
714
+ assert!(name.parent_scope().is_none());
715
+
716
+ let nesting_name = context.graph().names().get(&name.nesting().unwrap()).unwrap();
717
+ assert_eq!(StringId::from("Foo"), *nesting_name.str());
718
+ });
719
+ });
720
+ });
721
+ });
722
+ });
723
+ }
724
+
725
+ #[test]
726
+ fn index_module_new_with_constant_path() {
727
+ let context = index_source({
728
+ "
729
+ module Foo
730
+ Zip::Bar = Module.new do
731
+ include Baz
732
+
733
+ def qux
734
+ @var = 123
735
+ end
736
+ attr_reader :hello
737
+ end
738
+ end
739
+ "
740
+ });
741
+ assert_no_local_diagnostics!(&context);
742
+
743
+ assert_definition_at!(&context, "1:1-10:4", Module, |foo| {
744
+ assert_definition_at!(&context, "2:3-9:6", Module, |bar| {
745
+ assert_definition_at!(&context, "5:5-7:8", Method, |qux| {
746
+ assert_definition_at!(&context, "6:7-6:11", InstanceVariable, |var| {
747
+ assert_definition_at!(&context, "8:18-8:23", AttrReader, |hello| {
748
+ assert_def_name_eq!(&context, bar, "Zip::Bar");
749
+ assert_eq!(foo.id(), bar.lexical_nesting_id().unwrap());
750
+ assert_eq!(foo.members()[0], bar.id());
751
+
752
+ assert_eq!(bar.members()[0], qux.id());
753
+ assert_eq!(bar.members()[1], var.id());
754
+ assert_eq!(bar.members()[2], hello.id());
755
+
756
+ // We expect the `Baz` constant name to NOT be associated with `Bar` because `Module.new` does not
757
+ // produce a new lexical scope
758
+ let include = bar.mixins().first().unwrap();
759
+ let name = context
760
+ .graph()
761
+ .names()
762
+ .get(
763
+ context
764
+ .graph()
765
+ .constant_references()
766
+ .get(include.constant_reference_id())
767
+ .unwrap()
768
+ .name_id(),
769
+ )
770
+ .unwrap();
771
+
772
+ assert_eq!(StringId::from("Baz"), *name.str());
773
+ assert!(name.parent_scope().is_none());
774
+
775
+ let nesting_name = context.graph().names().get(&name.nesting().unwrap()).unwrap();
776
+ assert_eq!(StringId::from("Foo"), *nesting_name.str());
777
+ });
778
+ });
779
+ });
780
+ });
781
+ });
782
+ }
783
+
784
+ #[test]
785
+ fn index_class_new() {
786
+ let context = index_source({
787
+ "
788
+ module Foo
789
+ Bar = Class.new(Parent) do
790
+ include Baz
791
+
792
+ def qux
793
+ @var = 123
794
+ end
795
+ attr_reader :hello
796
+ end
797
+ end
798
+ "
799
+ });
800
+ assert_no_local_diagnostics!(&context);
801
+
802
+ assert_definition_at!(&context, "1:1-10:4", Module, |foo| {
803
+ assert_definition_at!(&context, "2:3-9:6", Class, |bar| {
804
+ assert_definition_at!(&context, "5:5-7:8", Method, |qux| {
805
+ assert_definition_at!(&context, "6:7-6:11", InstanceVariable, |var| {
806
+ assert_definition_at!(&context, "8:18-8:23", AttrReader, |hello| {
807
+ assert_def_name_eq!(&context, bar, "Bar");
808
+ assert_eq!(foo.id(), bar.lexical_nesting_id().unwrap());
809
+ assert_eq!(foo.members()[0], bar.id());
810
+
811
+ assert_eq!(bar.members()[0], qux.id());
812
+ assert_eq!(bar.members()[1], var.id());
813
+ assert_eq!(bar.members()[2], hello.id());
814
+
815
+ assert_def_superclass_ref_eq!(&context, bar, "Parent");
816
+
817
+ // We expect the `Baz` constant name to NOT be associated with `Bar` because `Module.new` does not
818
+ // produce a new lexical scope
819
+ let include = bar.mixins().first().unwrap();
820
+ let name = context
821
+ .graph()
822
+ .names()
823
+ .get(
824
+ context
825
+ .graph()
826
+ .constant_references()
827
+ .get(include.constant_reference_id())
828
+ .unwrap()
829
+ .name_id(),
830
+ )
831
+ .unwrap();
832
+
833
+ assert_eq!(StringId::from("Baz"), *name.str());
834
+ assert!(name.parent_scope().is_none());
835
+
836
+ let nesting_name = context.graph().names().get(&name.nesting().unwrap()).unwrap();
837
+ assert_eq!(StringId::from("Foo"), *nesting_name.str());
838
+ });
839
+ });
840
+ });
841
+ });
842
+ });
843
+ }
844
+
845
+ #[test]
846
+ fn index_class_new_no_parent() {
847
+ let context = index_source({
848
+ "
849
+ module Foo
850
+ Bar = Class.new do
851
+ include Baz
852
+
853
+ def qux
854
+ @var = 123
855
+ end
856
+ attr_reader :hello
857
+ end
858
+ end
859
+ "
860
+ });
861
+ assert_no_local_diagnostics!(&context);
862
+
863
+ assert_definition_at!(&context, "1:1-10:4", Module, |foo| {
864
+ assert_definition_at!(&context, "2:3-9:6", Class, |bar| {
865
+ assert_definition_at!(&context, "5:5-7:8", Method, |qux| {
866
+ assert_definition_at!(&context, "6:7-6:11", InstanceVariable, |var| {
867
+ assert_definition_at!(&context, "8:18-8:23", AttrReader, |hello| {
868
+ assert_def_name_eq!(&context, bar, "Bar");
869
+ assert_eq!(foo.id(), bar.lexical_nesting_id().unwrap());
870
+ assert_eq!(foo.members()[0], bar.id());
871
+
872
+ assert_eq!(bar.members()[0], qux.id());
873
+ assert_eq!(bar.members()[1], var.id());
874
+ assert_eq!(bar.members()[2], hello.id());
875
+
876
+ // We expect the `Baz` constant name to NOT be associated with `Bar` because `Module.new` does not
877
+ // produce a new lexical scope
878
+ let include = bar.mixins().first().unwrap();
879
+ let name = context
880
+ .graph()
881
+ .names()
882
+ .get(
883
+ context
884
+ .graph()
885
+ .constant_references()
886
+ .get(include.constant_reference_id())
887
+ .unwrap()
888
+ .name_id(),
889
+ )
890
+ .unwrap();
891
+
892
+ assert_eq!(StringId::from("Baz"), *name.str());
893
+ assert!(name.parent_scope().is_none());
894
+
895
+ let nesting_name = context.graph().names().get(&name.nesting().unwrap()).unwrap();
896
+ assert_eq!(StringId::from("Foo"), *nesting_name.str());
897
+ });
898
+ });
899
+ });
900
+ });
901
+ });
902
+ }
903
+
904
+ #[test]
905
+ fn index_class_new_with_constant_path() {
906
+ let context = index_source({
907
+ "
908
+ module Foo
909
+ Zip::Bar = Class.new(Parent) do
910
+ include Baz
911
+
912
+ def qux
913
+ @var = 123
914
+ end
915
+ attr_reader :hello
916
+ end
917
+ end
918
+ "
919
+ });
920
+ assert_no_local_diagnostics!(&context);
921
+
922
+ assert_definition_at!(&context, "1:1-10:4", Module, |foo| {
923
+ assert_definition_at!(&context, "2:3-9:6", Class, |bar| {
924
+ assert_definition_at!(&context, "5:5-7:8", Method, |qux| {
925
+ assert_definition_at!(&context, "6:7-6:11", InstanceVariable, |var| {
926
+ assert_definition_at!(&context, "8:18-8:23", AttrReader, |hello| {
927
+ assert_def_name_eq!(&context, bar, "Zip::Bar");
928
+ assert_eq!(foo.id(), bar.lexical_nesting_id().unwrap());
929
+ assert_eq!(foo.members()[0], bar.id());
930
+
931
+ assert_eq!(bar.members()[0], qux.id());
932
+ assert_eq!(bar.members()[1], var.id());
933
+ assert_eq!(bar.members()[2], hello.id());
934
+
935
+ assert_def_superclass_ref_eq!(&context, bar, "Parent");
936
+
937
+ // We expect the `Baz` constant name to NOT be associated with `Bar` because `Module.new` does not
938
+ // produce a new lexical scope
939
+ let include = bar.mixins().first().unwrap();
940
+ let name = context
941
+ .graph()
942
+ .names()
943
+ .get(
944
+ context
945
+ .graph()
946
+ .constant_references()
947
+ .get(include.constant_reference_id())
948
+ .unwrap()
949
+ .name_id(),
950
+ )
951
+ .unwrap();
952
+
953
+ assert_eq!(StringId::from("Baz"), *name.str());
954
+ assert!(name.parent_scope().is_none());
955
+
956
+ let nesting_name = context.graph().names().get(&name.nesting().unwrap()).unwrap();
957
+ assert_eq!(StringId::from("Foo"), *nesting_name.str());
958
+ });
959
+ });
960
+ });
961
+ });
962
+ });
963
+ }
964
+
965
+ #[test]
966
+ fn index_top_level_class_and_module_new() {
967
+ let context = index_source({
968
+ "
969
+ module Foo
970
+ Bar = ::Class.new do
971
+ end
972
+
973
+ Baz = ::Module.new do
974
+ end
975
+ end
976
+ "
977
+ });
978
+ assert_no_local_diagnostics!(&context);
979
+
980
+ assert_definition_at!(&context, "1:1-7:4", Module, |foo| {
981
+ assert_definition_at!(&context, "2:3-3:6", Class, |bar| {
982
+ assert_definition_at!(&context, "5:3-6:6", Module, |baz| {
983
+ assert_def_name_eq!(&context, bar, "Bar");
984
+ assert_def_name_eq!(&context, baz, "Baz");
985
+ assert_eq!(foo.id(), bar.lexical_nesting_id().unwrap());
986
+ assert_eq!(foo.id(), baz.lexical_nesting_id().unwrap());
987
+ assert_eq!(foo.members()[0], bar.id());
988
+ assert_eq!(foo.members()[1], baz.id());
989
+ });
990
+ });
991
+ });
992
+ }
993
+
994
+ #[test]
995
+ fn index_anonymous_class_and_module_new() {
996
+ let context = index_source({
997
+ "
998
+ module Foo
999
+ Class.new do
1000
+ def bar; end
1001
+ end
1002
+
1003
+ Module.new do
1004
+ def baz; end
1005
+ end
1006
+ end
1007
+ "
1008
+ });
1009
+ assert_no_local_diagnostics!(&context);
1010
+
1011
+ assert_definition_at!(&context, "1:1-9:4", Module, |foo| {
1012
+ assert_definition_at!(&context, "2:3-4:6", Class, |anonymous| {
1013
+ assert_eq!(foo.id(), anonymous.lexical_nesting_id().unwrap());
1014
+
1015
+ assert_definition_at!(&context, "3:5-3:17", Method, |bar| {
1016
+ assert_eq!(anonymous.id(), bar.lexical_nesting_id().unwrap());
1017
+ });
1018
+ });
1019
+
1020
+ assert_definition_at!(&context, "6:3-8:6", Module, |anonymous| {
1021
+ assert_eq!(foo.id(), anonymous.lexical_nesting_id().unwrap());
1022
+
1023
+ assert_definition_at!(&context, "7:5-7:17", Method, |baz| {
1024
+ assert_eq!(anonymous.id(), baz.lexical_nesting_id().unwrap());
1025
+ });
1026
+ });
1027
+ });
1028
+ }
1029
+
1030
+ #[test]
1031
+ fn index_nested_class_and_module_new() {
1032
+ let context = index_source({
1033
+ "
1034
+ module Foo
1035
+ Class.new do
1036
+ Module.new do
1037
+ end
1038
+ end
1039
+ end
1040
+ "
1041
+ });
1042
+ assert_no_local_diagnostics!(&context);
1043
+
1044
+ assert_definition_at!(&context, "1:1-6:4", Module, |foo| {
1045
+ assert_definition_at!(&context, "2:3-5:6", Class, |anonymous_class| {
1046
+ assert_eq!(foo.id(), anonymous_class.lexical_nesting_id().unwrap());
1047
+
1048
+ assert_definition_at!(&context, "3:5-4:8", Module, |anonymous_module| {
1049
+ assert_eq!(foo.id(), anonymous_module.lexical_nesting_id().unwrap());
1050
+ });
1051
+ });
1052
+ });
1053
+ }
1054
+
1055
+ #[test]
1056
+ fn index_named_module_nested_inside_anonymous() {
1057
+ let context = index_source({
1058
+ "
1059
+ module Foo
1060
+ Class.new do
1061
+ module Bar
1062
+ end
1063
+ end
1064
+ end
1065
+ "
1066
+ });
1067
+ assert_no_local_diagnostics!(&context);
1068
+
1069
+ assert_definition_at!(&context, "1:1-6:4", Module, |foo| {
1070
+ assert_definition_at!(&context, "2:3-5:6", Class, |anonymous_class| {
1071
+ assert_eq!(foo.id(), anonymous_class.lexical_nesting_id().unwrap());
1072
+
1073
+ assert_definition_at!(&context, "3:5-4:8", Module, |bar| {
1074
+ assert_eq!(foo.id(), bar.lexical_nesting_id().unwrap());
1075
+ });
1076
+ });
1077
+ });
1078
+ }
1079
+
1080
+ #[test]
1081
+ fn index_anonymous_namespace_mixins() {
1082
+ let context = index_source({
1083
+ "
1084
+ module Foo
1085
+ Class.new do
1086
+ include Bar
1087
+ end
1088
+ end
1089
+ "
1090
+ });
1091
+ assert_no_local_diagnostics!(&context);
1092
+
1093
+ assert_definition_at!(&context, "1:1-5:4", Module, |foo| {
1094
+ assert_definition_at!(&context, "2:3-4:6", Class, |anonymous_class| {
1095
+ assert_eq!(foo.id(), anonymous_class.lexical_nesting_id().unwrap());
1096
+
1097
+ assert_def_mixins_eq!(&context, anonymous_class, Include, ["Bar"]);
1098
+ });
1099
+ });
1100
+ }
1101
+
1102
+ #[test]
1103
+ fn index_singleton_method_in_class_new() {
1104
+ let context = index_source({
1105
+ "
1106
+ module Foo
1107
+ A = Class.new do
1108
+ def self.bar
1109
+ end
1110
+ end
1111
+ end
1112
+ "
1113
+ });
1114
+ assert_no_local_diagnostics!(&context);
1115
+
1116
+ assert_definition_at!(&context, "3:5-4:8", Method, |bar| {
1117
+ let Receiver::SelfReceiver(def_id) = bar.receiver().as_ref().unwrap() else {
1118
+ panic!("Expected SelfReceiver for def self.bar in Class.new");
1119
+ };
1120
+ let def = context.graph().definitions().get(def_id).unwrap();
1121
+ let name_id = def.name_id().expect("Owner definition should have a name_id");
1122
+ let name_ref = context.graph().names().get(name_id).unwrap();
1123
+ assert_eq!(StringId::from("A"), *name_ref.str());
1124
+
1125
+ let nesting_name = context.graph().names().get(&name_ref.nesting().unwrap()).unwrap();
1126
+ assert_eq!(StringId::from("Foo"), *nesting_name.str());
1127
+ });
1128
+ }
1129
+
1130
+ #[test]
1131
+ fn index_class_variable_in_class_new() {
1132
+ let context = index_source({
1133
+ "
1134
+ module Foo
1135
+ A = Class.new do
1136
+ def bar
1137
+ @@var = 123
1138
+ end
1139
+ end
1140
+ end
1141
+ "
1142
+ });
1143
+ assert_no_local_diagnostics!(&context);
1144
+
1145
+ assert_definition_at!(&context, "1:1-7:4", Module, |foo| {
1146
+ assert_definition_at!(&context, "4:7-4:12", ClassVariable, |var| {
1147
+ assert_eq!(foo.id(), var.lexical_nesting_id().unwrap());
1148
+ });
1149
+ });
1150
+ }
1151
+
1152
+ #[test]
1153
+ fn index_singleton_method_in_anonymous_namespace() {
1154
+ let context = index_source({
1155
+ "
1156
+ module Foo
1157
+ Class.new do
1158
+ def self.bar
1159
+ end
1160
+ end
1161
+ end
1162
+ "
1163
+ });
1164
+ assert_no_local_diagnostics!(&context);
1165
+
1166
+ assert_definition_at!(&context, "3:5-4:8", Method, |bar| {
1167
+ let Receiver::SelfReceiver(def_id) = bar.receiver().as_ref().unwrap() else {
1168
+ panic!("Expected SelfReceiver for def self.bar in anonymous Class.new");
1169
+ };
1170
+ let def = context.graph().definitions().get(def_id).unwrap();
1171
+ let name_id = def.name_id().expect("Owner definition should have a name_id");
1172
+ let name_ref = context.graph().names().get(name_id).unwrap();
1173
+ let uri_id = UriId::from("file:///foo.rb");
1174
+ assert_eq!(StringId::from(&format!("{uri_id}:13<anonymous>")), *name_ref.str());
1175
+ assert!(name_ref.nesting().is_none());
1176
+ assert!(name_ref.parent_scope().is_none());
1177
+ });
1178
+ }
1179
+
1180
+ #[test]
1181
+ fn index_constant_alias_simple() {
1182
+ let context = index_source({
1183
+ "
1184
+ module Foo; end
1185
+ ALIAS1 = Foo
1186
+ ALIAS2 ||= Foo
1187
+ "
1188
+ });
1189
+
1190
+ assert_no_local_diagnostics!(&context);
1191
+
1192
+ assert_definition_at!(&context, "2:1-2:7", ConstantAlias, |def| {
1193
+ assert_def_name_eq!(&context, def, "ALIAS1");
1194
+ assert_name_path_eq!(&context, "Foo", *def.target_name_id());
1195
+ });
1196
+ assert_definition_at!(&context, "3:1-3:7", ConstantAlias, |def| {
1197
+ assert_def_name_eq!(&context, def, "ALIAS2");
1198
+ assert_name_path_eq!(&context, "Foo", *def.target_name_id());
1199
+ });
1200
+ }
1201
+
1202
+ #[test]
1203
+ fn index_constant_alias_to_path() {
1204
+ let context = index_source({
1205
+ "
1206
+ module Foo
1207
+ module Bar; end
1208
+ end
1209
+ ALIAS = Foo::Bar
1210
+ "
1211
+ });
1212
+
1213
+ assert_no_local_diagnostics!(&context);
1214
+
1215
+ assert_definition_at!(&context, "4:1-4:6", ConstantAlias, |def| {
1216
+ assert_def_name_eq!(&context, def, "ALIAS");
1217
+ assert_name_path_eq!(&context, "Foo::Bar", *def.target_name_id());
1218
+ });
1219
+
1220
+ assert_constant_references_eq!(&context, ["Foo", "Bar"]);
1221
+ }
1222
+
1223
+ #[test]
1224
+ fn index_constant_alias_nested() {
1225
+ let context = index_source({
1226
+ "
1227
+ module Foo; end
1228
+ module Bar
1229
+ MyFoo = Foo
1230
+ end
1231
+ "
1232
+ });
1233
+
1234
+ assert_no_local_diagnostics!(&context);
1235
+
1236
+ assert_definition_at!(&context, "2:1-4:4", Module, |bar_module_def| {
1237
+ assert_definition_at!(&context, "3:3-3:8", ConstantAlias, |def| {
1238
+ assert_def_name_eq!(&context, def, "MyFoo");
1239
+ assert_eq!(bar_module_def.id(), def.lexical_nesting_id().unwrap());
1240
+ });
1241
+ });
1242
+ }
1243
+
1244
+ #[test]
1245
+ fn index_scoped_constant_alias() {
1246
+ let context = index_source({
1247
+ "
1248
+ module Foo; end
1249
+ module Bar; end
1250
+ Bar::ALIAS = Foo
1251
+ "
1252
+ });
1253
+
1254
+ assert_no_local_diagnostics!(&context);
1255
+
1256
+ assert_definition_at!(&context, "3:6-3:11", ConstantAlias, |def| {
1257
+ assert_def_name_eq!(&context, def, "Bar::ALIAS");
1258
+ });
1259
+ }
1260
+
1261
+ #[test]
1262
+ fn index_chained_constant_alias() {
1263
+ let context = index_source({
1264
+ "
1265
+ module Target; end
1266
+ A = B = Target
1267
+ "
1268
+ });
1269
+
1270
+ assert_no_local_diagnostics!(&context);
1271
+
1272
+ assert_definition_at!(&context, "2:1-2:2", ConstantAlias, |def| {
1273
+ assert_def_name_eq!(&context, def, "A");
1274
+ assert_name_path_eq!(&context, "Target", *def.target_name_id());
1275
+ });
1276
+ assert_definition_at!(&context, "2:5-2:6", ConstantAlias, |def| {
1277
+ assert_def_name_eq!(&context, def, "B");
1278
+ assert_name_path_eq!(&context, "Target", *def.target_name_id());
1279
+ });
1280
+
1281
+ assert_constant_references_eq!(&context, ["Target"]);
1282
+ }
1283
+
1284
+ #[test]
1285
+ fn index_constant_alias_to_top_level_constant() {
1286
+ let context = index_source({
1287
+ "
1288
+ module Foo; end
1289
+ ALIAS = ::Foo
1290
+ "
1291
+ });
1292
+
1293
+ assert_no_local_diagnostics!(&context);
1294
+
1295
+ assert_definition_at!(&context, "2:1-2:6", ConstantAlias, |def| {
1296
+ assert_def_name_eq!(&context, def, "ALIAS");
1297
+ assert_name_path_eq!(&context, "Foo", *def.target_name_id());
1298
+ });
1299
+ }
1300
+
1301
+ #[test]
1302
+ fn index_constant_alias_chain() {
1303
+ let context = index_source({
1304
+ "
1305
+ module Foo; end
1306
+ ALIAS1 = Foo
1307
+ ALIAS2 = ALIAS1
1308
+ "
1309
+ });
1310
+
1311
+ assert_no_local_diagnostics!(&context);
1312
+
1313
+ assert_definition_at!(&context, "2:1-2:7", ConstantAlias, |def| {
1314
+ assert_def_name_eq!(&context, def, "ALIAS1");
1315
+ assert_name_path_eq!(&context, "Foo", *def.target_name_id());
1316
+ });
1317
+ assert_definition_at!(&context, "3:1-3:7", ConstantAlias, |def| {
1318
+ assert_def_name_eq!(&context, def, "ALIAS2");
1319
+ assert_name_path_eq!(&context, "ALIAS1", *def.target_name_id());
1320
+ });
1321
+ }
1322
+
1323
+ // Comments
1324
+
1325
+ #[test]
1326
+ fn index_comments_attached_to_definitions() {
1327
+ let context = index_source({
1328
+ "
1329
+ # Single comment
1330
+ class Single; end
1331
+
1332
+ # Multi-line comment 1
1333
+ # Multi-line comment 2
1334
+ # Multi-line comment 3
1335
+ module Multi; end
1336
+
1337
+ # Comment 1
1338
+ #
1339
+ # Comment 2
1340
+ class EmptyCommentLine; end
1341
+
1342
+ # Comment directly above (no gap)
1343
+ NoGap = 42
1344
+
1345
+ #: ()
1346
+ #| -> void
1347
+ def foo; end
1348
+
1349
+ # Comment with blank line
1350
+
1351
+ class BlankLine; end
1352
+
1353
+ # Too far away
1354
+
1355
+
1356
+ class NoComment; end
1357
+ "
1358
+ });
1359
+
1360
+ assert_no_local_diagnostics!(&context);
1361
+
1362
+ assert_definition_at!(&context, "2:1-2:18", Class, |def| {
1363
+ assert_def_name_eq!(&context, def, "Single");
1364
+ assert_def_comments_eq!(&context, def, ["# Single comment"]);
1365
+ });
1366
+
1367
+ assert_definition_at!(&context, "7:1-7:18", Module, |def| {
1368
+ assert_def_name_eq!(&context, def, "Multi");
1369
+ assert_def_comments_eq!(
1370
+ &context,
1371
+ def,
1372
+ [
1373
+ "# Multi-line comment 1",
1374
+ "# Multi-line comment 2",
1375
+ "# Multi-line comment 3"
1376
+ ]
1377
+ );
1378
+ });
1379
+
1380
+ assert_definition_at!(&context, "12:1-12:28", Class, |def| {
1381
+ assert_def_name_eq!(&context, def, "EmptyCommentLine");
1382
+ assert_def_comments_eq!(&context, def, ["# Comment 1", "#", "# Comment 2"]);
1383
+ });
1384
+
1385
+ assert_definition_at!(&context, "15:1-15:6", Constant, |def| {
1386
+ assert_def_name_eq!(&context, def, "NoGap");
1387
+ assert_def_comments_eq!(&context, def, ["# Comment directly above (no gap)"]);
1388
+ });
1389
+
1390
+ assert_definition_at!(&context, "19:1-19:13", Method, |def| {
1391
+ assert_def_str_eq!(&context, def, "foo()");
1392
+ assert_def_comments_eq!(&context, def, ["#: ()", "#| -> void"]);
1393
+ });
1394
+
1395
+ assert_definition_at!(&context, "23:1-23:21", Class, |def| {
1396
+ assert_def_name_eq!(&context, def, "BlankLine");
1397
+ assert_def_comments_eq!(&context, def, ["# Comment with blank line"]);
1398
+ });
1399
+
1400
+ assert_definition_at!(&context, "28:1-28:21", Class, |def| {
1401
+ assert_def_name_eq!(&context, def, "NoComment");
1402
+ assert!(def.comments().is_empty());
1403
+ });
1404
+ }
1405
+
1406
+ #[test]
1407
+ fn index_comments_indented_and_nested() {
1408
+ let context = index_source({
1409
+ "
1410
+ # Outer class
1411
+ class Outer
1412
+ # Inner class at 2 spaces
1413
+ class Inner
1414
+ # Deep class at 4 spaces
1415
+ class Deep; end
1416
+ end
1417
+
1418
+ # Another inner class
1419
+ # with multiple lines
1420
+ class AnotherInner; end
1421
+ end
1422
+ "
1423
+ });
1424
+
1425
+ assert_no_local_diagnostics!(&context);
1426
+
1427
+ assert_definition_at!(&context, "2:1-12:4", Class, |def| {
1428
+ assert_def_name_eq!(&context, def, "Outer");
1429
+ assert_def_comments_eq!(&context, def, ["# Outer class"]);
1430
+ });
1431
+
1432
+ assert_definition_at!(&context, "4:3-7:6", Class, |def| {
1433
+ assert_def_name_eq!(&context, def, "Inner");
1434
+ assert_def_comments_eq!(&context, def, ["# Inner class at 2 spaces"]);
1435
+ });
1436
+
1437
+ assert_definition_at!(&context, "6:5-6:20", Class, |def| {
1438
+ assert_def_name_eq!(&context, def, "Deep");
1439
+ assert_def_comments_eq!(&context, def, ["# Deep class at 4 spaces"]);
1440
+ });
1441
+
1442
+ assert_definition_at!(&context, "11:3-11:26", Class, |def| {
1443
+ assert_def_name_eq!(&context, def, "AnotherInner");
1444
+ assert_def_comments_eq!(&context, def, ["# Another inner class", "# with multiple lines"]);
1445
+ });
1446
+ }
1447
+
1448
+ #[test]
1449
+ fn index_comments_with_tags() {
1450
+ let context = index_source({
1451
+ "
1452
+ # @deprecated
1453
+ class Deprecated; end
1454
+
1455
+ class NotDeprecated; end
1456
+
1457
+ # Multi-line comment
1458
+ # @deprecated Use something else
1459
+ def deprecated_method; end
1460
+
1461
+ # Not @deprecated
1462
+ def not_deprecated_method; end
1463
+ "
1464
+ });
1465
+
1466
+ assert!(context.definition_at("2:1-2:22").is_deprecated());
1467
+ assert!(!context.definition_at("4:1-4:25").is_deprecated());
1468
+ assert!(context.definition_at("8:1-8:27").is_deprecated());
1469
+ assert!(!context.definition_at("11:1-11:31").is_deprecated());
1470
+ }
1471
+
1472
+ #[test]
1473
+ fn index_comments_attr_accessor() {
1474
+ let context = index_source({
1475
+ "
1476
+ class Foo
1477
+ # Comment
1478
+ attr_reader :foo
1479
+
1480
+ # Comment 1
1481
+ # Comment 2
1482
+ # Comment 3
1483
+ attr_writer :bar
1484
+
1485
+ # Comment 1
1486
+ # Comment 2
1487
+ # Comment 3
1488
+ attr_accessor :baz, :qux
1489
+
1490
+ # Comment
1491
+ attr :quux, true
1492
+ end
1493
+ "
1494
+ });
1495
+
1496
+ assert_no_local_diagnostics!(&context);
1497
+
1498
+ assert_definition_at!(&context, "3:16-3:19", AttrReader, |def| {
1499
+ assert_def_comments_eq!(&context, def, ["# Comment"]);
1500
+ });
1501
+
1502
+ assert_definition_at!(&context, "8:16-8:19", AttrWriter, |def| {
1503
+ assert_def_comments_eq!(&context, def, ["# Comment 1", "# Comment 2", "# Comment 3"]);
1504
+ });
1505
+
1506
+ assert_definition_at!(&context, "13:18-13:21", AttrAccessor, |def| {
1507
+ assert_def_comments_eq!(&context, def, ["# Comment 1", "# Comment 2", "# Comment 3"]);
1508
+ });
1509
+
1510
+ assert_definition_at!(&context, "13:24-13:27", AttrAccessor, |def| {
1511
+ assert_def_comments_eq!(&context, def, ["# Comment 1", "# Comment 2", "# Comment 3"]);
1512
+ });
1513
+
1514
+ assert_definition_at!(&context, "16:9-16:13", AttrAccessor, |def| {
1515
+ assert_def_comments_eq!(&context, def, ["# Comment"]);
1516
+ });
1517
+ }
1518
+
1519
+ #[test]
1520
+ fn index_comments_on_top_of_signature() {
1521
+ let context = index_source({
1522
+ "
1523
+ class Foo
1524
+ # Bar docs
1525
+ # are here
1526
+ sig { returns(Integer) }
1527
+ attr_reader :bar
1528
+
1529
+ # Baz docs
1530
+ # are in this other place
1531
+ sig do
1532
+ params(x: Integer).void
1533
+ end
1534
+ def baz(x); end
1535
+ end
1536
+ "
1537
+ });
1538
+
1539
+ assert_no_local_diagnostics!(&context);
1540
+
1541
+ assert_definition_at!(&context, "5:16-5:19", AttrReader, |def| {
1542
+ assert_def_comments_eq!(&context, def, ["# Bar docs", "# are here"]);
1543
+ });
1544
+
1545
+ assert_definition_at!(&context, "12:3-12:18", Method, |def| {
1546
+ assert_def_comments_eq!(&context, def, ["# Baz docs", "# are in this other place"]);
1547
+ });
1548
+ }
1549
+
1550
+ #[test]
1551
+ fn index_comments_on_top_of_multiple_attribute_signature() {
1552
+ let context = index_source({
1553
+ "
1554
+ class Foo
1555
+ # Docs
1556
+ sig { returns(Integer) }
1557
+ attr_reader :bar, :baz
1558
+ end
1559
+ "
1560
+ });
1561
+
1562
+ assert_no_local_diagnostics!(&context);
1563
+
1564
+ assert_definition_at!(&context, "4:16-4:19", AttrReader, |def| {
1565
+ assert_def_comments_eq!(&context, def, ["# Docs"]);
1566
+ });
1567
+
1568
+ assert_definition_at!(&context, "4:22-4:25", AttrReader, |def| {
1569
+ assert_def_comments_eq!(&context, def, ["# Docs"]);
1570
+ });
1571
+ }
1572
+
1573
+ #[test]
1574
+ fn index_comments_on_sig_without_runtime() {
1575
+ let context = index_source({
1576
+ "
1577
+ class Foo
1578
+ # Docs
1579
+ T::Sig::WithoutRuntime.sig { returns(Integer) }
1580
+ def bar; end
1581
+ end
1582
+ "
1583
+ });
1584
+
1585
+ assert_no_local_diagnostics!(&context);
1586
+
1587
+ assert_definition_at!(&context, "4:3-4:15", Method, |def| {
1588
+ assert_def_comments_eq!(&context, def, ["# Docs"]);
1589
+ });
1590
+ }
1591
+
1592
+ #[test]
1593
+ fn index_comments_blank_line_between_annotation_and_def() {
1594
+ let context = index_source({
1595
+ "
1596
+ class Foo
1597
+ # Docs
1598
+ sig { returns(Integer) }
1599
+
1600
+ def bar; end
1601
+ end
1602
+ "
1603
+ });
1604
+
1605
+ assert_no_local_diagnostics!(&context);
1606
+
1607
+ assert_definition_at!(&context, "5:3-5:15", Method, |def| {
1608
+ assert!(def.comments().is_empty());
1609
+ });
1610
+ }
1611
+
1612
+ #[test]
1613
+ fn index_double_line_between_comment_and_annotation() {
1614
+ let context = index_source({
1615
+ "
1616
+ class Foo
1617
+ # Docs for bar
1618
+
1619
+
1620
+ sig { params(x: Integer).void }
1621
+ def bar(x); end
1622
+ end
1623
+ "
1624
+ });
1625
+
1626
+ assert_no_local_diagnostics!(&context);
1627
+
1628
+ assert_definition_at!(&context, "6:3-6:18", Method, |def| {
1629
+ assert!(def.comments().is_empty());
1630
+ });
1631
+ }
1632
+
1633
+ #[test]
1634
+ fn index_line_between_comment_and_annotation() {
1635
+ let context = index_source({
1636
+ "
1637
+ class Foo
1638
+ # Docs for bar
1639
+
1640
+ sig { params(x: Integer).void }
1641
+ def bar(x); end
1642
+ end
1643
+ "
1644
+ });
1645
+
1646
+ assert_no_local_diagnostics!(&context);
1647
+
1648
+ assert_definition_at!(&context, "5:3-5:18", Method, |def| {
1649
+ assert_def_comments_eq!(&context, def, ["# Docs for bar"]);
1650
+ });
1651
+ }
1652
+
1653
+ #[test]
1654
+ fn index_anything_between_comment_and_annotation() {
1655
+ let context = index_source({
1656
+ "
1657
+ class Foo
1658
+ # Docs for bar
1659
+ sig { params(x: Integer).void }
1660
+ something_else
1661
+ def bar(x); end
1662
+ end
1663
+ "
1664
+ });
1665
+
1666
+ assert_no_local_diagnostics!(&context);
1667
+
1668
+ assert_definition_at!(&context, "5:3-5:18", Method, |def| {
1669
+ assert!(def.comments().is_empty());
1670
+ });
1671
+ }
1672
+
1673
+ #[test]
1674
+ fn index_comments_annotation_does_not_leak_through_other_code() {
1675
+ let context = index_source({
1676
+ "
1677
+ class Foo
1678
+ # Should not leak
1679
+ sig { returns(Integer) }
1680
+ include SomeModule
1681
+
1682
+ # Docs for bar
1683
+ def bar; end
1684
+ end
1685
+ "
1686
+ });
1687
+
1688
+ assert_no_local_diagnostics!(&context);
1689
+
1690
+ assert_definition_at!(&context, "7:3-7:15", Method, |def| {
1691
+ assert_def_comments_eq!(&context, def, ["# Docs for bar"]);
1692
+ });
1693
+ }
1694
+
1695
+ #[test]
1696
+ fn index_comments_decorator_above_private_def() {
1697
+ let context = index_source({
1698
+ "
1699
+ class Foo
1700
+ # Docs for foo
1701
+ sig { params(x: Integer).void }
1702
+ private def foo(x); end
1703
+
1704
+ # Docs for bar
1705
+ sig { returns(Integer) }
1706
+ private attr_reader :bar
1707
+ end
1708
+ "
1709
+ });
1710
+
1711
+ assert_no_local_diagnostics!(&context);
1712
+
1713
+ assert_definition_at!(&context, "4:11-4:26", Method, |def| {
1714
+ assert_def_comments_eq!(&context, def, ["# Docs for foo"]);
1715
+ });
1716
+
1717
+ assert_definition_at!(&context, "8:24-8:27", AttrReader, |def| {
1718
+ assert_def_comments_eq!(&context, def, ["# Docs for bar"]);
1719
+ });
1720
+ }
1721
+
1722
+ #[test]
1723
+ fn index_comments_visibility() {
1724
+ let context = index_source({
1725
+ "
1726
+ class Foo
1727
+ # Comment
1728
+ private def foo; end
1729
+
1730
+ # Comment
1731
+ protected def bar; end
1732
+
1733
+ # Comment
1734
+ public def baz; end
1735
+
1736
+ # Comment
1737
+ private attr_reader :qux
1738
+ end
1739
+ "
1740
+ });
1741
+
1742
+ assert_definition_at!(&context, "3:11-3:23", Method, |def| {
1743
+ assert_def_comments_eq!(&context, def, ["# Comment"]);
1744
+ });
1745
+
1746
+ assert_definition_at!(&context, "6:13-6:25", Method, |def| {
1747
+ assert_def_comments_eq!(&context, def, ["# Comment"]);
1748
+ });
1749
+
1750
+ assert_definition_at!(&context, "9:10-9:22", Method, |def| {
1751
+ assert_def_comments_eq!(&context, def, ["# Comment"]);
1752
+ });
1753
+
1754
+ assert_definition_at!(&context, "12:24-12:27", AttrReader, |def| {
1755
+ assert_def_comments_eq!(&context, def, ["# Comment"]);
1756
+ });
1757
+ }
1758
+
1759
+ #[test]
1760
+ fn constant_with_call_value_is_promotable() {
1761
+ let context = index_source("Foo = some_call");
1762
+
1763
+ assert_definition_at!(&context, "1:1-1:4", Constant, |def| {
1764
+ assert_promotable!(def);
1765
+ });
1766
+ }
1767
+
1768
+ #[test]
1769
+ fn constant_with_literal_value_is_not_promotable() {
1770
+ let context = index_source("FOO = 42");
1771
+
1772
+ assert_definition_at!(&context, "1:1-1:4", Constant, |def| {
1773
+ assert_not_promotable!(def);
1774
+ });
1775
+ }
1776
+
1777
+ #[test]
1778
+ fn constant_with_operator_call_is_not_promotable() {
1779
+ let context = index_source("FOO = 1 + 2");
1780
+
1781
+ assert_definition_at!(&context, "1:1-1:4", Constant, |def| {
1782
+ assert_not_promotable!(def);
1783
+ });
1784
+ }
1785
+
1786
+ #[test]
1787
+ fn constant_with_dot_call_is_promotable() {
1788
+ let context = index_source("Foo = Bar.new");
1789
+
1790
+ assert_definition_at!(&context, "1:1-1:4", Constant, |def| {
1791
+ assert_promotable!(def);
1792
+ });
1793
+ }
1794
+
1795
+ #[test]
1796
+ fn constant_with_colon_colon_call_is_promotable() {
1797
+ let context = index_source("Foo = Bar::new");
1798
+
1799
+ assert_definition_at!(&context, "1:1-1:4", Constant, |def| {
1800
+ assert_promotable!(def);
1801
+ });
1802
+ }
1803
+
1804
+ mod constant_tests {
1805
+ use super::*;
1806
+
1807
+ #[test]
1808
+ fn index_constant_write_node() {
1809
+ let context = index_source({
1810
+ "
1811
+ FOO = 1
1812
+
1813
+ class Foo
1814
+ FOO = 2
1815
+ end
1816
+ "
1817
+ });
1818
+
1819
+ assert_no_local_diagnostics!(&context);
1820
+ assert_eq!(context.graph().definitions().len(), 3);
1821
+
1822
+ assert_definition_at!(&context, "1:1-1:4", Constant, |def| {
1823
+ assert_def_name_eq!(&context, def, "FOO");
1824
+ assert!(def.lexical_nesting_id().is_none());
1825
+ });
1826
+
1827
+ assert_definition_at!(&context, "4:3-4:6", Constant, |def| {
1828
+ assert_def_name_eq!(&context, def, "FOO");
1829
+
1830
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
1831
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
1832
+ assert_eq!(parent_nesting.members()[0], def.id());
1833
+ });
1834
+ });
1835
+ }
1836
+
1837
+ #[test]
1838
+ fn index_constant_path_write_node() {
1839
+ let context = index_source({
1840
+ "
1841
+ FOO::BAR = 1
1842
+
1843
+ class Foo
1844
+ FOO::BAR = 2
1845
+ ::BAZ = 3
1846
+ end
1847
+ "
1848
+ });
1849
+
1850
+ assert_no_local_diagnostics!(&context);
1851
+ assert_eq!(context.graph().definitions().len(), 4);
1852
+
1853
+ assert_definition_at!(&context, "1:6-1:9", Constant, |def| {
1854
+ assert_def_name_eq!(&context, def, "FOO::BAR");
1855
+ assert!(def.lexical_nesting_id().is_none());
1856
+ });
1857
+
1858
+ assert_definition_at!(&context, "4:8-4:11", Constant, |def| {
1859
+ assert_def_name_eq!(&context, def, "FOO::BAR");
1860
+
1861
+ assert_definition_at!(&context, "3:1-6:4", Class, |parent_nesting| {
1862
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
1863
+ assert_eq!(parent_nesting.members()[0], def.id());
1864
+ });
1865
+ });
1866
+
1867
+ assert_definition_at!(&context, "5:5-5:8", Constant, |def| {
1868
+ assert_def_name_eq!(&context, def, "BAZ");
1869
+
1870
+ assert_definition_at!(&context, "3:1-6:4", Class, |parent_nesting| {
1871
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
1872
+ assert_eq!(parent_nesting.members()[1], def.id());
1873
+ });
1874
+ });
1875
+ }
1876
+
1877
+ #[test]
1878
+ fn index_constant_or_write_node() {
1879
+ let context = index_source({
1880
+ "
1881
+ FOO ||= 1
1882
+
1883
+ class Bar
1884
+ BAZ ||= 2
1885
+ end
1886
+ "
1887
+ });
1888
+
1889
+ assert_no_local_diagnostics!(&context);
1890
+ assert_eq!(context.graph().definitions().len(), 3);
1891
+
1892
+ assert_definition_at!(&context, "1:1-1:4", Constant, |def| {
1893
+ assert_def_name_eq!(&context, def, "FOO");
1894
+ assert!(def.lexical_nesting_id().is_none());
1895
+ });
1896
+
1897
+ assert_definition_at!(&context, "4:3-4:6", Constant, |def| {
1898
+ assert_def_name_eq!(&context, def, "BAZ");
1899
+
1900
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
1901
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
1902
+ assert_eq!(parent_nesting.members()[0], def.id());
1903
+ });
1904
+ });
1905
+
1906
+ assert_constant_references_eq!(&context, ["FOO", "BAZ"]);
1907
+ }
1908
+
1909
+ #[test]
1910
+ fn index_constant_path_or_write_node() {
1911
+ let context = index_source({
1912
+ "
1913
+ FOO::BAR ||= 1
1914
+
1915
+ class MyClass
1916
+ FOO::BAR ||= 2
1917
+ ::BAZ ||= 3
1918
+ end
1919
+ "
1920
+ });
1921
+
1922
+ assert_no_local_diagnostics!(&context);
1923
+ assert_eq!(context.graph().definitions().len(), 4);
1924
+
1925
+ assert_definition_at!(&context, "1:6-1:9", Constant, |def| {
1926
+ assert_def_name_eq!(&context, def, "FOO::BAR");
1927
+ assert!(def.lexical_nesting_id().is_none());
1928
+ });
1929
+
1930
+ assert_definition_at!(&context, "4:8-4:11", Constant, |def| {
1931
+ assert_def_name_eq!(&context, def, "FOO::BAR");
1932
+
1933
+ assert_definition_at!(&context, "3:1-6:4", Class, |parent_nesting| {
1934
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
1935
+ assert_eq!(parent_nesting.members()[0], def.id());
1936
+ });
1937
+ });
1938
+
1939
+ assert_definition_at!(&context, "5:5-5:8", Constant, |def| {
1940
+ assert_def_name_eq!(&context, def, "BAZ");
1941
+
1942
+ assert_definition_at!(&context, "3:1-6:4", Class, |parent_nesting| {
1943
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
1944
+ assert_eq!(parent_nesting.members()[1], def.id());
1945
+ });
1946
+ });
1947
+
1948
+ assert_constant_references_eq!(&context, ["FOO", "BAR", "FOO", "BAR", "BAZ"]);
1949
+ }
1950
+
1951
+ #[test]
1952
+ fn index_constant_multi_write_node() {
1953
+ let context = index_source({
1954
+ "
1955
+ FOO, BAR::BAZ = 1, 2
1956
+
1957
+ class Foo
1958
+ FOO, BAR::BAZ, ::BAZ = 3, 4, 5
1959
+ end
1960
+ "
1961
+ });
1962
+
1963
+ assert_no_local_diagnostics!(&context);
1964
+ assert_eq!(context.graph().definitions().len(), 6);
1965
+
1966
+ assert_definition_at!(&context, "1:1-1:4", Constant, |def| {
1967
+ assert_def_name_eq!(&context, def, "FOO");
1968
+ assert!(def.lexical_nesting_id().is_none());
1969
+ });
1970
+
1971
+ assert_definition_at!(&context, "1:6-1:14", Constant, |def| {
1972
+ assert_def_name_eq!(&context, def, "BAR::BAZ");
1973
+ });
1974
+
1975
+ assert_definition_at!(&context, "4:3-4:6", Constant, |def| {
1976
+ assert_def_name_eq!(&context, def, "FOO");
1977
+
1978
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
1979
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
1980
+ assert_eq!(parent_nesting.members()[0], def.id());
1981
+ });
1982
+ });
1983
+
1984
+ assert_definition_at!(&context, "4:8-4:16", Constant, |def| {
1985
+ assert_def_name_eq!(&context, def, "BAR::BAZ");
1986
+
1987
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
1988
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
1989
+ assert_eq!(parent_nesting.members()[1], def.id());
1990
+ });
1991
+ });
1992
+
1993
+ assert_definition_at!(&context, "4:18-4:23", Constant, |def| {
1994
+ assert_def_name_eq!(&context, def, "BAZ");
1995
+
1996
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
1997
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
1998
+ assert_eq!(parent_nesting.members()[2], def.id());
1999
+ });
2000
+ });
2001
+ }
2002
+ }
2003
+
2004
+ mod variable_tests {
2005
+ use super::*;
2006
+
2007
+ #[test]
2008
+ fn index_global_variable_definition() {
2009
+ let context = index_source({
2010
+ "
2011
+ $foo = 1
2012
+ $bar, $baz = 2, 3
2013
+
2014
+ class Foo
2015
+ $qux = 2
2016
+ end
2017
+
2018
+ $one &= 1
2019
+ $two &&= 1
2020
+ $three ||= 1
2021
+ "
2022
+ });
2023
+
2024
+ assert_no_local_diagnostics!(&context);
2025
+ assert_eq!(context.graph().definitions().len(), 8);
2026
+
2027
+ assert_definition_at!(&context, "1:1-1:5", GlobalVariable, |def| {
2028
+ assert_def_str_eq!(&context, def, "$foo");
2029
+ assert!(def.lexical_nesting_id().is_none());
2030
+ });
2031
+
2032
+ assert_definition_at!(&context, "2:1-2:5", GlobalVariable, |def| {
2033
+ assert_def_str_eq!(&context, def, "$bar");
2034
+ assert!(def.lexical_nesting_id().is_none());
2035
+ });
2036
+
2037
+ assert_definition_at!(&context, "2:7-2:11", GlobalVariable, |def| {
2038
+ assert_def_str_eq!(&context, def, "$baz");
2039
+ assert!(def.lexical_nesting_id().is_none());
2040
+ });
2041
+
2042
+ assert_definition_at!(&context, "5:3-5:7", GlobalVariable, |def| {
2043
+ assert_def_str_eq!(&context, def, "$qux");
2044
+
2045
+ assert_definition_at!(&context, "4:1-6:4", Class, |parent_nesting| {
2046
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
2047
+ assert_eq!(parent_nesting.members()[0], def.id());
2048
+ });
2049
+ });
2050
+
2051
+ assert_definition_at!(&context, "8:1-8:5", GlobalVariable, |def| {
2052
+ assert_def_str_eq!(&context, def, "$one");
2053
+ assert!(def.lexical_nesting_id().is_none());
2054
+ });
2055
+
2056
+ assert_definition_at!(&context, "9:1-9:5", GlobalVariable, |def| {
2057
+ assert_def_str_eq!(&context, def, "$two");
2058
+ assert!(def.lexical_nesting_id().is_none());
2059
+ });
2060
+
2061
+ assert_definition_at!(&context, "10:1-10:7", GlobalVariable, |def| {
2062
+ assert_def_str_eq!(&context, def, "$three");
2063
+ assert!(def.lexical_nesting_id().is_none());
2064
+ });
2065
+ }
2066
+
2067
+ #[test]
2068
+ fn index_instance_variable_definition() {
2069
+ let context = index_source({
2070
+ "
2071
+ @foo = 1
2072
+
2073
+ class Foo
2074
+ @bar = 2
2075
+ @baz, @qux = 3, 4
2076
+ end
2077
+
2078
+ @bar &= 5
2079
+ @baz &&= 6
2080
+ @qux ||= 7
2081
+
2082
+ class Bar
2083
+ @foo &= 8
2084
+ @bar &&= 9
2085
+ @baz ||= 10
2086
+ end
2087
+ "
2088
+ });
2089
+
2090
+ assert_no_local_diagnostics!(&context);
2091
+
2092
+ assert_definition_at!(&context, "1:1-1:5", InstanceVariable, |def| {
2093
+ assert_def_str_eq!(&context, def, "@foo");
2094
+ assert!(def.lexical_nesting_id().is_none());
2095
+ });
2096
+
2097
+ assert_definition_at!(&context, "3:1-6:4", Class, |foo_class_def| {
2098
+ assert_definition_at!(&context, "4:3-4:7", InstanceVariable, |def| {
2099
+ assert_def_str_eq!(&context, def, "@bar");
2100
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
2101
+ assert_eq!(foo_class_def.members()[0], def.id());
2102
+ });
2103
+
2104
+ assert_definition_at!(&context, "5:3-5:7", InstanceVariable, |def| {
2105
+ assert_def_str_eq!(&context, def, "@baz");
2106
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
2107
+ assert_eq!(foo_class_def.members()[1], def.id());
2108
+ });
2109
+
2110
+ assert_definition_at!(&context, "5:9-5:13", InstanceVariable, |def| {
2111
+ assert_def_str_eq!(&context, def, "@qux");
2112
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
2113
+ assert_eq!(foo_class_def.members()[2], def.id());
2114
+ });
2115
+ });
2116
+
2117
+ assert_definition_at!(&context, "8:1-8:5", InstanceVariable, |def| {
2118
+ assert_def_str_eq!(&context, def, "@bar");
2119
+ assert!(def.lexical_nesting_id().is_none());
2120
+ });
2121
+
2122
+ assert_definition_at!(&context, "9:1-9:5", InstanceVariable, |def| {
2123
+ assert_def_str_eq!(&context, def, "@baz");
2124
+ assert!(def.lexical_nesting_id().is_none());
2125
+ });
2126
+
2127
+ assert_definition_at!(&context, "10:1-10:5", InstanceVariable, |def| {
2128
+ assert_def_str_eq!(&context, def, "@qux");
2129
+ assert!(def.lexical_nesting_id().is_none());
2130
+ });
2131
+
2132
+ assert_definition_at!(&context, "12:1-16:4", Class, |bar_class_def| {
2133
+ assert_definition_at!(&context, "13:3-13:7", InstanceVariable, |def| {
2134
+ assert_def_str_eq!(&context, def, "@foo");
2135
+ assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2136
+ assert_eq!(bar_class_def.members()[0], def.id());
2137
+ });
2138
+
2139
+ assert_definition_at!(&context, "14:3-14:7", InstanceVariable, |def| {
2140
+ assert_def_str_eq!(&context, def, "@bar");
2141
+ assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2142
+ assert_eq!(bar_class_def.members()[1], def.id());
2143
+ });
2144
+
2145
+ assert_definition_at!(&context, "15:3-15:7", InstanceVariable, |def| {
2146
+ assert_def_str_eq!(&context, def, "@baz");
2147
+ assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2148
+ assert_eq!(bar_class_def.members()[2], def.id());
2149
+ });
2150
+ });
2151
+ }
2152
+
2153
+ #[test]
2154
+ fn index_class_instance_variable() {
2155
+ let context = index_source({
2156
+ "
2157
+ class Foo
2158
+ @foo = 0
2159
+
2160
+ class << self
2161
+ @bar = 1
2162
+ end
2163
+ end
2164
+ "
2165
+ });
2166
+
2167
+ assert_no_local_diagnostics!(&context);
2168
+
2169
+ assert_definition_at!(&context, "1:1-7:4", Class, |foo_class_def| {
2170
+ assert_definition_at!(&context, "2:3-2:7", InstanceVariable, |foo_var_def| {
2171
+ assert_def_str_eq!(&context, foo_var_def, "@foo");
2172
+ assert_eq!(foo_class_def.id(), foo_var_def.lexical_nesting_id().unwrap());
2173
+ });
2174
+
2175
+ assert_definition_at!(&context, "4:3-6:6", SingletonClass, |foo_singleton_def| {
2176
+ assert_definition_at!(&context, "5:5-5:9", InstanceVariable, |bar_var_def| {
2177
+ assert_def_str_eq!(&context, bar_var_def, "@bar");
2178
+ assert_eq!(foo_singleton_def.id(), bar_var_def.lexical_nesting_id().unwrap());
2179
+ });
2180
+ });
2181
+ });
2182
+ }
2183
+
2184
+ #[test]
2185
+ fn index_instance_variable_inside_methods_stay_instance_variable() {
2186
+ let context = index_source({
2187
+ "
2188
+ class Foo
2189
+ def initialize
2190
+ @bar = 1
2191
+ end
2192
+
2193
+ def self.class_method
2194
+ @baz = 2
2195
+ end
2196
+
2197
+ class << self
2198
+ def singleton_method
2199
+ @qux = 3
2200
+ end
2201
+ end
2202
+ end
2203
+ "
2204
+ });
2205
+
2206
+ assert_no_local_diagnostics!(&context);
2207
+
2208
+ assert_definition_at!(&context, "3:5-3:9", InstanceVariable, |def| {
2209
+ assert_def_str_eq!(&context, def, "@bar");
2210
+ });
2211
+
2212
+ assert_definition_at!(&context, "7:5-7:9", InstanceVariable, |def| {
2213
+ assert_def_str_eq!(&context, def, "@baz");
2214
+ });
2215
+
2216
+ assert_definition_at!(&context, "12:7-12:11", InstanceVariable, |def| {
2217
+ assert_def_str_eq!(&context, def, "@qux");
2218
+ });
2219
+ }
2220
+
2221
+ #[test]
2222
+ fn index_instance_variable_in_method_with_non_self_receiver() {
2223
+ let context = index_source({
2224
+ "
2225
+ class Foo
2226
+ def String.bar
2227
+ @var = 123
2228
+ end
2229
+ end
2230
+ "
2231
+ });
2232
+
2233
+ assert_no_local_diagnostics!(&context);
2234
+
2235
+ // The instance variable is associated with the singleton class of String.
2236
+ // During indexing, we can't know what String resolves to because we haven't
2237
+ // resolved constants yet. The lexical nesting is the method definition.
2238
+ assert_definition_at!(&context, "1:1-5:4", Class, |_foo_class_def| {
2239
+ assert_definition_at!(&context, "2:3-4:6", Method, |method_def| {
2240
+ assert_definition_at!(&context, "3:5-3:9", InstanceVariable, |var_def| {
2241
+ assert_def_str_eq!(&context, var_def, "@var");
2242
+ // The lexical nesting of the ivar is the method
2243
+ assert_eq!(method_def.id(), var_def.lexical_nesting_id().unwrap());
2244
+ });
2245
+ });
2246
+ });
2247
+ }
2248
+
2249
+ #[test]
2250
+ fn index_class_variable_definition() {
2251
+ let context = index_source({
2252
+ "
2253
+ @@foo = 1
2254
+
2255
+ class Foo
2256
+ @@bar = 2
2257
+ @@baz, @@qux = 3, 4
2258
+ end
2259
+
2260
+ @@bar &= 5
2261
+ @@baz &&= 6
2262
+ @@qux ||= 7
2263
+
2264
+ class Bar
2265
+ @@foo &= 1
2266
+ @@bar &&= 2
2267
+ @@baz ||= 3
2268
+
2269
+ def set_foo
2270
+ @@foo = 4
2271
+ end
2272
+ end
2273
+ "
2274
+ });
2275
+
2276
+ // This is actually not allowed in Ruby and will raise a runtime error
2277
+ // But we should still index it so we can insert a diagnostic for it
2278
+ assert_no_local_diagnostics!(&context);
2279
+
2280
+ assert_definition_at!(&context, "1:1-1:6", ClassVariable, |def| {
2281
+ assert_def_str_eq!(&context, def, "@@foo");
2282
+ assert!(def.lexical_nesting_id().is_none());
2283
+ });
2284
+
2285
+ assert_definition_at!(&context, "3:1-6:4", Class, |foo_class_def| {
2286
+ assert_definition_at!(&context, "4:3-4:8", ClassVariable, |def| {
2287
+ assert_def_str_eq!(&context, def, "@@bar");
2288
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
2289
+ assert_eq!(foo_class_def.members()[0], def.id());
2290
+ });
2291
+
2292
+ assert_definition_at!(&context, "5:3-5:8", ClassVariable, |def| {
2293
+ assert_def_str_eq!(&context, def, "@@baz");
2294
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
2295
+ assert_eq!(foo_class_def.members()[1], def.id());
2296
+ });
2297
+
2298
+ assert_definition_at!(&context, "5:10-5:15", ClassVariable, |def| {
2299
+ assert_def_str_eq!(&context, def, "@@qux");
2300
+ assert_eq!(foo_class_def.id(), def.lexical_nesting_id().unwrap());
2301
+ assert_eq!(foo_class_def.members()[2], def.id());
2302
+ });
2303
+ });
2304
+
2305
+ assert_definition_at!(&context, "8:1-8:6", ClassVariable, |def| {
2306
+ assert_def_str_eq!(&context, def, "@@bar");
2307
+ assert!(def.lexical_nesting_id().is_none());
2308
+ });
2309
+
2310
+ assert_definition_at!(&context, "9:1-9:6", ClassVariable, |def| {
2311
+ assert_def_str_eq!(&context, def, "@@baz");
2312
+ assert!(def.lexical_nesting_id().is_none());
2313
+ });
2314
+
2315
+ assert_definition_at!(&context, "10:1-10:6", ClassVariable, |def| {
2316
+ assert_def_str_eq!(&context, def, "@@qux");
2317
+ assert!(def.lexical_nesting_id().is_none());
2318
+ });
2319
+
2320
+ assert_definition_at!(&context, "12:1-20:4", Class, |bar_class_def| {
2321
+ assert_definition_at!(&context, "13:3-13:8", ClassVariable, |def| {
2322
+ assert_def_str_eq!(&context, def, "@@foo");
2323
+ assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2324
+ assert_eq!(bar_class_def.members()[0], def.id());
2325
+ });
2326
+
2327
+ assert_definition_at!(&context, "14:3-14:8", ClassVariable, |def| {
2328
+ assert_def_str_eq!(&context, def, "@@bar");
2329
+ assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2330
+ assert_eq!(bar_class_def.members()[1], def.id());
2331
+ });
2332
+
2333
+ assert_definition_at!(&context, "15:3-15:8", ClassVariable, |def| {
2334
+ assert_def_str_eq!(&context, def, "@@baz");
2335
+ assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2336
+ assert_eq!(bar_class_def.members()[2], def.id());
2337
+ });
2338
+
2339
+ // Method `set_foo` is members()[3], class variable inside method is members()[4]
2340
+ assert_definition_at!(&context, "18:5-18:10", ClassVariable, |def| {
2341
+ assert_def_str_eq!(&context, def, "@@foo");
2342
+ assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2343
+ assert_eq!(bar_class_def.members()[4], def.id());
2344
+ });
2345
+ });
2346
+ }
2347
+
2348
+ #[test]
2349
+ fn index_class_variable_in_singleton_class_definition() {
2350
+ let context = index_source({
2351
+ "
2352
+ class Foo
2353
+ class << self
2354
+ @@var = 1
2355
+ end
2356
+ end
2357
+ "
2358
+ });
2359
+
2360
+ assert_no_local_diagnostics!(&context);
2361
+
2362
+ // During indexing, lexical_nesting_id is the actual enclosing scope (singleton class).
2363
+ // The resolution phase handles bypassing singleton classes for class variable ownership.
2364
+ assert_definition_at!(&context, "2:3-4:6", SingletonClass, |singleton_class| {
2365
+ assert_definition_at!(&context, "3:5-3:10", ClassVariable, |def| {
2366
+ assert_def_str_eq!(&context, def, "@@var");
2367
+ assert_eq!(Some(singleton_class.id()), *def.lexical_nesting_id());
2368
+ });
2369
+ });
2370
+ }
2371
+
2372
+ #[test]
2373
+ fn index_class_variable_in_nested_singleton_class_definition() {
2374
+ let context = index_source({
2375
+ "
2376
+ class Foo
2377
+ class << self
2378
+ class << self
2379
+ @@var = 1
2380
+ end
2381
+ end
2382
+ end
2383
+ "
2384
+ });
2385
+
2386
+ assert_no_local_diagnostics!(&context);
2387
+
2388
+ // During indexing, lexical_nesting_id is the actual enclosing scope (innermost singleton class).
2389
+ // The resolution phase handles bypassing singleton classes for class variable ownership.
2390
+ assert_definition_at!(&context, "3:5-5:8", SingletonClass, |nested_singleton| {
2391
+ assert_definition_at!(&context, "4:7-4:12", ClassVariable, |def| {
2392
+ assert_def_str_eq!(&context, def, "@@var");
2393
+ assert_eq!(Some(nested_singleton.id()), *def.lexical_nesting_id());
2394
+ });
2395
+ });
2396
+ }
2397
+
2398
+ #[test]
2399
+ fn index_class_variable_in_singleton_method_definition() {
2400
+ let context = index_source({
2401
+ "
2402
+ class Foo
2403
+ def self.bar
2404
+ @@var = 1
2405
+ end
2406
+ end
2407
+ "
2408
+ });
2409
+
2410
+ assert_no_local_diagnostics!(&context);
2411
+
2412
+ assert_definition_at!(&context, "1:1-5:4", Class, |class_def| {
2413
+ assert_definition_at!(&context, "3:5-3:10", ClassVariable, |def| {
2414
+ assert_def_str_eq!(&context, def, "@@var");
2415
+ assert_eq!(Some(class_def.id()), def.lexical_nesting_id().clone());
2416
+ });
2417
+ });
2418
+ }
2419
+ }
2420
+
2421
+ mod class_and_module_tests {
2422
+ use super::*;
2423
+
2424
+ #[test]
2425
+ fn index_class_node() {
2426
+ let context = index_source({
2427
+ "
2428
+ class Foo
2429
+ class Bar
2430
+ class Baz; end
2431
+ end
2432
+ end
2433
+ "
2434
+ });
2435
+
2436
+ assert_no_local_diagnostics!(&context);
2437
+ assert_eq!(context.graph().definitions().len(), 3);
2438
+
2439
+ assert_definition_at!(&context, "1:1-5:4", Class, |def| {
2440
+ assert_def_name_eq!(&context, def, "Foo");
2441
+ assert_def_name_offset_eq!(&context, def, "1:7-1:10");
2442
+ assert!(def.superclass_ref().is_none());
2443
+ assert_eq!(1, def.members().len());
2444
+ assert!(def.lexical_nesting_id().is_none());
2445
+ });
2446
+
2447
+ assert_definition_at!(&context, "2:3-4:6", Class, |def| {
2448
+ assert_def_name_eq!(&context, def, "Bar");
2449
+ assert_def_name_offset_eq!(&context, def, "2:9-2:12");
2450
+ assert!(def.superclass_ref().is_none());
2451
+ assert_eq!(1, def.members().len());
2452
+
2453
+ assert_definition_at!(&context, "1:1-5:4", Class, |parent_nesting| {
2454
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
2455
+ assert_eq!(parent_nesting.members()[0], def.id());
2456
+ });
2457
+ });
2458
+
2459
+ assert_definition_at!(&context, "3:5-3:19", Class, |def| {
2460
+ assert_def_name_eq!(&context, def, "Baz");
2461
+ assert_def_name_offset_eq!(&context, def, "3:11-3:14");
2462
+ assert!(def.superclass_ref().is_none());
2463
+ assert!(def.members().is_empty());
2464
+
2465
+ assert_definition_at!(&context, "2:3-4:6", Class, |parent_nesting| {
2466
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
2467
+ assert_eq!(parent_nesting.members()[0], def.id());
2468
+ });
2469
+ });
2470
+ }
2471
+
2472
+ #[test]
2473
+ fn index_class_node_with_qualified_name() {
2474
+ let context = index_source({
2475
+ "
2476
+ class Foo::Bar
2477
+ class Baz::Qux
2478
+ class ::Quuux; end
2479
+ end
2480
+ end
2481
+ "
2482
+ });
2483
+
2484
+ assert_no_local_diagnostics!(&context);
2485
+ assert_eq!(context.graph().definitions().len(), 3);
2486
+
2487
+ assert_definition_at!(&context, "1:1-5:4", Class, |def| {
2488
+ assert_def_name_eq!(&context, def, "Foo::Bar");
2489
+ assert_def_name_offset_eq!(&context, def, "1:12-1:15");
2490
+ assert!(def.superclass_ref().is_none());
2491
+ assert!(def.lexical_nesting_id().is_none());
2492
+ assert_eq!(1, def.members().len());
2493
+ });
2494
+
2495
+ assert_definition_at!(&context, "2:3-4:6", Class, |def| {
2496
+ assert_def_name_eq!(&context, def, "Baz::Qux");
2497
+ assert_def_name_offset_eq!(&context, def, "2:14-2:17");
2498
+ assert!(def.superclass_ref().is_none());
2499
+ assert_eq!(1, def.members().len());
2500
+
2501
+ assert_definition_at!(&context, "1:1-5:4", Class, |parent_nesting| {
2502
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
2503
+ assert_eq!(parent_nesting.members()[0], def.id());
2504
+ });
2505
+ });
2506
+
2507
+ assert_definition_at!(&context, "3:5-3:23", Class, |def| {
2508
+ assert_def_name_eq!(&context, def, "Quuux");
2509
+ assert_def_name_offset_eq!(&context, def, "3:13-3:18");
2510
+ assert!(def.superclass_ref().is_none());
2511
+ assert!(def.members().is_empty());
2512
+
2513
+ assert_definition_at!(&context, "2:3-4:6", Class, |parent_nesting| {
2514
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
2515
+ assert_eq!(parent_nesting.members()[0], def.id());
2516
+ });
2517
+ });
2518
+ }
2519
+
2520
+ #[test]
2521
+ fn index_class_with_dynamic_names() {
2522
+ let context = index_source({
2523
+ "
2524
+ class foo::Bar
2525
+ end
2526
+ "
2527
+ });
2528
+
2529
+ assert_local_diagnostics_eq!(
2530
+ &context,
2531
+ ["dynamic-constant-reference: Dynamic constant reference (1:7-1:10)"]
2532
+ );
2533
+ assert!(context.graph().definitions().is_empty());
2534
+ }
2535
+
2536
+ #[test]
2537
+ fn index_module_node() {
2538
+ let context = index_source({
2539
+ "
2540
+ module Foo
2541
+ module Bar
2542
+ module Baz; end
2543
+ end
2544
+ end
2545
+ "
2546
+ });
2547
+
2548
+ assert_no_local_diagnostics!(&context);
2549
+ assert_eq!(context.graph().definitions().len(), 3);
2550
+
2551
+ assert_definition_at!(&context, "1:1-5:4", Module, |def| {
2552
+ assert_def_name_eq!(&context, def, "Foo");
2553
+ assert_def_name_offset_eq!(&context, def, "1:8-1:11");
2554
+ assert_eq!(1, def.members().len());
2555
+ assert!(def.lexical_nesting_id().is_none());
2556
+ });
2557
+
2558
+ assert_definition_at!(&context, "2:3-4:6", Module, |def| {
2559
+ assert_def_name_eq!(&context, def, "Bar");
2560
+ assert_def_name_offset_eq!(&context, def, "2:10-2:13");
2561
+ assert_eq!(1, def.members().len());
2562
+
2563
+ assert_definition_at!(&context, "1:1-5:4", Module, |parent_nesting| {
2564
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
2565
+ assert_eq!(parent_nesting.members()[0], def.id());
2566
+ });
2567
+ });
2568
+
2569
+ assert_definition_at!(&context, "3:5-3:20", Module, |def| {
2570
+ assert_def_name_eq!(&context, def, "Baz");
2571
+ assert_def_name_offset_eq!(&context, def, "3:12-3:15");
2572
+
2573
+ assert_definition_at!(&context, "2:3-4:6", Module, |parent_nesting| {
2574
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
2575
+ assert_eq!(parent_nesting.members()[0], def.id());
2576
+ });
2577
+ });
2578
+ }
2579
+
2580
+ #[test]
2581
+ fn index_module_node_with_qualified_name() {
2582
+ let context = index_source({
2583
+ "
2584
+ module Foo::Bar
2585
+ module Baz::Qux
2586
+ module ::Quuux; end
2587
+ end
2588
+ end
2589
+ "
2590
+ });
2591
+
2592
+ assert_no_local_diagnostics!(&context);
2593
+ assert_eq!(context.graph().definitions().len(), 3);
2594
+
2595
+ assert_definition_at!(&context, "1:1-5:4", Module, |def| {
2596
+ assert_def_name_eq!(&context, def, "Foo::Bar");
2597
+ assert_def_name_offset_eq!(&context, def, "1:13-1:16");
2598
+ assert_eq!(1, def.members().len());
2599
+ assert!(def.lexical_nesting_id().is_none());
2600
+ });
2601
+
2602
+ assert_definition_at!(&context, "2:3-4:6", Module, |def| {
2603
+ assert_def_name_eq!(&context, def, "Baz::Qux");
2604
+ assert_def_name_offset_eq!(&context, def, "2:15-2:18");
2605
+ assert_eq!(1, def.members().len());
2606
+
2607
+ assert_definition_at!(&context, "1:1-5:4", Module, |parent_nesting| {
2608
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
2609
+ assert_eq!(parent_nesting.members()[0], def.id());
2610
+ });
2611
+ });
2612
+
2613
+ assert_definition_at!(&context, "3:5-3:24", Module, |def| {
2614
+ assert_def_name_eq!(&context, def, "Quuux");
2615
+ assert_def_name_offset_eq!(&context, def, "3:14-3:19");
2616
+
2617
+ assert_definition_at!(&context, "2:3-4:6", Module, |parent_nesting| {
2618
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
2619
+ assert_eq!(parent_nesting.members()[0], def.id());
2620
+ });
2621
+ });
2622
+ }
2623
+
2624
+ #[test]
2625
+ fn index_module_with_dynamic_names() {
2626
+ let context = index_source({
2627
+ "
2628
+ module foo::Bar
2629
+ end
2630
+ "
2631
+ });
2632
+
2633
+ assert_local_diagnostics_eq!(
2634
+ &context,
2635
+ ["dynamic-constant-reference: Dynamic constant reference (1:8-1:11)"]
2636
+ );
2637
+ assert!(context.graph().definitions().is_empty());
2638
+ }
2639
+ }
2640
+
2641
+ mod method_tests {
2642
+ use super::*;
2643
+
2644
+ /// Asserts that a parameter matches the expected kind.
2645
+ ///
2646
+ /// Usage:
2647
+ /// - `assert_parameter!(parameter, RequiredPositional, |param| { assert_string_eq!(context, param.str(), "a"); })`
2648
+ /// - `assert_parameter!(parameter, OptionalPositional, |param| { assert_string_eq!(context, param.str(), "b"); })`
2649
+ macro_rules! assert_parameter {
2650
+ ($expr:expr, $variant:ident, |$param:ident| $body:block) => {
2651
+ match $expr {
2652
+ Parameter::$variant($param) => $body,
2653
+ _ => panic!(
2654
+ "parameter kind mismatch: expected `{}`, got `{:?}`",
2655
+ stringify!($variant),
2656
+ $expr
2657
+ ),
2658
+ }
2659
+ };
2660
+ }
2661
+
2662
+ #[test]
2663
+ fn index_def_node() {
2664
+ let context = index_source({
2665
+ "
2666
+ def foo; end
2667
+
2668
+ class Foo
2669
+ def bar; end
2670
+ def self.baz; end
2671
+ end
2672
+
2673
+ class Bar
2674
+ def Foo.quz; end
2675
+ end
2676
+ "
2677
+ });
2678
+
2679
+ assert_no_local_diagnostics!(&context);
2680
+ assert_eq!(context.graph().definitions().len(), 6);
2681
+
2682
+ assert_definition_at!(&context, "1:1-1:13", Method, |def| {
2683
+ assert_def_str_eq!(&context, def, "foo()");
2684
+ assert_simple_signature!(def, |params| {
2685
+ assert_eq!(params.len(), 0);
2686
+ });
2687
+ assert!(def.receiver().is_none());
2688
+ assert!(def.lexical_nesting_id().is_none());
2689
+ });
2690
+
2691
+ assert_definition_at!(&context, "3:1-6:4", Class, |foo_class_def| {
2692
+ assert_definition_at!(&context, "4:3-4:15", Method, |bar_def| {
2693
+ assert_def_str_eq!(&context, bar_def, "bar()");
2694
+ assert_simple_signature!(bar_def, |params| {
2695
+ assert_eq!(params.len(), 0);
2696
+ });
2697
+ assert!(bar_def.receiver().is_none());
2698
+ assert_eq!(foo_class_def.id(), bar_def.lexical_nesting_id().unwrap());
2699
+ assert_eq!(foo_class_def.members()[0], bar_def.id());
2700
+ });
2701
+
2702
+ assert_definition_at!(&context, "5:3-5:20", Method, |baz_def| {
2703
+ assert_def_str_eq!(&context, baz_def, "baz()");
2704
+ assert_simple_signature!(baz_def, |params| {
2705
+ assert_eq!(params.len(), 0);
2706
+ });
2707
+ assert_method_has_receiver!(&context, baz_def, "Foo");
2708
+ assert_eq!(foo_class_def.id(), baz_def.lexical_nesting_id().unwrap());
2709
+ assert_eq!(foo_class_def.members()[1], baz_def.id());
2710
+ });
2711
+ });
2712
+
2713
+ assert_definition_at!(&context, "8:1-10:4", Class, |bar_class_def| {
2714
+ assert_def_name_eq!(&context, bar_class_def, "Bar");
2715
+
2716
+ assert_definition_at!(&context, "9:3-9:19", Method, |quz_def| {
2717
+ assert_def_str_eq!(&context, quz_def, "quz()");
2718
+ assert_simple_signature!(quz_def, |params| {
2719
+ assert_eq!(params.len(), 0);
2720
+ });
2721
+ assert_method_has_receiver!(&context, quz_def, "Foo");
2722
+ assert_eq!(bar_class_def.id(), quz_def.lexical_nesting_id().unwrap());
2723
+ });
2724
+ });
2725
+ }
2726
+
2727
+ #[test]
2728
+ fn do_not_index_def_node_with_dynamic_receiver() {
2729
+ let context = index_source({
2730
+ "
2731
+ def foo.bar; end
2732
+ "
2733
+ });
2734
+
2735
+ assert_local_diagnostics_eq!(
2736
+ &context,
2737
+ ["dynamic-singleton-definition: Dynamic receiver for singleton method definition (1:1-1:17)"]
2738
+ );
2739
+ assert_eq!(context.graph().definitions().len(), 0);
2740
+ assert_method_references_eq!(&context, ["foo"]);
2741
+ }
2742
+
2743
+ #[test]
2744
+ fn index_def_node_with_parameters() {
2745
+ let context = index_source({
2746
+ "
2747
+ def foo(a, b = 42, *c, d, e:, g: 42, **i, &j); end
2748
+ "
2749
+ });
2750
+
2751
+ assert_no_local_diagnostics!(&context);
2752
+
2753
+ assert_definition_at!(&context, "1:1-1:51", Method, |def| {
2754
+ assert_simple_signature!(def, |params| {
2755
+ assert_eq!(params.len(), 8);
2756
+
2757
+ assert_parameter!(&params[0], RequiredPositional, |param| {
2758
+ assert_string_eq!(context, param.str(), "a");
2759
+ });
2760
+
2761
+ assert_parameter!(&params[1], OptionalPositional, |param| {
2762
+ assert_string_eq!(context, param.str(), "b");
2763
+ });
2764
+
2765
+ assert_parameter!(&params[2], RestPositional, |param| {
2766
+ assert_string_eq!(context, param.str(), "c");
2767
+ });
2768
+
2769
+ assert_parameter!(&params[3], Post, |param| {
2770
+ assert_string_eq!(context, param.str(), "d");
2771
+ });
2772
+
2773
+ assert_parameter!(&params[4], RequiredKeyword, |param| {
2774
+ assert_string_eq!(context, param.str(), "e");
2775
+ });
2776
+
2777
+ assert_parameter!(&params[5], OptionalKeyword, |param| {
2778
+ assert_string_eq!(context, param.str(), "g");
2779
+ });
2780
+
2781
+ assert_parameter!(&params[6], RestKeyword, |param| {
2782
+ assert_string_eq!(context, param.str(), "i");
2783
+ });
2784
+
2785
+ assert_parameter!(&params[7], Block, |param| {
2786
+ assert_string_eq!(context, param.str(), "j");
2787
+ });
2788
+ });
2789
+ });
2790
+ }
2791
+
2792
+ #[test]
2793
+ fn index_def_node_with_forward_parameters() {
2794
+ let context = index_source({
2795
+ "
2796
+ def foo(...); end
2797
+ "
2798
+ });
2799
+
2800
+ assert_no_local_diagnostics!(&context);
2801
+
2802
+ assert_definition_at!(&context, "1:1-1:18", Method, |def| {
2803
+ assert_simple_signature!(def, |params| {
2804
+ assert_eq!(params.len(), 1);
2805
+ assert_parameter!(&params[0], Forward, |param| {
2806
+ assert_string_eq!(context, param.str(), "...");
2807
+ });
2808
+ });
2809
+ });
2810
+ }
2811
+
2812
+ #[test]
2813
+ fn index_nested_method_definitions() {
2814
+ let context = index_source({
2815
+ "
2816
+ class Foo
2817
+ def bar
2818
+ def baz; end
2819
+ end
2820
+ end
2821
+ "
2822
+ });
2823
+ assert_no_local_diagnostics!(&context);
2824
+
2825
+ assert_definition_at!(&context, "1:1-5:4", Class, |foo| {
2826
+ assert_definition_at!(&context, "2:3-4:6", Method, |bar| {
2827
+ assert_definition_at!(&context, "3:5-3:17", Method, |baz| {
2828
+ assert_eq!(foo.id(), bar.lexical_nesting_id().unwrap());
2829
+ assert_eq!(foo.id(), baz.lexical_nesting_id().unwrap());
2830
+ });
2831
+ });
2832
+ });
2833
+ }
2834
+ }
2835
+
2836
+ mod singleton_class_tests {
2837
+ use super::*;
2838
+
2839
+ #[test]
2840
+ fn index_class_self_block_creates_singleton_class() {
2841
+ let context = index_source({
2842
+ "
2843
+ class Bar; end
2844
+
2845
+ class Foo
2846
+ class << self
2847
+ def baz; end
2848
+
2849
+ class << Bar
2850
+ def self.qux; end
2851
+ end
2852
+
2853
+ class << self
2854
+ def quz; end
2855
+ end
2856
+ end
2857
+ end
2858
+ "
2859
+ });
2860
+
2861
+ assert_no_local_diagnostics!(&context);
2862
+
2863
+ // class Bar
2864
+ assert_definition_at!(&context, "1:1-1:15", Class, |bar_class| {
2865
+ assert_def_name_eq!(&context, bar_class, "Bar");
2866
+ assert_def_name_offset_eq!(&context, bar_class, "1:7-1:10");
2867
+ });
2868
+
2869
+ // class Foo
2870
+ assert_definition_at!(&context, "3:1-15:4", Class, |foo_class| {
2871
+ assert_def_name_eq!(&context, foo_class, "Foo");
2872
+ assert_def_name_offset_eq!(&context, foo_class, "3:7-3:10");
2873
+
2874
+ // class << self (inside Foo)
2875
+ assert_definition_at!(&context, "4:3-14:6", SingletonClass, |foo_singleton| {
2876
+ assert_def_name_eq!(&context, foo_singleton, "Foo::<Foo>");
2877
+ // name_offset points to "self"
2878
+ assert_def_name_offset_eq!(&context, foo_singleton, "4:12-4:16");
2879
+ assert_eq!(foo_singleton.lexical_nesting_id(), &Some(foo_class.id()));
2880
+
2881
+ // def baz (inside class << self)
2882
+ assert_definition_at!(&context, "5:5-5:17", Method, |baz_method| {
2883
+ assert_eq!(baz_method.lexical_nesting_id(), &Some(foo_singleton.id()));
2884
+ });
2885
+
2886
+ // class << Bar (inside class << self of Foo)
2887
+ assert_definition_at!(&context, "7:5-9:8", SingletonClass, |bar_singleton| {
2888
+ assert_def_name_eq!(&context, bar_singleton, "Bar::<Bar>");
2889
+ // name_offset points to "Bar"
2890
+ assert_def_name_offset_eq!(&context, bar_singleton, "7:14-7:17");
2891
+ assert_eq!(bar_singleton.lexical_nesting_id(), &Some(foo_singleton.id()));
2892
+
2893
+ // def self.qux (inside class << Bar)
2894
+ assert_definition_at!(&context, "8:7-8:24", Method, |qux_method| {
2895
+ assert_eq!(qux_method.lexical_nesting_id(), &Some(bar_singleton.id()));
2896
+ assert_method_has_receiver!(&context, qux_method, "<Bar>");
2897
+ });
2898
+ });
2899
+
2900
+ // class << self (nested inside outer class << self)
2901
+ assert_definition_at!(&context, "11:5-13:8", SingletonClass, |nested_singleton| {
2902
+ assert_def_name_eq!(&context, nested_singleton, "Foo::<Foo>::<<Foo>>");
2903
+ // name_offset points to "self"
2904
+ assert_def_name_offset_eq!(&context, nested_singleton, "11:14-11:18");
2905
+ assert_eq!(nested_singleton.lexical_nesting_id(), &Some(foo_singleton.id()));
2906
+
2907
+ // def quz (inside nested class << self)
2908
+ assert_definition_at!(&context, "12:7-12:19", Method, |quz_method| {
2909
+ assert_eq!(quz_method.lexical_nesting_id(), &Some(nested_singleton.id()));
2910
+ });
2911
+ });
2912
+ });
2913
+ });
2914
+ }
2915
+
2916
+ #[test]
2917
+ fn index_singleton_class_definition_in_compact_namespace() {
2918
+ let context = index_source({
2919
+ "
2920
+ class Foo::Bar
2921
+ class << self
2922
+ def baz; end
2923
+ end
2924
+ end
2925
+ "
2926
+ });
2927
+
2928
+ assert_no_local_diagnostics!(&context);
2929
+
2930
+ assert_definition_at!(&context, "1:1-5:4", Class, |class_def| {
2931
+ assert_def_name_eq!(&context, class_def, "Foo::Bar");
2932
+ assert_definition_at!(&context, "2:3-4:6", SingletonClass, |singleton_class| {
2933
+ assert_eq!(singleton_class.lexical_nesting_id(), &Some(class_def.id()));
2934
+ assert_definition_at!(&context, "3:5-3:17", Method, |method| {
2935
+ assert_eq!(method.lexical_nesting_id(), &Some(singleton_class.id()));
2936
+ });
2937
+ });
2938
+ });
2939
+
2940
+ assert_constant_references_eq!(&context, ["Foo"]);
2941
+ }
2942
+
2943
+ #[test]
2944
+ fn index_constant_in_singleton_class_definition() {
2945
+ let context = index_source({
2946
+ "
2947
+ class Foo
2948
+ class << self
2949
+ A = 1
2950
+ end
2951
+ end
2952
+ "
2953
+ });
2954
+
2955
+ assert_no_local_diagnostics!(&context);
2956
+
2957
+ assert_definition_at!(&context, "1:1-5:4", Class, |class_def| {
2958
+ assert_definition_at!(&context, "2:3-4:6", SingletonClass, |singleton_class| {
2959
+ assert_eq!(singleton_class.lexical_nesting_id(), &Some(class_def.id()));
2960
+ assert_definition_at!(&context, "3:5-3:6", Constant, |def| {
2961
+ assert_def_name_eq!(&context, def, "A");
2962
+ assert_eq!(Some(singleton_class.id()), def.lexical_nesting_id().clone());
2963
+ });
2964
+ });
2965
+ });
2966
+ }
2967
+
2968
+ #[test]
2969
+ fn do_not_index_singleton_class_with_dynamic_expression() {
2970
+ let context = index_source({
2971
+ "
2972
+ class << foo
2973
+ def bar; end
2974
+ end
2975
+ "
2976
+ });
2977
+
2978
+ assert_local_diagnostics_eq!(
2979
+ &context,
2980
+ ["dynamic-singleton-definition: Dynamic singleton class definition (1:1-3:4)"]
2981
+ );
2982
+ assert_eq!(context.graph().definitions().len(), 0);
2983
+ }
2984
+ }
2985
+
2986
+ mod visibility_tests {
2987
+ use super::*;
2988
+
2989
+ #[test]
2990
+ fn index_def_node_with_visibility_top_level() {
2991
+ let context = index_source({
2992
+ "
2993
+ def m1; end
2994
+
2995
+ protected def m2; end
2996
+
2997
+ public
2998
+
2999
+ def m3; end
3000
+ "
3001
+ });
3002
+
3003
+ assert_no_local_diagnostics!(&context);
3004
+
3005
+ assert_definition_at!(&context, "1:1-1:12", Method, |def| {
3006
+ assert_def_str_eq!(&context, def, "m1()");
3007
+ assert_eq!(def.visibility(), &Visibility::Private);
3008
+ });
3009
+
3010
+ assert_definition_at!(&context, "3:11-3:22", Method, |def| {
3011
+ assert_def_str_eq!(&context, def, "m2()");
3012
+ assert_eq!(def.visibility(), &Visibility::Protected);
3013
+ });
3014
+
3015
+ assert_definition_at!(&context, "7:1-7:12", Method, |def| {
3016
+ assert_def_str_eq!(&context, def, "m3()");
3017
+ assert_eq!(def.visibility(), &Visibility::Public);
3018
+ });
3019
+ }
3020
+
3021
+ #[test]
3022
+ fn index_module_function() {
3023
+ let context = index_source({
3024
+ "
3025
+ module Foo
3026
+ def bar; end
3027
+
3028
+ module_function
3029
+
3030
+ def baz; end
3031
+ attr_reader :attribute
3032
+
3033
+ public
3034
+
3035
+ def qux; end
3036
+
3037
+ module_function def boop; end
3038
+
3039
+ def zip; end
3040
+ end
3041
+ "
3042
+ });
3043
+
3044
+ assert_no_local_diagnostics!(&context);
3045
+
3046
+ assert_definition_at!(&context, "2:3-2:15", Method, |def| {
3047
+ assert_def_str_eq!(&context, def, "bar()");
3048
+ assert_eq!(def.visibility(), &Visibility::Public);
3049
+ });
3050
+
3051
+ let definitions = context.all_definitions_at("6:3-6:15");
3052
+ assert_eq!(
3053
+ definitions.len(),
3054
+ 2,
3055
+ "module_function should create two definitions for baz"
3056
+ );
3057
+
3058
+ let instance_method = definitions
3059
+ .iter()
3060
+ .find(|d| matches!(d, Definition::Method(m) if m.receiver().is_none()))
3061
+ .expect("should have instance method definition");
3062
+ let Definition::Method(instance_method) = instance_method else {
3063
+ panic!()
3064
+ };
3065
+ assert_def_str_eq!(&context, instance_method, "baz()");
3066
+ assert_eq!(instance_method.visibility(), &Visibility::Private);
3067
+
3068
+ let singleton_method = definitions
3069
+ .iter()
3070
+ .find(|d| matches!(d, Definition::Method(m) if m.receiver().is_some()))
3071
+ .expect("should have singleton method definition");
3072
+ let Definition::Method(singleton_method) = singleton_method else {
3073
+ panic!()
3074
+ };
3075
+ assert_def_str_eq!(&context, singleton_method, "baz()");
3076
+ assert_eq!(singleton_method.visibility(), &Visibility::Public);
3077
+
3078
+ assert_definition_at!(&context, "7:16-7:25", AttrReader, |def| {
3079
+ assert_def_str_eq!(&context, def, "attribute()");
3080
+ assert_eq!(def.visibility(), &Visibility::Private);
3081
+ });
3082
+
3083
+ assert_definition_at!(&context, "11:3-11:15", Method, |def| {
3084
+ assert_def_str_eq!(&context, def, "qux()");
3085
+ assert_eq!(def.visibility(), &Visibility::Public);
3086
+ });
3087
+
3088
+ let definitions = context.all_definitions_at("13:19-13:32");
3089
+ assert_eq!(
3090
+ definitions.len(),
3091
+ 2,
3092
+ "module_function should create two definitions for boop"
3093
+ );
3094
+
3095
+ let instance_method = definitions
3096
+ .iter()
3097
+ .find(|d| matches!(d, Definition::Method(m) if m.receiver().is_none()))
3098
+ .expect("boop: should have instance method definition");
3099
+ let Definition::Method(instance_method) = instance_method else {
3100
+ panic!()
3101
+ };
3102
+ assert_def_str_eq!(&context, instance_method, "boop()");
3103
+ assert_eq!(instance_method.visibility(), &Visibility::Private);
3104
+
3105
+ let singleton_method = definitions
3106
+ .iter()
3107
+ .find(|d| matches!(d, Definition::Method(m) if m.receiver().is_some()))
3108
+ .expect("boop: should have singleton method definition");
3109
+ let Definition::Method(singleton_method) = singleton_method else {
3110
+ panic!()
3111
+ };
3112
+ assert_def_str_eq!(&context, singleton_method, "boop()");
3113
+ assert_eq!(singleton_method.visibility(), &Visibility::Public);
3114
+
3115
+ assert_definition_at!(&context, "15:3-15:15", Method, |def| {
3116
+ assert_def_str_eq!(&context, def, "zip()");
3117
+ assert_eq!(def.visibility(), &Visibility::Public);
3118
+ });
3119
+ }
3120
+
3121
+ #[test]
3122
+ fn index_def_node_with_visibility_nested() {
3123
+ let context = index_source({
3124
+ "
3125
+ protected
3126
+
3127
+ class Foo
3128
+ def m1; end
3129
+
3130
+ private
3131
+
3132
+ module Bar
3133
+ def m2; end
3134
+
3135
+ private
3136
+
3137
+ def m3; end
3138
+
3139
+ protected
3140
+ end
3141
+
3142
+ def m4; end
3143
+ end
3144
+ "
3145
+ });
3146
+
3147
+ assert_no_local_diagnostics!(&context);
3148
+
3149
+ assert_definition_at!(&context, "4:3-4:14", Method, |def| {
3150
+ assert_def_str_eq!(&context, def, "m1()");
3151
+ assert_eq!(def.visibility(), &Visibility::Public);
3152
+ });
3153
+
3154
+ assert_definition_at!(&context, "9:5-9:16", Method, |def| {
3155
+ assert_def_str_eq!(&context, def, "m2()");
3156
+ assert_eq!(def.visibility(), &Visibility::Public);
3157
+ });
3158
+
3159
+ assert_definition_at!(&context, "13:5-13:16", Method, |def| {
3160
+ assert_def_str_eq!(&context, def, "m3()");
3161
+ assert_eq!(def.visibility(), &Visibility::Private);
3162
+ });
3163
+
3164
+ assert_definition_at!(&context, "18:3-18:14", Method, |def| {
3165
+ assert_def_str_eq!(&context, def, "m4()");
3166
+ assert_eq!(def.visibility(), &Visibility::Private);
3167
+ });
3168
+ }
3169
+
3170
+ #[test]
3171
+ fn index_def_node_singleton_visibility() {
3172
+ let context = index_source({
3173
+ "
3174
+ protected
3175
+
3176
+ def self.m1; end
3177
+
3178
+ protected def self.m2; end
3179
+
3180
+ class Foo
3181
+ private
3182
+
3183
+ def self.m3; end
3184
+ end
3185
+ "
3186
+ });
3187
+
3188
+ assert_no_local_diagnostics!(&context);
3189
+
3190
+ assert_definition_at!(&context, "3:1-3:17", Method, |def| {
3191
+ assert_def_str_eq!(&context, def, "m1()");
3192
+ assert_eq!(def.visibility(), &Visibility::Public);
3193
+ });
3194
+
3195
+ assert_definition_at!(&context, "5:11-5:27", Method, |def| {
3196
+ assert_def_str_eq!(&context, def, "m2()");
3197
+ assert_eq!(def.visibility(), &Visibility::Public);
3198
+ });
3199
+
3200
+ assert_definition_at!(&context, "10:3-10:19", Method, |def| {
3201
+ assert_def_str_eq!(&context, def, "m3()");
3202
+ assert_eq!(def.visibility(), &Visibility::Public);
3203
+ });
3204
+ }
3205
+
3206
+ #[test]
3207
+ fn index_visibility_in_singleton_class() {
3208
+ let context = index_source({
3209
+ "
3210
+ class Foo
3211
+ protected
3212
+
3213
+ class << self
3214
+ def m1; end
3215
+
3216
+ private
3217
+
3218
+ def m2; end
3219
+ end
3220
+
3221
+ def m3; end
3222
+ end
3223
+ "
3224
+ });
3225
+
3226
+ assert_no_local_diagnostics!(&context);
3227
+
3228
+ assert_definition_at!(&context, "5:5-5:16", Method, |def| {
3229
+ assert_def_str_eq!(&context, def, "m1()");
3230
+ assert_eq!(def.visibility(), &Visibility::Public);
3231
+ });
3232
+
3233
+ assert_definition_at!(&context, "9:5-9:16", Method, |def| {
3234
+ assert_def_str_eq!(&context, def, "m2()");
3235
+ assert_eq!(def.visibility(), &Visibility::Private);
3236
+ });
3237
+
3238
+ assert_definition_at!(&context, "12:3-12:14", Method, |def| {
3239
+ assert_def_str_eq!(&context, def, "m3()");
3240
+ assert_eq!(def.visibility(), &Visibility::Protected);
3241
+ });
3242
+ }
3243
+
3244
+ #[test]
3245
+ fn index_private_constant_calls() {
3246
+ let context = index_source({
3247
+ r#"
3248
+ module Foo
3249
+ BAR = 42
3250
+ BAZ = 43
3251
+ FOO = 44
3252
+
3253
+ private_constant :BAR, :BAZ
3254
+ private_constant "FOO"
3255
+
3256
+ class Qux
3257
+ BAR = 42
3258
+ BAZ = 43
3259
+
3260
+ Foo.public_constant :BAR
3261
+ Foo.public_constant "BAZ"
3262
+ end
3263
+
3264
+ self.private_constant :Qux
3265
+ end
3266
+
3267
+ Foo.public_constant :BAR
3268
+ "#
3269
+ });
3270
+
3271
+ assert_no_local_diagnostics!(&context);
3272
+
3273
+ assert_definition_at!(&context, "6:21-6:24", ConstantVisibility, |def| {
3274
+ assert_def_name_eq!(&context, def, "BAR");
3275
+ assert_eq!(def.visibility(), &Visibility::Private);
3276
+ });
3277
+ assert_definition_at!(&context, "6:27-6:30", ConstantVisibility, |def| {
3278
+ assert_def_name_eq!(&context, def, "BAZ");
3279
+ assert_eq!(def.visibility(), &Visibility::Private);
3280
+ });
3281
+ assert_definition_at!(&context, "7:20-7:25", ConstantVisibility, |def| {
3282
+ assert_def_name_eq!(&context, def, "FOO");
3283
+ assert_eq!(def.visibility(), &Visibility::Private);
3284
+ });
3285
+ assert_definition_at!(&context, "13:26-13:29", ConstantVisibility, |def| {
3286
+ assert_def_name_eq!(&context, def, "Foo::BAR");
3287
+ assert_eq!(def.visibility(), &Visibility::Public);
3288
+ });
3289
+ assert_definition_at!(&context, "14:25-14:30", ConstantVisibility, |def| {
3290
+ assert_def_name_eq!(&context, def, "Foo::BAZ");
3291
+ assert_eq!(def.visibility(), &Visibility::Public);
3292
+ });
3293
+ assert_definition_at!(&context, "17:26-17:29", ConstantVisibility, |def| {
3294
+ assert_def_name_eq!(&context, def, "Qux");
3295
+ assert_eq!(def.visibility(), &Visibility::Private);
3296
+ });
3297
+ assert_definition_at!(&context, "20:22-20:25", ConstantVisibility, |def| {
3298
+ assert_def_name_eq!(&context, def, "Foo::BAR");
3299
+ assert_eq!(def.visibility(), &Visibility::Public);
3300
+ });
3301
+ }
3302
+
3303
+ #[test]
3304
+ fn index_private_constant_calls_diagnostics() {
3305
+ let context = index_source({
3306
+ "
3307
+ private_constant :NOT_INDEXED
3308
+ self.private_constant :NOT_INDEXED
3309
+ foo.private_constant :NOT_INDEXED # not indexed, dynamic receiver
3310
+
3311
+ module Foo
3312
+ private_constant NOT_INDEXED, not_indexed # not indexed, not a symbol
3313
+ private_constant # not indexed, no arguments
3314
+
3315
+ def self.qux
3316
+ private_constant :Bar # not indexed, dynamic
3317
+ end
3318
+
3319
+ def foo
3320
+ private_constant :Bar # not indexed, dynamic
3321
+ end
3322
+ end
3323
+ "
3324
+ });
3325
+
3326
+ assert_local_diagnostics_eq!(
3327
+ &context,
3328
+ vec![
3329
+ "invalid-private-constant: Private constant called at top level (1:1-1:30)",
3330
+ "invalid-private-constant: Private constant called at top level (2:1-2:35)",
3331
+ "invalid-private-constant: Dynamic receiver for private constant (3:1-3:34)",
3332
+ "invalid-private-constant: Private constant called with non-symbol argument (6:20-6:31)",
3333
+ ]
3334
+ );
3335
+
3336
+ assert_eq!(context.graph().definitions().len(), 3); // Foo, Foo::Qux, Foo#foo
3337
+ }
3338
+
3339
+ #[test]
3340
+ fn index_retroactive_method_visibility() {
3341
+ let context = index_source(
3342
+ "
3343
+ class Foo
3344
+ def foo; end
3345
+ def bar; end
3346
+ def baz; end
3347
+
3348
+ private :foo
3349
+ protected :bar, :baz
3350
+ public :foo
3351
+ end
3352
+ ",
3353
+ );
3354
+
3355
+ assert_no_local_diagnostics!(&context);
3356
+
3357
+ assert_definition_at!(&context, "6:12-6:15", MethodVisibility, |def| {
3358
+ assert_def_str_eq!(&context, def, "foo()");
3359
+ assert_eq!(def.visibility(), &Visibility::Private);
3360
+ });
3361
+ assert_definition_at!(&context, "7:14-7:17", MethodVisibility, |def| {
3362
+ assert_def_str_eq!(&context, def, "bar()");
3363
+ assert_eq!(def.visibility(), &Visibility::Protected);
3364
+ });
3365
+ assert_definition_at!(&context, "7:20-7:23", MethodVisibility, |def| {
3366
+ assert_def_str_eq!(&context, def, "baz()");
3367
+ assert_eq!(def.visibility(), &Visibility::Protected);
3368
+ });
3369
+ assert_definition_at!(&context, "8:11-8:14", MethodVisibility, |def| {
3370
+ assert_def_str_eq!(&context, def, "foo()");
3371
+ assert_eq!(def.visibility(), &Visibility::Public);
3372
+ });
3373
+ }
3374
+
3375
+ #[test]
3376
+ fn index_retroactive_method_visibility_string_targets() {
3377
+ let context = index_source(
3378
+ "
3379
+ class Foo
3380
+ def foo; end
3381
+
3382
+ private \"foo\"
3383
+ end
3384
+ ",
3385
+ );
3386
+
3387
+ assert_no_local_diagnostics!(&context);
3388
+
3389
+ assert_definition_at!(&context, "4:11-4:16", MethodVisibility, |def| {
3390
+ assert_def_str_eq!(&context, def, "foo()");
3391
+ assert_eq!(def.visibility(), &Visibility::Private);
3392
+ });
3393
+ }
3394
+
3395
+ #[test]
3396
+ fn index_retroactive_method_visibility_mixed_args_diagnostic() {
3397
+ let context = index_source(
3398
+ "
3399
+ class Foo
3400
+ def foo; end
3401
+
3402
+ private :foo, SOME_CONST
3403
+ end
3404
+ ",
3405
+ );
3406
+
3407
+ assert_local_diagnostics_eq!(
3408
+ &context,
3409
+ vec!["invalid-method-visibility: `private` called with a non-literal argument (4:17-4:27)"]
3410
+ );
3411
+
3412
+ // :foo is a literal arg, so visibility is still applied
3413
+ assert_definition_at!(&context, "4:12-4:15", MethodVisibility, |def| {
3414
+ assert_def_str_eq!(&context, def, "foo()");
3415
+ assert_eq!(def.visibility(), &Visibility::Private);
3416
+ });
3417
+
3418
+ assert_constant_references_eq!(&context, ["SOME_CONST"]);
3419
+ }
3420
+
3421
+ #[test]
3422
+ fn index_retroactive_method_visibility_receiver_ignored() {
3423
+ let context = index_source(
3424
+ "
3425
+ class Foo
3426
+ def foo; end
3427
+
3428
+ Foo.private :foo
3429
+ end
3430
+ ",
3431
+ );
3432
+
3433
+ assert_local_diagnostics_eq!(
3434
+ &context,
3435
+ vec!["invalid-method-visibility: `private` cannot be called with an explicit receiver (4:3-4:19)"]
3436
+ );
3437
+
3438
+ for def in context.graph().definitions().values() {
3439
+ assert!(
3440
+ !matches!(def, Definition::MethodVisibility(_)),
3441
+ "should not create MethodVisibility with explicit receiver"
3442
+ );
3443
+ }
3444
+ }
3445
+
3446
+ #[test]
3447
+ fn index_retroactive_method_visibility_dynamic_only_diagnostic() {
3448
+ let context = index_source(
3449
+ "
3450
+ class Foo
3451
+ def foo; end
3452
+
3453
+ private SOME_CONST
3454
+ end
3455
+ ",
3456
+ );
3457
+
3458
+ assert_local_diagnostics_eq!(
3459
+ &context,
3460
+ vec!["invalid-method-visibility: `private` called with a non-literal argument (4:11-4:21)"]
3461
+ );
3462
+
3463
+ // No MethodVisibilityDefinition created
3464
+ for def in context.graph().definitions().values() {
3465
+ assert!(
3466
+ !matches!(def, Definition::MethodVisibility(_)),
3467
+ "should not create MethodVisibility for dynamic-only args"
3468
+ );
3469
+ }
3470
+ }
3471
+
3472
+ #[test]
3473
+ fn index_retroactive_method_visibility_call_expression_arg_diagnosed() {
3474
+ let context = index_source(
3475
+ "
3476
+ class Foo
3477
+ private helper(:foo)
3478
+ end
3479
+ ",
3480
+ );
3481
+
3482
+ assert_local_diagnostics_eq!(
3483
+ &context,
3484
+ vec!["invalid-method-visibility: `private` called with a non-literal argument (2:11-2:23)"]
3485
+ );
3486
+
3487
+ // No MethodVisibilityDefinition created
3488
+ for def in context.graph().definitions().values() {
3489
+ assert!(
3490
+ !matches!(def, Definition::MethodVisibility(_)),
3491
+ "should not create MethodVisibility for call-expression arg"
3492
+ );
3493
+ }
3494
+ }
3495
+
3496
+ #[test]
3497
+ fn index_receiver_attr_reader_not_scoped_definition() {
3498
+ let context = index_source(
3499
+ "
3500
+ class Foo
3501
+ private helper.attr_reader(:foo)
3502
+ end
3503
+ ",
3504
+ );
3505
+
3506
+ assert_local_diagnostics_eq!(
3507
+ &context,
3508
+ vec!["invalid-method-visibility: `private` called with a non-literal argument (2:11-2:35)"]
3509
+ );
3510
+
3511
+ // No MethodVisibilityDefinition or AttrReader created from that call
3512
+ for def in context.graph().definitions().values() {
3513
+ assert!(
3514
+ !matches!(def, Definition::MethodVisibility(_) | Definition::AttrReader(_)),
3515
+ "should not create MethodVisibility or AttrReader for receiver attr_reader call"
3516
+ );
3517
+ }
3518
+ }
3519
+
3520
+ #[test]
3521
+ fn index_scoped_private_attr_reader_single_arg() {
3522
+ let context = index_source(
3523
+ "
3524
+ class Foo
3525
+ private attr_reader(:foo)
3526
+ end
3527
+ ",
3528
+ );
3529
+
3530
+ assert_no_local_diagnostics!(&context);
3531
+
3532
+ assert_definition_at!(&context, "2:24-2:27", AttrReader, |def| {
3533
+ assert_def_str_eq!(&context, def, "foo()");
3534
+ assert_eq!(def.visibility(), &Visibility::Private);
3535
+ });
3536
+ }
3537
+
3538
+ #[test]
3539
+ fn index_attr_reader_with_extra_args_not_scoped() {
3540
+ let context = index_source(
3541
+ "
3542
+ class Foo
3543
+ private attr_reader(:foo), :bar
3544
+ end
3545
+ ",
3546
+ );
3547
+
3548
+ // attr_reader(:foo) returns an array in multi-arg context, invalid for `private`
3549
+ assert_local_diagnostics_eq!(
3550
+ &context,
3551
+ vec![
3552
+ "invalid-method-visibility: `private` with `attr_*` is only supported as a single argument (2:11-2:28)"
3553
+ ]
3554
+ );
3555
+
3556
+ // foo reader still defined via side effects, but public
3557
+ assert_definition_at!(&context, "2:24-2:27", AttrReader, |def| {
3558
+ assert_def_str_eq!(&context, def, "foo()");
3559
+ assert_eq!(def.visibility(), &Visibility::Public);
3560
+ });
3561
+ }
3562
+
3563
+ #[test]
3564
+ fn index_retroactive_module_function_symbol_target() {
3565
+ let context = index_source(
3566
+ "
3567
+ module Foo
3568
+ def foo; end
3569
+
3570
+ module_function :foo
3571
+ end
3572
+ ",
3573
+ );
3574
+
3575
+ assert_no_local_diagnostics!(&context);
3576
+
3577
+ assert_definition_at!(&context, "4:20-4:23", MethodVisibility, |def| {
3578
+ assert_def_str_eq!(&context, def, "foo()");
3579
+ assert_eq!(def.visibility(), &Visibility::ModuleFunction);
3580
+ });
3581
+ }
3582
+
3583
+ #[test]
3584
+ fn index_retroactive_module_function_in_class_is_invalid() {
3585
+ let context = index_source(
3586
+ "
3587
+ class Foo
3588
+ def foo; end
3589
+
3590
+ module_function :foo
3591
+ end
3592
+ ",
3593
+ );
3594
+
3595
+ assert_local_diagnostics_eq!(
3596
+ &context,
3597
+ vec!["invalid-method-visibility: `module_function` can only be used in modules (4:3-4:23)"]
3598
+ );
3599
+
3600
+ for def in context.graph().definitions().values() {
3601
+ assert!(
3602
+ !matches!(def, Definition::MethodVisibility(_)),
3603
+ "should not create MethodVisibility for module_function in class"
3604
+ );
3605
+ }
3606
+ }
3607
+
3608
+ #[test]
3609
+ fn index_inline_visibility_mixed_with_retroactive() {
3610
+ let context = index_source(
3611
+ "
3612
+ class Foo
3613
+ def bar; end
3614
+ private def foo; end, :bar
3615
+ end
3616
+ ",
3617
+ );
3618
+
3619
+ assert_no_local_diagnostics!(&context);
3620
+
3621
+ assert_definition_at!(&context, "3:11-3:23", Method, |def| {
3622
+ assert_def_str_eq!(&context, def, "foo()");
3623
+ assert_eq!(def.visibility(), &Visibility::Private);
3624
+ });
3625
+
3626
+ assert_definition_at!(&context, "3:26-3:29", MethodVisibility, |def| {
3627
+ assert_def_str_eq!(&context, def, "bar()");
3628
+ assert_eq!(def.visibility(), &Visibility::Private);
3629
+ });
3630
+ }
3631
+
3632
+ #[test]
3633
+ fn index_inline_visibility_multiple_defs() {
3634
+ let context = index_source(
3635
+ "
3636
+ class Foo
3637
+ private def foo; end, def bar; end
3638
+ end
3639
+ ",
3640
+ );
3641
+
3642
+ assert_no_local_diagnostics!(&context);
3643
+
3644
+ assert_definition_at!(&context, "2:11-2:23", Method, |def| {
3645
+ assert_def_str_eq!(&context, def, "foo()");
3646
+ assert_eq!(def.visibility(), &Visibility::Private);
3647
+ });
3648
+
3649
+ assert_definition_at!(&context, "2:25-2:37", Method, |def| {
3650
+ assert_def_str_eq!(&context, def, "bar()");
3651
+ assert_eq!(def.visibility(), &Visibility::Private);
3652
+ });
3653
+ }
3654
+
3655
+ #[test]
3656
+ fn index_inline_visibility_mixed_with_unsupported() {
3657
+ let context = index_source(
3658
+ "
3659
+ class Foo
3660
+ private def foo; end, CONST_A
3661
+ private CONST_B, def bar; end
3662
+ end
3663
+ ",
3664
+ );
3665
+
3666
+ assert_local_diagnostics_eq!(
3667
+ &context,
3668
+ vec![
3669
+ "invalid-method-visibility: `private` called with a non-literal argument (2:25-2:32)",
3670
+ "invalid-method-visibility: `private` called with a non-literal argument (3:11-3:18)",
3671
+ ]
3672
+ );
3673
+
3674
+ // Def gets visibility regardless of arg position
3675
+ assert_definition_at!(&context, "2:11-2:23", Method, |def| {
3676
+ assert_def_str_eq!(&context, def, "foo()");
3677
+ assert_eq!(def.visibility(), &Visibility::Private);
3678
+ });
3679
+ assert_definition_at!(&context, "3:20-3:32", Method, |def| {
3680
+ assert_def_str_eq!(&context, def, "bar()");
3681
+ assert_eq!(def.visibility(), &Visibility::Private);
3682
+ });
3683
+
3684
+ assert_constant_references_eq!(&context, ["CONST_A", "CONST_B"]);
3685
+ }
3686
+
3687
+ #[test]
3688
+ fn index_retroactive_visibility_multiple_unsupported_args() {
3689
+ let context = index_source(
3690
+ "
3691
+ class Foo
3692
+ private CONST_A, CONST_B
3693
+ end
3694
+ ",
3695
+ );
3696
+
3697
+ assert_local_diagnostics_eq!(
3698
+ &context,
3699
+ vec![
3700
+ "invalid-method-visibility: `private` called with a non-literal argument (2:11-2:18)",
3701
+ "invalid-method-visibility: `private` called with a non-literal argument (2:20-2:27)",
3702
+ ]
3703
+ );
3704
+
3705
+ assert_constant_references_eq!(&context, ["CONST_A", "CONST_B"]);
3706
+ }
3707
+
3708
+ #[test]
3709
+ fn index_module_function_mixed_args_in_class_is_invalid() {
3710
+ let context = index_source(
3711
+ "
3712
+ class Foo
3713
+ module_function def foo; end, :bar
3714
+ end
3715
+ ",
3716
+ );
3717
+
3718
+ // module_function in a class is always invalid regardless of args
3719
+ assert_local_diagnostics_eq!(
3720
+ &context,
3721
+ vec!["invalid-method-visibility: `module_function` can only be used in modules (2:3-2:37)"]
3722
+ );
3723
+
3724
+ for def in context.graph().definitions().values() {
3725
+ assert!(
3726
+ !matches!(def, Definition::MethodVisibility(_)),
3727
+ "should not create MethodVisibility for module_function in class"
3728
+ );
3729
+ }
3730
+ }
3731
+ }
3732
+
3733
+ mod attr_accessor_tests {
3734
+ use super::*;
3735
+
3736
+ #[test]
3737
+ fn index_attr_accessor_definition() {
3738
+ let context = index_source({
3739
+ "
3740
+ attr_accessor :foo
3741
+
3742
+ class Foo
3743
+ attr_accessor :bar, :baz
3744
+ end
3745
+
3746
+ foo.attr_accessor :not_indexed
3747
+ "
3748
+ });
3749
+
3750
+ assert_no_local_diagnostics!(&context);
3751
+ assert_eq!(context.graph().definitions().len(), 4);
3752
+
3753
+ assert_definition_at!(&context, "1:16-1:19", AttrAccessor, |def| {
3754
+ assert_def_str_eq!(&context, def, "foo()");
3755
+ assert!(def.lexical_nesting_id().is_none());
3756
+ });
3757
+
3758
+ assert_definition_at!(&context, "4:18-4:21", AttrAccessor, |def| {
3759
+ assert_def_str_eq!(&context, def, "bar()");
3760
+
3761
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
3762
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
3763
+ assert_eq!(parent_nesting.members()[0], def.id());
3764
+ });
3765
+ });
3766
+
3767
+ assert_definition_at!(&context, "4:24-4:27", AttrAccessor, |def| {
3768
+ assert_def_str_eq!(&context, def, "baz()");
3769
+
3770
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
3771
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
3772
+ assert_eq!(parent_nesting.members()[1], def.id());
3773
+ });
3774
+ });
3775
+ }
3776
+
3777
+ #[test]
3778
+ fn index_attr_reader_definition() {
3779
+ let context = index_source({
3780
+ "
3781
+ attr_reader :foo
3782
+
3783
+ class Foo
3784
+ attr_reader :bar, :baz
3785
+ end
3786
+ "
3787
+ });
3788
+
3789
+ assert_no_local_diagnostics!(&context);
3790
+ assert_eq!(context.graph().definitions().len(), 4);
3791
+
3792
+ assert_definition_at!(&context, "1:14-1:17", AttrReader, |def| {
3793
+ assert_def_str_eq!(&context, def, "foo()");
3794
+ assert!(def.lexical_nesting_id().is_none());
3795
+ });
3796
+
3797
+ assert_definition_at!(&context, "4:16-4:19", AttrReader, |def| {
3798
+ assert_def_str_eq!(&context, def, "bar()");
3799
+
3800
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
3801
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
3802
+ assert_eq!(parent_nesting.members()[0], def.id());
3803
+ });
3804
+ });
3805
+
3806
+ assert_definition_at!(&context, "4:22-4:25", AttrReader, |def| {
3807
+ assert_def_str_eq!(&context, def, "baz()");
3808
+
3809
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
3810
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
3811
+ assert_eq!(parent_nesting.members()[1], def.id());
3812
+ });
3813
+ });
3814
+ }
3815
+
3816
+ #[test]
3817
+ fn index_attr_writer_definition() {
3818
+ let context = index_source({
3819
+ "
3820
+ attr_writer :foo
3821
+
3822
+ class Foo
3823
+ attr_writer :bar, :baz
3824
+ end
3825
+ "
3826
+ });
3827
+
3828
+ assert_no_local_diagnostics!(&context);
3829
+ assert_eq!(context.graph().definitions().len(), 4);
3830
+
3831
+ assert_definition_at!(&context, "1:14-1:17", AttrWriter, |def| {
3832
+ assert_def_str_eq!(&context, def, "foo()");
3833
+ assert!(def.lexical_nesting_id().is_none());
3834
+ });
3835
+
3836
+ assert_definition_at!(&context, "4:16-4:19", AttrWriter, |def| {
3837
+ assert_def_str_eq!(&context, def, "bar()");
3838
+
3839
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
3840
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
3841
+ assert_eq!(parent_nesting.members()[0], def.id());
3842
+ });
3843
+ });
3844
+
3845
+ assert_definition_at!(&context, "4:22-4:25", AttrWriter, |def| {
3846
+ assert_def_str_eq!(&context, def, "baz()");
3847
+
3848
+ assert_definition_at!(&context, "3:1-5:4", Class, |parent_nesting| {
3849
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
3850
+ assert_eq!(parent_nesting.members()[1], def.id());
3851
+ });
3852
+ });
3853
+ }
3854
+
3855
+ #[test]
3856
+ fn index_attr_definition() {
3857
+ let context = index_source({
3858
+ r#"
3859
+ attr "a1", :a2
3860
+
3861
+ class Foo
3862
+ attr "a3", true
3863
+ attr :a4, false
3864
+ attr :a5, 123
3865
+ end
3866
+ "#
3867
+ });
3868
+
3869
+ assert_no_local_diagnostics!(&context);
3870
+ assert_eq!(context.graph().definitions().len(), 6);
3871
+
3872
+ assert_definition_at!(&context, "1:6-1:10", AttrReader, |def| {
3873
+ assert_def_str_eq!(&context, def, "a1()");
3874
+ assert!(def.lexical_nesting_id().is_none());
3875
+ });
3876
+
3877
+ assert_definition_at!(&context, "1:13-1:15", AttrReader, |def| {
3878
+ assert_def_str_eq!(&context, def, "a2()");
3879
+ assert!(def.lexical_nesting_id().is_none());
3880
+ });
3881
+
3882
+ assert_definition_at!(&context, "4:8-4:12", AttrAccessor, |def| {
3883
+ assert_def_str_eq!(&context, def, "a3()");
3884
+
3885
+ assert_definition_at!(&context, "3:1-7:4", Class, |parent_nesting| {
3886
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
3887
+ assert_eq!(parent_nesting.members()[0], def.id());
3888
+ });
3889
+ });
3890
+
3891
+ assert_definition_at!(&context, "5:9-5:11", AttrReader, |def| {
3892
+ assert_def_str_eq!(&context, def, "a4()");
3893
+
3894
+ assert_definition_at!(&context, "3:1-7:4", Class, |parent_nesting| {
3895
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
3896
+ assert_eq!(parent_nesting.members()[1], def.id());
3897
+ });
3898
+ });
3899
+
3900
+ assert_definition_at!(&context, "6:9-6:11", AttrReader, |def| {
3901
+ assert_def_str_eq!(&context, def, "a5()");
3902
+ assert_definition_at!(&context, "3:1-7:4", Class, |parent_nesting| {
3903
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
3904
+ assert_eq!(parent_nesting.members()[2], def.id());
3905
+ });
3906
+ });
3907
+ }
3908
+
3909
+ #[test]
3910
+ fn index_attr_accessor_with_visibility_top_level() {
3911
+ let context = index_source({
3912
+ "
3913
+ attr_accessor :foo
3914
+
3915
+ protected attr_reader :bar
3916
+
3917
+ public
3918
+
3919
+ attr_writer :baz
3920
+ "
3921
+ });
3922
+
3923
+ assert_no_local_diagnostics!(&context);
3924
+
3925
+ assert_definition_at!(&context, "1:16-1:19", AttrAccessor, |def| {
3926
+ assert_def_str_eq!(&context, def, "foo()");
3927
+ assert_eq!(def.visibility(), &Visibility::Private);
3928
+ });
3929
+
3930
+ assert_definition_at!(&context, "3:24-3:27", AttrReader, |def| {
3931
+ assert_def_str_eq!(&context, def, "bar()");
3932
+ assert_eq!(def.visibility(), &Visibility::Protected);
3933
+ });
3934
+
3935
+ assert_definition_at!(&context, "7:14-7:17", AttrWriter, |def| {
3936
+ assert_def_str_eq!(&context, def, "baz()");
3937
+ assert_eq!(def.visibility(), &Visibility::Public);
3938
+ });
3939
+ }
3940
+
3941
+ #[test]
3942
+ fn index_attr_accessor_with_visibility_nested() {
3943
+ let context = index_source({
3944
+ "
3945
+ protected
3946
+
3947
+ class Foo
3948
+ attr_accessor :foo
3949
+
3950
+ private
3951
+
3952
+ module Bar
3953
+ attr_accessor :bar
3954
+
3955
+ private
3956
+
3957
+ attr_reader :baz
3958
+
3959
+ public
3960
+ end
3961
+
3962
+ attr_writer :qux
3963
+ end
3964
+ "
3965
+ });
3966
+
3967
+ assert_no_local_diagnostics!(&context);
3968
+
3969
+ assert_definition_at!(&context, "4:18-4:21", AttrAccessor, |def| {
3970
+ assert_def_str_eq!(&context, def, "foo()");
3971
+ assert_eq!(def.visibility(), &Visibility::Public);
3972
+ });
3973
+
3974
+ assert_definition_at!(&context, "9:20-9:23", AttrAccessor, |def| {
3975
+ assert_def_str_eq!(&context, def, "bar()");
3976
+ assert_eq!(def.visibility(), &Visibility::Public);
3977
+ });
3978
+
3979
+ assert_definition_at!(&context, "13:18-13:21", AttrReader, |def| {
3980
+ assert_def_str_eq!(&context, def, "baz()");
3981
+ assert_eq!(def.visibility(), &Visibility::Private);
3982
+ });
3983
+
3984
+ assert_definition_at!(&context, "18:16-18:19", AttrWriter, |def| {
3985
+ assert_def_str_eq!(&context, def, "qux()");
3986
+ assert_eq!(def.visibility(), &Visibility::Private);
3987
+ });
3988
+ }
3989
+ }
3990
+
3991
+ mod constant_reference_tests {
3992
+ use super::*;
3993
+
3994
+ #[test]
3995
+ fn index_unresolved_constant_references() {
3996
+ let context = index_source({
3997
+ r##"
3998
+ puts C1
3999
+ puts C2::C3::C4
4000
+ puts ignored0::IGNORED0
4001
+ puts C6.foo
4002
+ foo = C7
4003
+ C8 << 42
4004
+ C9 += 42
4005
+ C10 ||= 42
4006
+ C11 &&= 42
4007
+ C12[C13]
4008
+ C14::IGNORED1 = 42 # IGNORED1 is an assignment
4009
+ C15::C16 << 42
4010
+ C17::C18 += 42
4011
+ C19::C20 ||= 42
4012
+ C21::C22 &&= 42
4013
+ puts "#{C23}"
4014
+
4015
+ ::IGNORED2 = 42 # IGNORED2 is an assignment
4016
+ puts "IGNORED3"
4017
+ puts :IGNORED4
4018
+ "##
4019
+ });
4020
+
4021
+ assert_local_diagnostics_eq!(
4022
+ &context,
4023
+ [
4024
+ "dynamic-constant-reference: Dynamic constant reference (3:6-3:14)",
4025
+ "parse-warning: assigned but unused variable - foo (5:1-5:4)",
4026
+ ]
4027
+ );
4028
+
4029
+ assert_constant_references_eq!(
4030
+ &context,
4031
+ [
4032
+ "C1", "C2", "C3", "C4", "<C6>", "C6", "C7", "<C8>", "C8", "C9", "C10", "C11", "<C12>", "C12", "C13",
4033
+ "C14", "<C16>", "C15", "C16", "C17", "C18", "C19", "C20", "C21", "C22", "C23"
4034
+ ]
4035
+ );
4036
+ }
4037
+
4038
+ #[test]
4039
+ fn index_unresolved_constant_references_from_values() {
4040
+ let context = index_source({
4041
+ "
4042
+ IGNORED1 = C1
4043
+ IGNORED2 = [C2::C3]
4044
+ C4 << C5
4045
+ C6 += C7
4046
+ C8 ||= C9
4047
+ C10 &&= C11
4048
+ C12[C13] = C14
4049
+ "
4050
+ });
4051
+
4052
+ assert_no_local_diagnostics!(&context);
4053
+
4054
+ assert_constant_references_eq!(
4055
+ &context,
4056
+ [
4057
+ "C1", "C2", "C3", "<C4>", "C4", "C5", "C6", "C7", "C8", "C9", "C10", "C11", "<C12>", "C12", "C13",
4058
+ "C14"
4059
+ ]
4060
+ );
4061
+ }
4062
+
4063
+ #[test]
4064
+ fn index_constant_path_and_write_visits_value() {
4065
+ let context = index_source({
4066
+ "
4067
+ C1::C2 &&= C3
4068
+ C4::C5 += C6
4069
+ C7::C8 ||= C9
4070
+ "
4071
+ });
4072
+
4073
+ assert_no_local_diagnostics!(&context);
4074
+
4075
+ assert_constant_references_eq!(&context, ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"]);
4076
+ }
4077
+
4078
+ #[test]
4079
+ fn index_unresolved_constant_references_for_classes() {
4080
+ let context = index_source({
4081
+ "
4082
+ C1.new
4083
+
4084
+ class IGNORED < ::C2; end
4085
+ class IGNORED < C3; end
4086
+ class IGNORED < C4::C5; end
4087
+ class IGNORED < ::C7::C6; end
4088
+
4089
+ class C8::IGNORED; end
4090
+ class ::C9::IGNORED; end
4091
+ class C10::C11::IGNORED; end
4092
+ "
4093
+ });
4094
+
4095
+ assert_no_local_diagnostics!(&context);
4096
+
4097
+ assert_constant_references_eq!(
4098
+ &context,
4099
+ [
4100
+ "<C1>", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10", "C11"
4101
+ ]
4102
+ );
4103
+ }
4104
+
4105
+ #[test]
4106
+ fn index_unresolved_constant_references_for_modules() {
4107
+ let context = index_source({
4108
+ "
4109
+ module X
4110
+ include M1
4111
+ include M2::M3
4112
+ extend M4
4113
+ extend M5::M6
4114
+ prepend M7
4115
+ prepend M8::M9
4116
+ end
4117
+
4118
+ M10.include M11
4119
+ M12.extend M13
4120
+ M14.prepend M15
4121
+
4122
+ module M16::IGNORED; end
4123
+ module ::M17::IGNORED; end
4124
+ module M18::M19::IGNORED; end
4125
+
4126
+ module M20
4127
+ include self
4128
+ end
4129
+
4130
+ module M21
4131
+ extend self
4132
+ end
4133
+
4134
+ module M22
4135
+ prepend self
4136
+ end
4137
+ "
4138
+ });
4139
+
4140
+ assert_no_local_diagnostics!(&context);
4141
+
4142
+ assert_constant_references_eq!(
4143
+ &context,
4144
+ [
4145
+ "M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9", "M10", "M11", "M12", "M13", "M14", "M15", "M16",
4146
+ "M17", "M18", "M19", "M20", "M21", "M22",
4147
+ ]
4148
+ );
4149
+ }
4150
+ }
4151
+
4152
+ mod method_reference_tests {
4153
+ use super::*;
4154
+
4155
+ #[test]
4156
+ fn index_method_reference_references() {
4157
+ let context = index_source({
4158
+ "
4159
+ m1
4160
+ m2(m3)
4161
+ m4 m5
4162
+ self.m6
4163
+ self.m7(m8)
4164
+ self.m9 m10
4165
+ C.m11
4166
+ C.m12(m13)
4167
+ C.m14 m15
4168
+ m16.m17
4169
+ m18.m19(m20)
4170
+ m21.m22 m23
4171
+
4172
+ m24.m25.m26
4173
+
4174
+ !m27 # The `!` is collected and will count as one more reference
4175
+ m28&.m29
4176
+ m30(&m31)
4177
+ m32 { m33 }
4178
+ m34 do m35 end
4179
+ m36[m37] # The `[]` is collected and will count as one more reference
4180
+
4181
+ def foo(&block)
4182
+ m38(&block)
4183
+ end
4184
+
4185
+ m39(&:m40)
4186
+ m41(&m42)
4187
+ m43(m44, &m45(m46))
4188
+ m47(x: m48, m49:)
4189
+ m50(...)
4190
+ "
4191
+ });
4192
+
4193
+ assert_local_diagnostics_eq!(
4194
+ &context,
4195
+ ["parse-error: unexpected ... when the parent method is not forwarding (31:5-31:8)"]
4196
+ );
4197
+
4198
+ assert_method_references_eq!(
4199
+ &context,
4200
+ [
4201
+ "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9", "m10", "m11", "m12", "m13", "m14", "m15", "m16",
4202
+ "m17", "m18", "m19", "m20", "m21", "m22", "m23", "m24", "m25", "m26", "!", "m27", "m28", "m29", "m30",
4203
+ "m31", "m32", "m33", "m34", "m35", "m36", "[]", "m37", "m38", "m39", "m40", "m41", "m42", "m43", "m44",
4204
+ "m45", "m46", "m47", "m48", "m49", "m50"
4205
+ ]
4206
+ );
4207
+ }
4208
+
4209
+ #[test]
4210
+ fn index_method_reference_assign_references() {
4211
+ let context = index_source({
4212
+ "
4213
+ self.m1 = m2
4214
+ m3.m4.m5 = m6.m7.m8
4215
+ self.m9, self.m10 = m11, m12
4216
+ "
4217
+ });
4218
+
4219
+ assert_no_local_diagnostics!(&context);
4220
+
4221
+ assert_method_references_eq!(
4222
+ &context,
4223
+ [
4224
+ "m1=", "m2", "m3", "m4", "m5=", "m6", "m7", "m8", "m9=", "m10=", "m11", "m12"
4225
+ ]
4226
+ );
4227
+ }
4228
+
4229
+ #[test]
4230
+ fn index_method_reference_opassign_references() {
4231
+ let context = index_source({
4232
+ "
4233
+ self.m1 += 42
4234
+ self.m2 |= 42
4235
+ self.m3 ||= 42
4236
+ self.m4 &&= 42
4237
+ m5.m6 += m7
4238
+ m8.m9 ||= m10
4239
+ m11.m12 &&= m13
4240
+ "
4241
+ });
4242
+
4243
+ assert_no_local_diagnostics!(&context);
4244
+
4245
+ assert_method_references_eq!(
4246
+ &context,
4247
+ [
4248
+ "m1", "m1=", "m2", "m2=", "m3", "m3=", "m4", "m4=", "m5", "m6", "m6=", "m7", "m8", "m9", "m9=", "m10",
4249
+ "m11", "m12", "m12=", "m13",
4250
+ ]
4251
+ );
4252
+ }
4253
+
4254
+ #[test]
4255
+ fn index_method_reference_operator_references() {
4256
+ let context = index_source({
4257
+ "
4258
+ X != Y
4259
+ X % Y
4260
+ X & Y
4261
+ X && Y
4262
+ X * Y
4263
+ X ** Y
4264
+ X + Y
4265
+ X - Y
4266
+ X / Y
4267
+ X << Y
4268
+ X == Y
4269
+ X === Y
4270
+ X >> Y
4271
+ X ^ Y
4272
+ X | Y
4273
+ X || Y
4274
+ X <=> Y
4275
+ "
4276
+ });
4277
+
4278
+ assert_local_diagnostics_eq!(
4279
+ &context,
4280
+ [
4281
+ "parse-warning: possibly useless use of != in void context (1:1-1:7)",
4282
+ "parse-warning: possibly useless use of % in void context (2:1-2:6)",
4283
+ "parse-warning: possibly useless use of & in void context (3:1-3:6)",
4284
+ "parse-warning: possibly useless use of * in void context (5:1-5:6)",
4285
+ "parse-warning: possibly useless use of ** in void context (6:1-6:7)",
4286
+ "parse-warning: possibly useless use of + in void context (7:1-7:6)",
4287
+ "parse-warning: possibly useless use of - in void context (8:1-8:6)",
4288
+ "parse-warning: possibly useless use of / in void context (9:1-9:6)",
4289
+ "parse-warning: possibly useless use of == in void context (11:1-11:7)",
4290
+ "parse-warning: possibly useless use of ^ in void context (14:1-14:6)",
4291
+ "parse-warning: possibly useless use of | in void context (15:1-15:6)",
4292
+ "parse-warning: possibly useless use of <=> in void context (17:1-17:8)"
4293
+ ]
4294
+ );
4295
+
4296
+ assert_method_references_eq!(
4297
+ &context,
4298
+ [
4299
+ "!=", "%", "&", "&&", "*", "**", "+", "-", "/", "<<", "==", "===", ">>", "^", "|", "||", "<=>",
4300
+ ]
4301
+ );
4302
+ }
4303
+
4304
+ #[test]
4305
+ fn index_method_reference_lesser_than_operator_references() {
4306
+ let context = index_source({
4307
+ "
4308
+ x < y
4309
+ "
4310
+ });
4311
+
4312
+ assert_local_diagnostics_eq!(
4313
+ &context,
4314
+ ["parse-warning: possibly useless use of < in void context (1:1-1:6)"]
4315
+ );
4316
+
4317
+ assert_method_references_eq!(&context, ["x", "<", "<=>", "y"]);
4318
+ }
4319
+
4320
+ #[test]
4321
+ fn index_method_reference_lesser_than_or_equal_to_operator_references() {
4322
+ let context = index_source({
4323
+ "
4324
+ x <= y
4325
+ "
4326
+ });
4327
+
4328
+ assert_local_diagnostics_eq!(
4329
+ &context,
4330
+ ["parse-warning: possibly useless use of <= in void context (1:1-1:7)"]
4331
+ );
4332
+
4333
+ assert_method_references_eq!(&context, ["x", "<=", "<=>", "y"]);
4334
+ }
4335
+
4336
+ #[test]
4337
+ fn index_method_reference_greater_than_operator_references() {
4338
+ let context = index_source({
4339
+ "
4340
+ x > y
4341
+ "
4342
+ });
4343
+
4344
+ assert_local_diagnostics_eq!(
4345
+ &context,
4346
+ ["parse-warning: possibly useless use of > in void context (1:1-1:6)"]
4347
+ );
4348
+
4349
+ assert_method_references_eq!(&context, ["x", "<=>", ">", "y"]);
4350
+ }
4351
+
4352
+ #[test]
4353
+ fn index_method_reference_greater_than_or_equal_to_operator_references() {
4354
+ let context = index_source({
4355
+ "
4356
+ x >= y
4357
+ "
4358
+ });
4359
+
4360
+ assert_local_diagnostics_eq!(
4361
+ &context,
4362
+ ["parse-warning: possibly useless use of >= in void context (1:1-1:7)"]
4363
+ );
4364
+
4365
+ assert_method_references_eq!(&context, ["x", "<=>", ">=", "y"]);
4366
+ }
4367
+
4368
+ #[test]
4369
+ fn index_method_reference_empty_message() {
4370
+ // Indexing this method reference is necessary for triggering the creation of the singleton class and its
4371
+ // ancestor linearization, which then triggers correct completion
4372
+ let context = index_source({
4373
+ "
4374
+ Foo.
4375
+ "
4376
+ });
4377
+
4378
+ let method_ref = context.graph().method_references().values().next().unwrap();
4379
+ assert_eq!(StringId::from(""), *method_ref.str());
4380
+
4381
+ let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4382
+ assert_eq!(StringId::from("<Foo>"), *receiver.str());
4383
+ assert!(receiver.nesting().is_none());
4384
+
4385
+ let parent_scope = context
4386
+ .graph()
4387
+ .names()
4388
+ .get(&receiver.parent_scope().expect("Should exist"))
4389
+ .unwrap();
4390
+ assert_eq!(StringId::from("Foo"), *parent_scope.str());
4391
+ assert!(parent_scope.nesting().is_none());
4392
+ assert!(parent_scope.parent_scope().is_none());
4393
+ }
4394
+
4395
+ #[test]
4396
+ fn index_method_reference_alias_references() {
4397
+ let context = index_source({
4398
+ "
4399
+ alias ignored m1
4400
+ alias_method :ignored, :m2
4401
+ alias_method :ignored, ignored
4402
+ "
4403
+ });
4404
+
4405
+ assert_no_local_diagnostics!(&context);
4406
+ assert_method_references_eq!(&context, ["m1()", "m2()"]);
4407
+ }
4408
+
4409
+ #[test]
4410
+ fn method_call_operators() {
4411
+ let context = index_source({
4412
+ "
4413
+ Foo.bar ||= {}
4414
+ Foo.bar += {}
4415
+ Foo.bar &&= {}
4416
+ "
4417
+ });
4418
+
4419
+ assert_no_local_diagnostics!(&context);
4420
+ // We expect two constant references for `Foo` and `<Foo>` on each singleton method call
4421
+ assert_eq!(6, context.graph().constant_references().len());
4422
+ }
4423
+
4424
+ #[test]
4425
+ fn invoking_new_creates_singleton_reference() {
4426
+ let context = index_source(
4427
+ r"
4428
+ class Foo; end
4429
+ Foo.new.bar
4430
+ ",
4431
+ );
4432
+
4433
+ assert_no_local_diagnostics!(&context);
4434
+ // We expect two constant references for `Foo` and `<Foo>` due to the new call
4435
+ assert_eq!(2, context.graph().constant_references().len());
4436
+ }
4437
+
4438
+ #[test]
4439
+ fn class_new_creates_singleton_reference() {
4440
+ let context = index_source(
4441
+ r"
4442
+ CONST = Class.new
4443
+ ",
4444
+ );
4445
+
4446
+ assert_no_local_diagnostics!(&context);
4447
+ // We expect two constant references for `Class` and `<Class>` due to the new call
4448
+ assert_eq!(2, context.graph().constant_references().len());
4449
+ }
4450
+
4451
+ #[test]
4452
+ fn module_new_creates_singleton_reference() {
4453
+ let context = index_source(
4454
+ r"
4455
+ CONST = Module.new
4456
+ ",
4457
+ );
4458
+
4459
+ assert_no_local_diagnostics!(&context);
4460
+ // We expect two constant references for `Module` and `<Module>` due to the new call
4461
+ assert_eq!(2, context.graph().constant_references().len());
4462
+ }
4463
+ }
4464
+
4465
+ mod method_receiver_tests {
4466
+ use super::*;
4467
+
4468
+ /// Asserts that exactly one method reference with the given name has the expected receiver.
4469
+ ///
4470
+ /// Panics if there isn't exactly one `MethodRef` with that name.
4471
+ ///
4472
+ /// Usage:
4473
+ /// - `assert_method_ref_receiver!(context, "bar", "<Foo>")`
4474
+ macro_rules! assert_method_ref_receiver {
4475
+ ($context:expr, $method_name:expr, $expected_receiver:expr) => {{
4476
+ let target = StringId::from($method_name);
4477
+ let matches: Vec<_> = $context
4478
+ .graph()
4479
+ .method_references()
4480
+ .values()
4481
+ .filter(|method_ref| *method_ref.str() == target)
4482
+ .collect();
4483
+
4484
+ assert_eq!(
4485
+ matches.len(),
4486
+ 1,
4487
+ "expected exactly one method reference for `{}`, found {}",
4488
+ $method_name,
4489
+ matches.len()
4490
+ );
4491
+
4492
+ let method_ref = matches[0];
4493
+ let receiver_id = method_ref
4494
+ .receiver()
4495
+ .unwrap_or_else(|| panic!("method reference for `{}` has no receiver", $method_name));
4496
+ let receiver = $context.graph().names().get(&receiver_id).unwrap();
4497
+
4498
+ assert_eq!(
4499
+ StringId::from($expected_receiver),
4500
+ *receiver.str(),
4501
+ "receiver mismatch for `{}`: expected `{}`, got `{}`",
4502
+ $method_name,
4503
+ $expected_receiver,
4504
+ $context.graph().strings().get(receiver.str()).unwrap().as_str()
4505
+ );
4506
+ }};
4507
+ }
4508
+
4509
+ #[test]
4510
+ fn index_method_reference_constant_receiver() {
4511
+ let context = index_source({
4512
+ "
4513
+ Foo.bar
4514
+ "
4515
+ });
4516
+
4517
+ assert_no_local_diagnostics!(&context);
4518
+
4519
+ let method_ref = context.graph().method_references().values().next().unwrap();
4520
+ assert_eq!(StringId::from("bar"), *method_ref.str());
4521
+
4522
+ let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4523
+ assert_eq!(StringId::from("<Foo>"), *receiver.str());
4524
+ assert!(receiver.nesting().is_none());
4525
+
4526
+ let parent_scope = context
4527
+ .graph()
4528
+ .names()
4529
+ .get(&receiver.parent_scope().expect("Should exist"))
4530
+ .unwrap();
4531
+ assert_eq!(StringId::from("Foo"), *parent_scope.str());
4532
+ assert!(parent_scope.nesting().is_none());
4533
+ assert!(parent_scope.parent_scope().is_none());
4534
+ }
4535
+
4536
+ #[test]
4537
+ fn index_method_receiver_at_class_level() {
4538
+ let context = index_source({
4539
+ "
4540
+ class Foo
4541
+ self.bar
4542
+ baz
4543
+ end
4544
+ Foo.qux
4545
+ "
4546
+ });
4547
+
4548
+ assert_no_local_diagnostics!(&context);
4549
+
4550
+ assert_method_ref_receiver!(context, "bar", "<Foo>");
4551
+ assert_method_ref_receiver!(context, "baz", "<Foo>");
4552
+ assert_method_ref_receiver!(context, "qux", "<Foo>");
4553
+ }
4554
+
4555
+ #[test]
4556
+ fn index_method_receiver_self_at_module_level() {
4557
+ let context = index_source({
4558
+ "
4559
+ module Foo
4560
+ self.bar
4561
+ end
4562
+ "
4563
+ });
4564
+
4565
+ assert_no_local_diagnostics!(&context);
4566
+ assert_method_ref_receiver!(context, "bar", "<Foo>");
4567
+ }
4568
+
4569
+ #[test]
4570
+ fn index_method_receiver_inside_singleton_class() {
4571
+ let context = index_source({
4572
+ "
4573
+ class Foo
4574
+ class << self
4575
+ self.bar
4576
+ baz
4577
+ end
4578
+ end
4579
+ "
4580
+ });
4581
+
4582
+ assert_no_local_diagnostics!(&context);
4583
+ assert_method_ref_receiver!(context, "bar", "<<Foo>>");
4584
+ assert_method_ref_receiver!(context, "baz", "<<Foo>>");
4585
+ }
4586
+
4587
+ #[test]
4588
+ fn index_method_receiver_at_top_level() {
4589
+ let context = index_source({
4590
+ "
4591
+ self.bar
4592
+ "
4593
+ });
4594
+
4595
+ assert_no_local_diagnostics!(&context);
4596
+ assert_method_ref_receiver!(context, "bar", "Object");
4597
+ }
4598
+
4599
+ #[test]
4600
+ fn index_method_reference_self_receiver() {
4601
+ let context = index_source({
4602
+ "
4603
+ class Foo
4604
+ def bar
4605
+ baz
4606
+ end
4607
+
4608
+ def baz
4609
+ end
4610
+ end
4611
+ "
4612
+ });
4613
+
4614
+ assert_no_local_diagnostics!(&context);
4615
+
4616
+ let method_ref = context.graph().method_references().values().next().unwrap();
4617
+ assert_eq!(StringId::from("baz"), *method_ref.str());
4618
+
4619
+ let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4620
+ assert_eq!(StringId::from("Foo"), *receiver.str());
4621
+ assert!(receiver.nesting().is_none());
4622
+ assert!(receiver.parent_scope().is_none());
4623
+ }
4624
+
4625
+ #[test]
4626
+ fn index_method_reference_explicit_self_receiver() {
4627
+ let context = index_source({
4628
+ "
4629
+ class Foo
4630
+ def bar
4631
+ self.baz
4632
+ end
4633
+
4634
+ def baz
4635
+ end
4636
+ end
4637
+ "
4638
+ });
4639
+
4640
+ assert_no_local_diagnostics!(&context);
4641
+
4642
+ let method_ref = context.graph().method_references().values().next().unwrap();
4643
+ assert_eq!(StringId::from("baz"), *method_ref.str());
4644
+
4645
+ let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4646
+ assert_eq!(StringId::from("Foo"), *receiver.str());
4647
+ assert!(receiver.nesting().is_none());
4648
+ assert!(receiver.parent_scope().is_none());
4649
+ }
4650
+
4651
+ #[test]
4652
+ fn index_method_reference_self_receiver_in_method_ref_with_receiver() {
4653
+ let context = index_source({
4654
+ "
4655
+ class Foo
4656
+ def Bar.bar
4657
+ baz
4658
+ end
4659
+ end
4660
+ "
4661
+ });
4662
+
4663
+ assert_no_local_diagnostics!(&context);
4664
+
4665
+ let method_ref = context.graph().method_references().values().next().unwrap();
4666
+ assert_eq!(StringId::from("baz"), *method_ref.str());
4667
+
4668
+ let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4669
+ assert_eq!(StringId::from("<Bar>"), *receiver.str());
4670
+ assert!(receiver.nesting().is_none());
4671
+
4672
+ let parent_scope = context
4673
+ .graph()
4674
+ .names()
4675
+ .get(&receiver.parent_scope().expect("Should exist"))
4676
+ .unwrap();
4677
+ assert_eq!(StringId::from("Bar"), *parent_scope.str());
4678
+ assert!(parent_scope.parent_scope().is_none());
4679
+
4680
+ let nesting = context.graph().names().get(&parent_scope.nesting().unwrap()).unwrap();
4681
+ assert_eq!(StringId::from("Foo"), *nesting.str());
4682
+ assert!(nesting.nesting().is_none());
4683
+ assert!(nesting.parent_scope().is_none());
4684
+ }
4685
+
4686
+ #[test]
4687
+ fn index_method_reference_self_receiver_in_singleton_method() {
4688
+ let context = index_source({
4689
+ "
4690
+ class Foo
4691
+ def self.bar
4692
+ baz
4693
+ end
4694
+ end
4695
+ "
4696
+ });
4697
+
4698
+ assert_no_local_diagnostics!(&context);
4699
+
4700
+ let method_ref = context.graph().method_references().values().next().unwrap();
4701
+ assert_eq!(StringId::from("baz"), *method_ref.str());
4702
+
4703
+ let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4704
+ assert_eq!(StringId::from("<Foo>"), *receiver.str());
4705
+ assert!(receiver.nesting().is_none());
4706
+
4707
+ let parent_scope = context
4708
+ .graph()
4709
+ .names()
4710
+ .get(&receiver.parent_scope().expect("Should exist"))
4711
+ .unwrap();
4712
+ assert_eq!(StringId::from("Foo"), *parent_scope.str());
4713
+ assert!(parent_scope.parent_scope().is_none());
4714
+ assert!(parent_scope.nesting().is_none());
4715
+ }
4716
+
4717
+ #[test]
4718
+ fn index_method_reference_singleton_class_receiver() {
4719
+ let context = index_source({
4720
+ "
4721
+ Foo.singleton_class.bar
4722
+ "
4723
+ });
4724
+
4725
+ assert_no_local_diagnostics!(&context);
4726
+
4727
+ let method_ref = context.graph().method_references().values().next().unwrap();
4728
+ assert_eq!(StringId::from("bar"), *method_ref.str());
4729
+
4730
+ let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4731
+ assert_eq!(StringId::from("<<Foo>>"), *receiver.str(),);
4732
+ assert!(receiver.nesting().is_none());
4733
+
4734
+ let singleton = context
4735
+ .graph()
4736
+ .names()
4737
+ .get(&receiver.parent_scope().expect("Should exist"))
4738
+ .unwrap();
4739
+ assert_eq!(StringId::from("<Foo>"), *singleton.str());
4740
+ assert!(singleton.nesting().is_none());
4741
+
4742
+ let attached = context
4743
+ .graph()
4744
+ .names()
4745
+ .get(&singleton.parent_scope().expect("Should exist"))
4746
+ .unwrap();
4747
+ assert_eq!(StringId::from("Foo"), *attached.str());
4748
+ assert!(attached.nesting().is_none());
4749
+ assert!(attached.parent_scope().is_none());
4750
+ }
4751
+
4752
+ #[test]
4753
+ fn index_method_reference_and_node_constant_receiver() {
4754
+ let context = index_source({
4755
+ "
4756
+ Foo && bar
4757
+ "
4758
+ });
4759
+
4760
+ assert_no_local_diagnostics!(&context);
4761
+
4762
+ let method_ref = context
4763
+ .graph()
4764
+ .method_references()
4765
+ .values()
4766
+ .find(|r| *r.str() == StringId::from("&&"))
4767
+ .unwrap();
4768
+
4769
+ let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4770
+ assert_eq!(StringId::from("<Foo>"), *receiver.str());
4771
+ assert!(receiver.nesting().is_none());
4772
+
4773
+ let parent_scope = context
4774
+ .graph()
4775
+ .names()
4776
+ .get(&receiver.parent_scope().expect("Should exist"))
4777
+ .unwrap();
4778
+ assert_eq!(StringId::from("Foo"), *parent_scope.str());
4779
+ assert!(parent_scope.nesting().is_none());
4780
+ assert!(parent_scope.parent_scope().is_none());
4781
+ }
4782
+
4783
+ #[test]
4784
+ fn index_method_reference_or_node_constant_receiver() {
4785
+ let context = index_source({
4786
+ "
4787
+ Foo || bar
4788
+ "
4789
+ });
4790
+
4791
+ assert_no_local_diagnostics!(&context);
4792
+
4793
+ let method_ref = context
4794
+ .graph()
4795
+ .method_references()
4796
+ .values()
4797
+ .find(|r| *r.str() == StringId::from("||"))
4798
+ .unwrap();
4799
+
4800
+ let receiver = context.graph().names().get(&method_ref.receiver().unwrap()).unwrap();
4801
+ assert_eq!(StringId::from("<Foo>"), *receiver.str());
4802
+ assert!(receiver.nesting().is_none());
4803
+
4804
+ let parent_scope = context
4805
+ .graph()
4806
+ .names()
4807
+ .get(&receiver.parent_scope().expect("Should exist"))
4808
+ .unwrap();
4809
+ assert_eq!(StringId::from("Foo"), *parent_scope.str());
4810
+ assert!(parent_scope.nesting().is_none());
4811
+ assert!(parent_scope.parent_scope().is_none());
4812
+ }
4813
+ }
4814
+
4815
+ mod superclass_tests {
4816
+ use super::*;
4817
+
4818
+ #[test]
4819
+ fn superclasses_are_indexed_as_constant_ref_ids() {
4820
+ let context = index_source({
4821
+ "
4822
+ class Foo < Bar; end
4823
+ "
4824
+ });
4825
+
4826
+ assert_no_local_diagnostics!(&context);
4827
+
4828
+ assert_definition_at!(&context, "1:1-1:21", Class, |def| {
4829
+ assert_def_superclass_ref_eq!(&context, def, "Bar");
4830
+ });
4831
+ }
4832
+
4833
+ #[test]
4834
+ fn constant_path_superclasses() {
4835
+ let context = index_source({
4836
+ "
4837
+ class Foo < Bar::Baz; end
4838
+ "
4839
+ });
4840
+
4841
+ assert_no_local_diagnostics!(&context);
4842
+
4843
+ let mut refs = context.graph().constant_references().values().collect::<Vec<_>>();
4844
+ refs.sort_by_key(|a| (a.offset().start(), a.offset().end()));
4845
+
4846
+ assert_definition_at!(&context, "1:1-1:26", Class, |def| {
4847
+ assert_def_superclass_ref_eq!(&context, def, "Bar::Baz");
4848
+ assert_def_name_offset_eq!(&context, def, "1:7-1:10");
4849
+ });
4850
+ }
4851
+
4852
+ #[test]
4853
+ fn ignored_super_classes() {
4854
+ let context = index_source({
4855
+ "
4856
+ class Foo < method_call; end
4857
+ class Bar < 123; end
4858
+ class MyMigration < ActiveRecord::Migration[8.0]; end
4859
+ class Baz < foo::Bar; end
4860
+ "
4861
+ });
4862
+
4863
+ assert_local_diagnostics_eq!(
4864
+ &context,
4865
+ [
4866
+ "dynamic-ancestor: Dynamic superclass (1:13-1:24)",
4867
+ "dynamic-ancestor: Dynamic superclass (2:13-2:16)",
4868
+ "dynamic-constant-reference: Dynamic constant reference (4:13-4:16)",
4869
+ "dynamic-ancestor: Dynamic superclass (4:13-4:21)",
4870
+ ]
4871
+ );
4872
+
4873
+ assert_definition_at!(&context, "1:1-1:29", Class, |def| {
4874
+ assert!(def.superclass_ref().is_none());
4875
+ });
4876
+
4877
+ assert_definition_at!(&context, "2:1-2:21", Class, |def| {
4878
+ assert!(def.superclass_ref().is_none());
4879
+ });
4880
+
4881
+ assert_definition_at!(&context, "3:1-3:54", Class, |def| {
4882
+ assert!(def.superclass_ref().is_some());
4883
+ });
4884
+
4885
+ assert_definition_at!(&context, "4:1-4:26", Class, |def| {
4886
+ assert!(def.superclass_ref().is_none());
4887
+ });
4888
+ }
4889
+ }
4890
+
4891
+ mod name_dependent_tests {
4892
+ use super::*;
4893
+
4894
+ #[test]
4895
+ fn track_dependency_chain() {
4896
+ let context = index_source(
4897
+ "
4898
+ module Bar; end
4899
+ CONST = 1
4900
+ CONST2 = CONST
4901
+
4902
+ module Foo
4903
+ class Bar::Baz
4904
+ CONST
4905
+ end
4906
+
4907
+ CONST2
4908
+ end
4909
+ ",
4910
+ );
4911
+
4912
+ assert_dependents!(&context, "Bar", [ChildName("Baz")]);
4913
+ assert_dependents!(&context, "Foo", [NestedName("Baz"), NestedName("CONST2")]);
4914
+ assert_dependents!(&context, "Bar::Baz", [Definition("Baz"), NestedName("CONST")]);
4915
+ }
4916
+
4917
+ #[test]
4918
+ fn multi_level_chain() {
4919
+ let context = index_source(
4920
+ "
4921
+ module Foo
4922
+ module Bar
4923
+ module Baz
4924
+ end
4925
+ end
4926
+ end
4927
+ ",
4928
+ );
4929
+
4930
+ assert_dependents!(&context, "Foo", [NestedName("Bar")]);
4931
+ assert_dependents!(&context, "Bar", [NestedName("Baz")]);
4932
+ }
4933
+
4934
+ #[test]
4935
+ fn singleton_class() {
4936
+ let context = index_source(
4937
+ "
4938
+ class Foo
4939
+ class << self
4940
+ def bar; end
4941
+ end
4942
+ end
4943
+ ",
4944
+ );
4945
+
4946
+ assert_dependents!(&context, "Foo", [ChildName("<Foo>")]);
4947
+ }
4948
+
4949
+ #[test]
4950
+ fn nested_vs_compact() {
4951
+ let context = index_source(
4952
+ "
4953
+ module Foo
4954
+ class Bar; end
4955
+ class Foo::Baz; end
4956
+ end
4957
+ ",
4958
+ );
4959
+
4960
+ assert_dependents!(&context, "Foo", [NestedName("Bar"), ChildName("Baz")]);
4961
+ }
4962
+ }