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,1551 @@
1
+ //! Visit the RBS AST and create type definitions.
2
+
3
+ use core::panic;
4
+
5
+ use ruby_rbs::node::{
6
+ self, AliasKind, ClassNode, CommentNode, ConstantNode, ExtendNode, FunctionTypeNode, GlobalNode, IncludeNode,
7
+ ModuleNode, Node, NodeList, PrependNode, TypeNameNode, Visit,
8
+ };
9
+
10
+ use crate::diagnostic::Rule;
11
+ use crate::indexing::local_graph::LocalGraph;
12
+ use crate::model::comment::Comment;
13
+ use crate::model::definitions::{
14
+ ClassDefinition, ConstantDefinition, Definition, DefinitionFlags, ExtendDefinition, GlobalVariableDefinition,
15
+ IncludeDefinition, MethodAliasDefinition, MethodDefinition, Mixin, ModuleDefinition, Parameter, ParameterStruct,
16
+ PrependDefinition, Receiver, Signature, Signatures,
17
+ };
18
+ use crate::model::document::Document;
19
+ use crate::model::ids::{ConstantReferenceId, DefinitionId, NameId, StringId, UriId};
20
+ use crate::model::name::{Name, ParentScope};
21
+ use crate::model::references::ConstantReference;
22
+ use crate::model::visibility::Visibility;
23
+ use crate::offset::Offset;
24
+
25
+ pub struct RBSIndexer<'a> {
26
+ uri_id: UriId,
27
+ local_graph: LocalGraph,
28
+ source: &'a str,
29
+ nesting_stack: Vec<DefinitionId>,
30
+ current_visibility: Visibility,
31
+ }
32
+
33
+ impl<'a> RBSIndexer<'a> {
34
+ #[must_use]
35
+ pub fn new(uri: String, source: &'a str) -> Self {
36
+ let uri_id = UriId::from(&uri);
37
+ let local_graph = LocalGraph::new(uri_id, Document::new(uri, source));
38
+
39
+ Self {
40
+ uri_id,
41
+ local_graph,
42
+ source,
43
+ nesting_stack: Vec::new(),
44
+ current_visibility: Visibility::Public,
45
+ }
46
+ }
47
+
48
+ #[must_use]
49
+ pub fn local_graph(self) -> LocalGraph {
50
+ self.local_graph
51
+ }
52
+
53
+ pub fn index(&mut self) {
54
+ let Ok(signature) = node::parse(self.source) else {
55
+ self.local_graph.add_diagnostic(
56
+ Rule::ParseError,
57
+ Offset::new(0, 0),
58
+ "Failed to parse RBS document".to_string(),
59
+ );
60
+ return;
61
+ };
62
+
63
+ self.visit(&signature.as_node());
64
+ }
65
+
66
+ /// Converts an RBS `TypeNameNode` into a rubydex `NameId`.
67
+ ///
68
+ /// Walks the namespace path (e.g. `Foo::Bar` in `Foo::Bar::Baz`) to build
69
+ /// a `ParentScope` chain, then creates the final `Name` for the leaf segment.
70
+ fn index_type_name(&mut self, type_name: &TypeNameNode, nesting_name_id: Option<NameId>) -> NameId {
71
+ let namespace = type_name.namespace();
72
+
73
+ let mut parent_scope = if namespace.absolute() {
74
+ ParentScope::TopLevel
75
+ } else {
76
+ ParentScope::None
77
+ };
78
+
79
+ for path_node in namespace.path().iter() {
80
+ let Node::Symbol(symbol) = path_node else {
81
+ continue;
82
+ };
83
+ parent_scope = ParentScope::Some(self.intern_name(&symbol, parent_scope, nesting_name_id));
84
+ }
85
+
86
+ self.intern_name(&type_name.name(), parent_scope, nesting_name_id)
87
+ }
88
+
89
+ fn intern_name(
90
+ &mut self,
91
+ symbol: &node::SymbolNode,
92
+ parent_scope: ParentScope,
93
+ nesting_name_id: Option<NameId>,
94
+ ) -> NameId {
95
+ let string_id = self.local_graph.intern_string(symbol.as_str().to_owned());
96
+ self.local_graph
97
+ .add_name(Name::new(string_id, parent_scope, nesting_name_id))
98
+ }
99
+
100
+ fn parent_lexical_scope_id(&self) -> Option<DefinitionId> {
101
+ self.nesting_stack.last().copied()
102
+ }
103
+
104
+ fn nesting_name_id(&self, lexical_nesting_id: Option<DefinitionId>) -> Option<NameId> {
105
+ lexical_nesting_id.map(|id| {
106
+ let owner = self
107
+ .local_graph
108
+ .definitions()
109
+ .get(&id)
110
+ .expect("owner definition should exist");
111
+ *owner.name_id().expect("nesting definition should have a name")
112
+ })
113
+ }
114
+
115
+ fn add_mixin_to_current_lexical_scope(&mut self, owner_id: DefinitionId, mixin: Mixin) {
116
+ let owner = self
117
+ .local_graph
118
+ .get_definition_mut(owner_id)
119
+ .expect("owner definition should exist");
120
+
121
+ match owner {
122
+ Definition::Class(class) => class.add_mixin(mixin),
123
+ Definition::Module(module) => module.add_mixin(mixin),
124
+ _ => unreachable!("RBS nesting stack only contains modules/classes"),
125
+ }
126
+ }
127
+
128
+ fn index_mixin(&mut self, type_name: &TypeNameNode, mixin_fn: fn(ConstantReferenceId) -> Mixin) {
129
+ let Some(lexical_nesting_id) = self.parent_lexical_scope_id() else {
130
+ return;
131
+ };
132
+
133
+ let nesting_name_id = self.nesting_name_id(Some(lexical_nesting_id));
134
+ let name_id = self.index_type_name(type_name, nesting_name_id);
135
+ let offset = Offset::from_rbs_location(&type_name.location());
136
+
137
+ let constant_ref_id =
138
+ self.local_graph
139
+ .add_constant_reference(ConstantReference::new(name_id, self.uri_id, offset));
140
+
141
+ self.add_mixin_to_current_lexical_scope(lexical_nesting_id, mixin_fn(constant_ref_id));
142
+ }
143
+
144
+ fn add_member_to_current_lexical_scope(&mut self, owner_id: DefinitionId, member_id: DefinitionId) {
145
+ let owner = self
146
+ .local_graph
147
+ .get_definition_mut(owner_id)
148
+ .expect("owner definition should exist");
149
+
150
+ match owner {
151
+ Definition::Module(module) => module.add_member(member_id),
152
+ Definition::Class(class) => class.add_member(member_id),
153
+ _ => unreachable!("RBS nesting stack only contains modules/classes"),
154
+ }
155
+ }
156
+
157
+ #[allow(clippy::cast_possible_truncation)]
158
+ fn collect_comments(&self, comment_node: Option<CommentNode>) -> Box<[Comment]> {
159
+ let Some(comment_node) = comment_node else {
160
+ return Box::new([]);
161
+ };
162
+
163
+ let location = comment_node.location();
164
+ let start = location.start().cast_unsigned() as usize;
165
+ let end = location.end().cast_unsigned() as usize;
166
+
167
+ let comment_block = &self.source[start..end];
168
+ let lines: Vec<&str> = comment_block.split('\n').collect();
169
+
170
+ let mut comments = Vec::with_capacity(lines.len());
171
+ let mut current_offset = start as u32;
172
+
173
+ for (i, line) in lines.iter().enumerate() {
174
+ let mut size = 1;
175
+ let mut line = *line;
176
+
177
+ if line.ends_with('\r') {
178
+ line = line.trim_end_matches('\r');
179
+ size += 1;
180
+ }
181
+
182
+ let line_indent = if i == 0 {
183
+ 0
184
+ } else {
185
+ line.len() - line.trim_start().len()
186
+ };
187
+
188
+ // Skip past indentation to the comment text
189
+ current_offset += line_indent as u32;
190
+
191
+ let line_text = line[line_indent..].to_string();
192
+ let line_bytes = line_text.len() as u32;
193
+ let offset = Offset::new(current_offset, current_offset + line_bytes);
194
+ comments.push(Comment::new(offset, line_text));
195
+
196
+ // Advance past current line text + \r (if present) + \n for the next line
197
+ if i < lines.len() - 1 {
198
+ current_offset += line_bytes + size;
199
+ }
200
+ }
201
+
202
+ comments.into_boxed_slice()
203
+ }
204
+
205
+ fn register_definition(
206
+ &mut self,
207
+ definition: Definition,
208
+ lexical_nesting_id: Option<DefinitionId>,
209
+ ) -> DefinitionId {
210
+ let definition_id = self.local_graph.add_definition(definition);
211
+ if let Some(id) = lexical_nesting_id {
212
+ self.add_member_to_current_lexical_scope(id, definition_id);
213
+ }
214
+ definition_id
215
+ }
216
+
217
+ #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
218
+ fn source_at(&self, location: &node::RBSLocationRange) -> String {
219
+ let start = location.start() as usize;
220
+ let end = location.end() as usize;
221
+ self.source[start..end].to_string()
222
+ }
223
+
224
+ fn intern_param(&mut self, param: &node::FunctionParamNode, default_name: &str) -> ParameterStruct {
225
+ if let Some(name_loc) = param.name_location() {
226
+ let str_id = self.local_graph.intern_string(self.source_at(&name_loc));
227
+ ParameterStruct::new(Offset::from_rbs_location(&name_loc), str_id)
228
+ } else {
229
+ let location = param.type_().location();
230
+ let str_id = self.local_graph.intern_string(default_name.to_string());
231
+ ParameterStruct::new(Offset::from_rbs_location(&location), str_id)
232
+ }
233
+ }
234
+
235
+ fn collect_parameters(&mut self, function_node: &FunctionTypeNode) -> Vec<Parameter> {
236
+ let mut parameters = Vec::new();
237
+ let mut positional_index: usize = 0;
238
+
239
+ for node in function_node.required_positionals().iter() {
240
+ let Node::FunctionParam(param) = node else {
241
+ panic!("Expected FunctionParam node, found {node:?}")
242
+ };
243
+ let default_name = format!("arg{positional_index}");
244
+ parameters.push(Parameter::RequiredPositional(self.intern_param(&param, &default_name)));
245
+ positional_index += 1;
246
+ }
247
+
248
+ for node in function_node.optional_positionals().iter() {
249
+ let Node::FunctionParam(param) = node else {
250
+ panic!("Expected FunctionParam node, found {node:?}")
251
+ };
252
+ let default_name = format!("arg{positional_index}");
253
+ parameters.push(Parameter::OptionalPositional(self.intern_param(&param, &default_name)));
254
+ positional_index += 1;
255
+ }
256
+
257
+ if let Some(node) = function_node.rest_positionals() {
258
+ let Node::FunctionParam(param) = node else {
259
+ panic!("Expected FunctionParam node, found {node:?}")
260
+ };
261
+ parameters.push(Parameter::RestPositional(self.intern_param(&param, "args")));
262
+ }
263
+
264
+ for node in function_node.trailing_positionals().iter() {
265
+ let Node::FunctionParam(param) = node else {
266
+ panic!("Expected FunctionParam node, found {node:?}")
267
+ };
268
+ let default_name = format!("arg{positional_index}");
269
+ parameters.push(Parameter::Post(self.intern_param(&param, &default_name)));
270
+ positional_index += 1;
271
+ }
272
+
273
+ for (key, _value) in function_node.required_keywords().iter() {
274
+ let name = self.source_at(&key.location());
275
+ let offset = Offset::from_rbs_location(&key.location());
276
+ let str_id = self.local_graph.intern_string(name);
277
+ parameters.push(Parameter::RequiredKeyword(ParameterStruct::new(offset, str_id)));
278
+ }
279
+
280
+ for (key, _value) in function_node.optional_keywords().iter() {
281
+ let name = self.source_at(&key.location());
282
+ let offset = Offset::from_rbs_location(&key.location());
283
+ let str_id = self.local_graph.intern_string(name);
284
+ parameters.push(Parameter::OptionalKeyword(ParameterStruct::new(offset, str_id)));
285
+ }
286
+
287
+ if let Some(node) = function_node.rest_keywords() {
288
+ let Node::FunctionParam(param) = &node else {
289
+ panic!("Expected FunctionParam node, found {node:?}")
290
+ };
291
+ parameters.push(Parameter::RestKeyword(self.intern_param(param, "kwargs")));
292
+ }
293
+
294
+ parameters
295
+ }
296
+
297
+ fn build_signatures(sigs: &mut Vec<Signature>) -> Signatures {
298
+ match sigs.len() {
299
+ 0 => Signatures::Simple(Box::new([])),
300
+ 1 => Signatures::Simple(sigs.pop().unwrap()),
301
+ _ => Signatures::Overloaded(std::mem::take(sigs).into_boxed_slice()),
302
+ }
303
+ }
304
+
305
+ fn collect_overload_signatures(&mut self, def_node: &node::MethodDefinitionNode) -> Signatures {
306
+ let mut sigs: Vec<Signature> = Vec::new();
307
+
308
+ for overload_node in def_node.overloads().iter() {
309
+ let Node::MethodDefinitionOverload(overload) = overload_node else {
310
+ panic!("Expected MethodDefinitionOverload node in overloads, found {overload_node:?}");
311
+ };
312
+ let Node::MethodType(method_type) = overload.method_type() else {
313
+ panic!(
314
+ "Expected MethodType node in overloads, found {:?}",
315
+ overload.method_type()
316
+ );
317
+ };
318
+ let mut params = match method_type.type_() {
319
+ Node::FunctionType(function_type) => self.collect_parameters(&function_type),
320
+ Node::UntypedFunctionType(_) => Vec::new(),
321
+ other => panic!("Expected FunctionType node in overloads, found {other:?}"),
322
+ };
323
+ if let Some(block) = method_type.block() {
324
+ let str_id = self.local_graph.intern_string("block".to_string());
325
+ let offset = Offset::from_rbs_location(&block.location());
326
+ params.push(Parameter::Block(ParameterStruct::new(offset, str_id)));
327
+ }
328
+ sigs.push(params.into_boxed_slice());
329
+ }
330
+
331
+ Self::build_signatures(&mut sigs)
332
+ }
333
+
334
+ /// Registers two method definitions for `module_function` (SingletonInstance):
335
+ /// a public singleton method and a private instance method.
336
+ #[allow(clippy::too_many_arguments)]
337
+ fn register_singleton_instance_method(
338
+ &mut self,
339
+ str_id: StringId,
340
+ offset: Offset,
341
+ comments: Box<[Comment]>,
342
+ flags: DefinitionFlags,
343
+ lexical_nesting_id: Option<DefinitionId>,
344
+ signatures: Signatures,
345
+ ) {
346
+ let singleton_def = Definition::Method(Box::new(MethodDefinition::new(
347
+ str_id,
348
+ self.uri_id,
349
+ offset.clone(),
350
+ comments.clone(),
351
+ flags.clone(),
352
+ lexical_nesting_id,
353
+ signatures.clone(),
354
+ Visibility::Public,
355
+ lexical_nesting_id.map(Receiver::SelfReceiver),
356
+ )));
357
+ self.register_definition(singleton_def, lexical_nesting_id);
358
+
359
+ let instance_def = Definition::Method(Box::new(MethodDefinition::new(
360
+ str_id,
361
+ self.uri_id,
362
+ offset,
363
+ comments,
364
+ flags,
365
+ lexical_nesting_id,
366
+ signatures,
367
+ Visibility::Private,
368
+ None,
369
+ )));
370
+ self.register_definition(instance_def, lexical_nesting_id);
371
+ }
372
+
373
+ /// Extracts definition flags from the list of RBS annotations.
374
+ ///
375
+ /// panics when a non-annotation node is encountered in the list, since only annotations should be present.
376
+ fn flags(list: &NodeList) -> DefinitionFlags {
377
+ let mut flags = DefinitionFlags::empty();
378
+
379
+ for node in list.iter() {
380
+ if let Node::Annotation(annotation) = node {
381
+ let string = annotation.string();
382
+ let content = string.as_bytes();
383
+ if content == b"deprecated" || content.starts_with(b"deprecated:") {
384
+ flags |= DefinitionFlags::DEPRECATED;
385
+ }
386
+ } else {
387
+ panic!("Expected annotation node, found {node:?}");
388
+ }
389
+ }
390
+
391
+ flags
392
+ }
393
+ }
394
+
395
+ impl Visit for RBSIndexer<'_> {
396
+ fn visit_class_node(&mut self, class_node: &ClassNode) {
397
+ let lexical_nesting_id = self.parent_lexical_scope_id();
398
+ let nesting_name_id = self.nesting_name_id(lexical_nesting_id);
399
+
400
+ let type_name = class_node.name();
401
+ let name_id = self.index_type_name(&type_name, nesting_name_id);
402
+ let offset = Offset::from_rbs_location(&class_node.location());
403
+ let name_offset = Offset::from_rbs_location(&type_name.name().location());
404
+
405
+ let comments = self.collect_comments(class_node.comment());
406
+
407
+ let superclass_ref = class_node.super_class().as_ref().map(|super_node| {
408
+ let type_name = super_node.name();
409
+ let name_id = self.index_type_name(&type_name, nesting_name_id);
410
+ let offset = Offset::from_rbs_location(&super_node.location());
411
+ self.local_graph
412
+ .add_constant_reference(ConstantReference::new(name_id, self.uri_id, offset))
413
+ });
414
+
415
+ let definition = Definition::Class(Box::new(ClassDefinition::new(
416
+ name_id,
417
+ self.uri_id,
418
+ offset,
419
+ name_offset,
420
+ comments,
421
+ Self::flags(&class_node.annotations()),
422
+ lexical_nesting_id,
423
+ superclass_ref,
424
+ )));
425
+
426
+ let definition_id = self.register_definition(definition, lexical_nesting_id);
427
+ self.nesting_stack.push(definition_id);
428
+ let saved_visibility = std::mem::replace(&mut self.current_visibility, Visibility::Public);
429
+
430
+ for member in class_node.members().iter() {
431
+ self.visit(&member);
432
+ }
433
+
434
+ self.current_visibility = saved_visibility;
435
+ self.nesting_stack.pop();
436
+ }
437
+
438
+ fn visit_module_node(&mut self, module_node: &ModuleNode) {
439
+ let lexical_nesting_id = self.parent_lexical_scope_id();
440
+ let nesting_name_id = self.nesting_name_id(lexical_nesting_id);
441
+
442
+ let type_name = module_node.name();
443
+ let name_id = self.index_type_name(&type_name, nesting_name_id);
444
+ let offset = Offset::from_rbs_location(&module_node.location());
445
+ let name_offset = Offset::from_rbs_location(&type_name.name().location());
446
+
447
+ let comments = self.collect_comments(module_node.comment());
448
+
449
+ let definition = Definition::Module(Box::new(ModuleDefinition::new(
450
+ name_id,
451
+ self.uri_id,
452
+ offset,
453
+ name_offset,
454
+ comments,
455
+ Self::flags(&module_node.annotations()),
456
+ lexical_nesting_id,
457
+ )));
458
+
459
+ let definition_id = self.register_definition(definition, lexical_nesting_id);
460
+ self.nesting_stack.push(definition_id);
461
+ let saved_visibility = std::mem::replace(&mut self.current_visibility, Visibility::Public);
462
+
463
+ for member in module_node.members().iter() {
464
+ self.visit(&member);
465
+ }
466
+
467
+ self.current_visibility = saved_visibility;
468
+ self.nesting_stack.pop();
469
+ }
470
+
471
+ fn visit_constant_node(&mut self, constant_node: &ConstantNode) {
472
+ let lexical_nesting_id = self.parent_lexical_scope_id();
473
+ let nesting_name_id = self.nesting_name_id(lexical_nesting_id);
474
+
475
+ let type_name = constant_node.name();
476
+ let name_id = self.index_type_name(&type_name, nesting_name_id);
477
+ let offset = Offset::from_rbs_location(&constant_node.location());
478
+
479
+ let comments = self.collect_comments(constant_node.comment());
480
+
481
+ let definition = Definition::Constant(Box::new(ConstantDefinition::new(
482
+ name_id,
483
+ self.uri_id,
484
+ offset,
485
+ comments,
486
+ Self::flags(&constant_node.annotations()),
487
+ lexical_nesting_id,
488
+ )));
489
+
490
+ self.register_definition(definition, lexical_nesting_id);
491
+ }
492
+
493
+ fn visit_global_node(&mut self, global_node: &GlobalNode) {
494
+ let lexical_nesting_id = self.parent_lexical_scope_id();
495
+
496
+ let str_id = self.local_graph.intern_string(global_node.name().to_string());
497
+ let offset = Offset::from_rbs_location(&global_node.location());
498
+
499
+ let comments = self.collect_comments(global_node.comment());
500
+
501
+ let definition = Definition::GlobalVariable(Box::new(GlobalVariableDefinition::new(
502
+ str_id,
503
+ self.uri_id,
504
+ offset,
505
+ comments,
506
+ Self::flags(&global_node.annotations()),
507
+ lexical_nesting_id,
508
+ )));
509
+
510
+ self.register_definition(definition, lexical_nesting_id);
511
+ }
512
+
513
+ fn visit_include_node(&mut self, include_node: &IncludeNode) {
514
+ self.index_mixin(&include_node.name(), |ref_id| {
515
+ Mixin::Include(IncludeDefinition::new(ref_id))
516
+ });
517
+ }
518
+
519
+ fn visit_prepend_node(&mut self, prepend_node: &PrependNode) {
520
+ self.index_mixin(&prepend_node.name(), |ref_id| {
521
+ Mixin::Prepend(PrependDefinition::new(ref_id))
522
+ });
523
+ }
524
+
525
+ fn visit_extend_node(&mut self, extend_node: &ExtendNode) {
526
+ self.index_mixin(&extend_node.name(), |ref_id| {
527
+ Mixin::Extend(ExtendDefinition::new(ref_id))
528
+ });
529
+ }
530
+
531
+ fn visit_alias_node(&mut self, alias_node: &node::AliasNode) {
532
+ let lexical_nesting_id = self.parent_lexical_scope_id();
533
+
534
+ let receiver = match alias_node.kind() {
535
+ AliasKind::Instance => None,
536
+ AliasKind::Singleton => lexical_nesting_id.map(Receiver::SelfReceiver),
537
+ };
538
+
539
+ let new_name = alias_node.new_name();
540
+ let old_name = alias_node.old_name();
541
+
542
+ let new_name_str_id = self.local_graph.intern_string(format!("{new_name}()"));
543
+ let old_name_str_id = self.local_graph.intern_string(format!("{old_name}()"));
544
+
545
+ let offset = Offset::from_rbs_location(&alias_node.location());
546
+ let comments = self.collect_comments(alias_node.comment());
547
+
548
+ let definition = Definition::MethodAlias(Box::new(MethodAliasDefinition::new(
549
+ new_name_str_id,
550
+ old_name_str_id,
551
+ self.uri_id,
552
+ offset,
553
+ comments,
554
+ Self::flags(&alias_node.annotations()),
555
+ lexical_nesting_id,
556
+ receiver,
557
+ )));
558
+
559
+ self.register_definition(definition, lexical_nesting_id);
560
+ }
561
+
562
+ fn visit_method_definition_node(&mut self, def_node: &node::MethodDefinitionNode) {
563
+ let str_id = self.local_graph.intern_string(format!("{}()", def_node.name()));
564
+ let offset = Offset::from_rbs_location(&def_node.location());
565
+ let comments = self.collect_comments(def_node.comment());
566
+ let flags = Self::flags(&def_node.annotations());
567
+ let lexical_nesting_id = self.parent_lexical_scope_id();
568
+ let signatures = self.collect_overload_signatures(def_node);
569
+
570
+ if def_node.kind() == node::MethodDefinitionKind::SingletonInstance {
571
+ self.register_singleton_instance_method(str_id, offset, comments, flags, lexical_nesting_id, signatures);
572
+ return;
573
+ }
574
+
575
+ let (visibility, receiver) = match def_node.kind() {
576
+ node::MethodDefinitionKind::Instance => {
577
+ let vis = match def_node.visibility() {
578
+ node::MethodDefinitionVisibility::Private => Visibility::Private,
579
+ node::MethodDefinitionVisibility::Public => Visibility::Public,
580
+ node::MethodDefinitionVisibility::Unspecified => self.current_visibility,
581
+ };
582
+ (vis, None)
583
+ }
584
+ node::MethodDefinitionKind::Singleton => {
585
+ let vis = match def_node.visibility() {
586
+ node::MethodDefinitionVisibility::Private => Visibility::Private,
587
+ node::MethodDefinitionVisibility::Public | node::MethodDefinitionVisibility::Unspecified => {
588
+ Visibility::Public
589
+ }
590
+ };
591
+ (
592
+ vis,
593
+ Some(Receiver::SelfReceiver(
594
+ lexical_nesting_id.expect("Singleton method must have a lexical enclosing scope"),
595
+ )),
596
+ )
597
+ }
598
+ node::MethodDefinitionKind::SingletonInstance => unreachable!("handled above"),
599
+ };
600
+
601
+ let definition = Definition::Method(Box::new(MethodDefinition::new(
602
+ str_id,
603
+ self.uri_id,
604
+ offset,
605
+ comments,
606
+ flags,
607
+ lexical_nesting_id,
608
+ signatures,
609
+ visibility,
610
+ receiver,
611
+ )));
612
+
613
+ self.register_definition(definition, lexical_nesting_id);
614
+ }
615
+
616
+ fn visit_public_node(&mut self, _public_node: &node::PublicNode) {
617
+ self.current_visibility = Visibility::Public;
618
+ }
619
+
620
+ fn visit_private_node(&mut self, _private_node: &node::PrivateNode) {
621
+ self.current_visibility = Visibility::Private;
622
+ }
623
+ }
624
+
625
+ #[cfg(test)]
626
+ mod tests {
627
+ use ruby_rbs::node::{self, Node, NodeList};
628
+
629
+ use crate::indexing::rbs_indexer::RBSIndexer;
630
+ use crate::model::definitions::{Definition, DefinitionFlags, Parameter, Signatures};
631
+ use crate::model::visibility::Visibility;
632
+ use crate::test_utils::LocalGraphTest;
633
+ use crate::{
634
+ assert_def_comments_eq, assert_def_mixins_eq, assert_def_name_eq, assert_def_name_offset_eq, assert_def_str_eq,
635
+ assert_def_superclass_ref_eq, assert_definition_at, assert_local_diagnostics_eq, assert_method_has_receiver,
636
+ assert_no_local_diagnostics, assert_offset_string, assert_string_eq,
637
+ };
638
+
639
+ macro_rules! assert_parameter {
640
+ ($expr:expr, $variant:ident, |$param:ident| $body:block) => {
641
+ match $expr {
642
+ Parameter::$variant($param) => $body,
643
+ _ => panic!(
644
+ "parameter kind mismatch: expected `{}`, got `{:?}`",
645
+ stringify!($variant),
646
+ $expr
647
+ ),
648
+ }
649
+ };
650
+ }
651
+
652
+ fn index_source(source: &str) -> LocalGraphTest {
653
+ LocalGraphTest::new_rbs("file:///foo.rbs", source)
654
+ }
655
+
656
+ #[test]
657
+ fn index_source_with_errors() {
658
+ let context = index_source("module");
659
+
660
+ assert_local_diagnostics_eq!(&context, ["parse-error: Failed to parse RBS document (1:1-1:1)"]);
661
+
662
+ assert!(context.graph().definitions().is_empty());
663
+ }
664
+
665
+ #[test]
666
+ fn index_module_node() {
667
+ let context = index_source({
668
+ "
669
+ module Foo
670
+ module Bar
671
+ end
672
+ end
673
+ "
674
+ });
675
+
676
+ assert_no_local_diagnostics!(&context);
677
+ assert_eq!(context.graph().definitions().len(), 2);
678
+
679
+ assert_definition_at!(&context, "1:1-4:4", Module, |def| {
680
+ assert_def_name_eq!(&context, def, "Foo");
681
+ assert_def_name_offset_eq!(&context, def, "1:8-1:11");
682
+ assert_eq!(1, def.members().len());
683
+ assert!(def.lexical_nesting_id().is_none());
684
+ });
685
+
686
+ assert_definition_at!(&context, "2:3-3:6", Module, |def| {
687
+ assert_def_name_eq!(&context, def, "Bar");
688
+ assert_def_name_offset_eq!(&context, def, "2:10-2:13");
689
+
690
+ assert_definition_at!(&context, "1:1-4:4", Module, |parent_nesting| {
691
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
692
+ assert_eq!(parent_nesting.members()[0], def.id());
693
+ });
694
+ });
695
+ }
696
+
697
+ #[test]
698
+ fn index_module_node_with_qualified_name() {
699
+ let context = index_source({
700
+ "
701
+ module Foo
702
+ module Bar::Baz
703
+ end
704
+ end
705
+ "
706
+ });
707
+
708
+ assert_no_local_diagnostics!(&context);
709
+ assert_eq!(context.graph().definitions().len(), 2);
710
+
711
+ assert_definition_at!(&context, "1:1-4:4", Module, |def| {
712
+ assert_def_name_eq!(&context, def, "Foo");
713
+ assert_def_name_offset_eq!(&context, def, "1:8-1:11");
714
+ assert_eq!(1, def.members().len());
715
+ assert!(def.lexical_nesting_id().is_none());
716
+ });
717
+
718
+ assert_definition_at!(&context, "2:3-3:6", Module, |def| {
719
+ assert_def_name_eq!(&context, def, "Bar::Baz");
720
+ assert_def_name_offset_eq!(&context, def, "2:15-2:18");
721
+
722
+ assert_definition_at!(&context, "1:1-4:4", Module, |parent_nesting| {
723
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
724
+ assert_eq!(parent_nesting.members()[0], def.id());
725
+ });
726
+ });
727
+ }
728
+
729
+ #[test]
730
+ fn index_class_node() {
731
+ let context = index_source({
732
+ "
733
+ class Foo
734
+ class Bar
735
+ end
736
+ end
737
+ "
738
+ });
739
+
740
+ assert_no_local_diagnostics!(&context);
741
+ assert_eq!(context.graph().definitions().len(), 2);
742
+
743
+ assert_definition_at!(&context, "1:1-4:4", Class, |def| {
744
+ assert_def_name_eq!(&context, def, "Foo");
745
+ assert_def_name_offset_eq!(&context, def, "1:7-1:10");
746
+ assert_eq!(1, def.members().len());
747
+ assert!(def.lexical_nesting_id().is_none());
748
+ });
749
+
750
+ assert_definition_at!(&context, "2:3-3:6", Class, |def| {
751
+ assert_def_name_eq!(&context, def, "Bar");
752
+ assert_def_name_offset_eq!(&context, def, "2:9-2:12");
753
+
754
+ assert_definition_at!(&context, "1:1-4:4", Class, |parent_nesting| {
755
+ assert_eq!(parent_nesting.id(), def.lexical_nesting_id().unwrap());
756
+ assert_eq!(parent_nesting.members()[0], def.id());
757
+ });
758
+ });
759
+ }
760
+
761
+ #[test]
762
+ fn index_class_node_with_superclass() {
763
+ let context = index_source({
764
+ "
765
+ class Foo < Bar
766
+ end
767
+ "
768
+ });
769
+
770
+ assert_no_local_diagnostics!(&context);
771
+
772
+ assert_definition_at!(&context, "1:1-2:4", Class, |def| {
773
+ assert_def_name_eq!(&context, def, "Foo");
774
+ assert_def_superclass_ref_eq!(&context, def, "Bar");
775
+ });
776
+ }
777
+
778
+ #[test]
779
+ fn index_constant_node() {
780
+ let context = index_source("FOO: String");
781
+
782
+ assert_no_local_diagnostics!(&context);
783
+ assert_eq!(context.graph().definitions().len(), 1);
784
+
785
+ assert_definition_at!(&context, "1:1-1:12", Constant, |def| {
786
+ assert_def_name_eq!(&context, def, "FOO");
787
+ assert!(def.lexical_nesting_id().is_none());
788
+ });
789
+ }
790
+
791
+ #[test]
792
+ fn index_qualified_constant_node() {
793
+ let context = index_source("Foo::BAR: String");
794
+
795
+ assert_no_local_diagnostics!(&context);
796
+ assert_eq!(context.graph().definitions().len(), 1);
797
+
798
+ assert_definition_at!(&context, "1:1-1:17", Constant, |def| {
799
+ assert_def_name_eq!(&context, def, "Foo::BAR");
800
+ });
801
+ }
802
+
803
+ #[test]
804
+ fn index_constant_inside_class() {
805
+ let context = index_source({
806
+ "
807
+ class Foo
808
+ FOO: Integer
809
+ end
810
+ "
811
+ });
812
+
813
+ assert_no_local_diagnostics!(&context);
814
+
815
+ assert_definition_at!(&context, "1:1-3:4", Class, |class_def| {
816
+ assert_def_name_eq!(&context, class_def, "Foo");
817
+ assert_eq!(1, class_def.members().len());
818
+
819
+ assert_definition_at!(&context, "2:3-2:15", Constant, |def| {
820
+ assert_def_name_eq!(&context, def, "FOO");
821
+ assert_eq!(class_def.id(), def.lexical_nesting_id().unwrap());
822
+ assert_eq!(class_def.members()[0], def.id());
823
+ });
824
+ });
825
+ }
826
+
827
+ #[test]
828
+ fn index_constant_node_with_comment() {
829
+ let context = index_source({
830
+ "
831
+ # Some documentation
832
+ FOO: String
833
+ "
834
+ });
835
+
836
+ assert_no_local_diagnostics!(&context);
837
+
838
+ assert_definition_at!(&context, "2:1-2:12", Constant, |def| {
839
+ assert_def_name_eq!(&context, def, "FOO");
840
+ assert_def_comments_eq!(&context, def, ["# Some documentation"]);
841
+ });
842
+ }
843
+
844
+ #[test]
845
+ fn index_global_node() {
846
+ let context = index_source({
847
+ "
848
+ $foo: String
849
+
850
+ # A global variable
851
+ $bar: Integer
852
+ "
853
+ });
854
+
855
+ assert_no_local_diagnostics!(&context);
856
+ assert_eq!(context.graph().definitions().len(), 2);
857
+
858
+ assert_definition_at!(&context, "1:1-1:13", GlobalVariable, |def| {
859
+ assert_def_str_eq!(&context, def, "$foo");
860
+ assert!(def.lexical_nesting_id().is_none());
861
+ });
862
+
863
+ assert_definition_at!(&context, "4:1-4:14", GlobalVariable, |def| {
864
+ assert_def_str_eq!(&context, def, "$bar");
865
+ assert_def_comments_eq!(&context, def, ["# A global variable"]);
866
+ });
867
+ }
868
+
869
+ #[test]
870
+ fn index_mixins() {
871
+ let context = index_source({
872
+ "
873
+ class Foo
874
+ include Bar
875
+ prepend Baz
876
+ extend Qux
877
+ end
878
+ "
879
+ });
880
+
881
+ assert_no_local_diagnostics!(&context);
882
+
883
+ assert_definition_at!(&context, "1:1-5:4", Class, |def| {
884
+ assert_def_mixins_eq!(&context, def, Include, ["Bar"]);
885
+ assert_def_mixins_eq!(&context, def, Prepend, ["Baz"]);
886
+ assert_def_mixins_eq!(&context, def, Extend, ["Qux"]);
887
+ });
888
+ }
889
+
890
+ #[test]
891
+ fn index_multiple_includes() {
892
+ let context = index_source({
893
+ "
894
+ module Foo
895
+ include Bar
896
+ include Baz
897
+ end
898
+ "
899
+ });
900
+
901
+ assert_no_local_diagnostics!(&context);
902
+
903
+ assert_definition_at!(&context, "1:1-4:4", Module, |def| {
904
+ assert_def_mixins_eq!(&context, def, Include, ["Bar", "Baz"]);
905
+ });
906
+ }
907
+
908
+ #[test]
909
+ fn index_include_qualified_name() {
910
+ let context = index_source({
911
+ "
912
+ class Foo
913
+ include Bar::Baz
914
+ end
915
+ "
916
+ });
917
+
918
+ assert_no_local_diagnostics!(&context);
919
+
920
+ assert_definition_at!(&context, "1:1-3:4", Class, |def| {
921
+ assert_def_mixins_eq!(&context, def, Include, ["Baz"]);
922
+ });
923
+ }
924
+
925
+ #[test]
926
+ fn index_class_and_module_nesting() {
927
+ let context = index_source({
928
+ "
929
+ module Foo
930
+ class Bar
931
+ end
932
+ end
933
+ "
934
+ });
935
+
936
+ assert_no_local_diagnostics!(&context);
937
+ assert_eq!(context.graph().definitions().len(), 2);
938
+
939
+ assert_definition_at!(&context, "1:1-4:4", Module, |module_def| {
940
+ assert_def_name_eq!(&context, module_def, "Foo");
941
+ assert_eq!(1, module_def.members().len());
942
+
943
+ assert_definition_at!(&context, "2:3-3:6", Class, |class_def| {
944
+ assert_eq!(module_def.members()[0], class_def.id());
945
+ assert_def_name_eq!(&context, class_def, "Bar");
946
+ assert_eq!(module_def.id(), class_def.lexical_nesting_id().unwrap());
947
+ });
948
+ });
949
+ }
950
+
951
+ #[test]
952
+ fn flags_test_deprecation() {
953
+ fn extract_annotations<F: FnOnce(&NodeList)>(annots: &[u8], f: F) {
954
+ let source = format!("{} module Foo end", std::str::from_utf8(annots).unwrap());
955
+ let Ok(signature) = node::parse(&source) else {
956
+ panic!("Failed to parse RBS source");
957
+ };
958
+ let decl = signature
959
+ .declarations()
960
+ .iter()
961
+ .next()
962
+ .expect("Expected at least one declaration");
963
+ let Node::Module(module_node) = decl else {
964
+ panic!("Expected a module declaration");
965
+ };
966
+ f(&module_node.annotations());
967
+ }
968
+
969
+ extract_annotations(b"%a{deprecated}", |list| {
970
+ assert!(RBSIndexer::flags(list).contains(DefinitionFlags::DEPRECATED));
971
+ });
972
+ extract_annotations(b"%a{deprecated: Some message here}", |list| {
973
+ assert!(RBSIndexer::flags(list).contains(DefinitionFlags::DEPRECATED));
974
+ });
975
+ extract_annotations(b"%a{deprecated} %a{pure}", |list| {
976
+ assert!(RBSIndexer::flags(list).contains(DefinitionFlags::DEPRECATED));
977
+ });
978
+ extract_annotations(b"", |list| {
979
+ assert!(!RBSIndexer::flags(list).contains(DefinitionFlags::DEPRECATED));
980
+ });
981
+ extract_annotations(b"%a{deprecatedxxxx}", |list| {
982
+ assert!(!RBSIndexer::flags(list).contains(DefinitionFlags::DEPRECATED));
983
+ });
984
+ }
985
+
986
+ #[test]
987
+ fn index_declarations_with_deprecation() {
988
+ let context = index_source({
989
+ "
990
+ %a{deprecated}
991
+ module Foo
992
+ end
993
+
994
+ %a{deprecated: Use Bar2 instead}
995
+ class Bar
996
+ end
997
+
998
+ %a(deprecated)
999
+ FOO: String
1000
+
1001
+ %a[deprecated]
1002
+ $BAR: String
1003
+ "
1004
+ });
1005
+
1006
+ assert_no_local_diagnostics!(&context);
1007
+
1008
+ assert_definition_at!(&context, "2:1-3:4", Module, |def| {
1009
+ assert_def_name_eq!(&context, def, "Foo");
1010
+ assert!(def.flags().contains(DefinitionFlags::DEPRECATED));
1011
+ });
1012
+
1013
+ assert_definition_at!(&context, "6:1-7:4", Class, |def| {
1014
+ assert_def_name_eq!(&context, def, "Bar");
1015
+ assert!(def.flags().contains(DefinitionFlags::DEPRECATED));
1016
+ });
1017
+
1018
+ assert_definition_at!(&context, "10:1-10:12", Constant, |def| {
1019
+ assert_def_name_eq!(&context, def, "FOO");
1020
+ assert!(def.flags().contains(DefinitionFlags::DEPRECATED));
1021
+ });
1022
+
1023
+ assert_definition_at!(&context, "13:1-13:13", GlobalVariable, |def| {
1024
+ assert_def_str_eq!(&context, def, "$BAR");
1025
+ assert!(def.flags().contains(DefinitionFlags::DEPRECATED));
1026
+ });
1027
+ }
1028
+
1029
+ #[test]
1030
+ fn index_alias_node() {
1031
+ let context = index_source({
1032
+ "
1033
+ class Foo
1034
+ # Some documentation
1035
+ alias bar baz
1036
+ end
1037
+ "
1038
+ });
1039
+
1040
+ assert_no_local_diagnostics!(&context);
1041
+
1042
+ assert_definition_at!(&context, "1:1-4:4", Class, |class_def| {
1043
+ assert_eq!(1, class_def.members().len());
1044
+
1045
+ assert_definition_at!(&context, "3:3-3:16", MethodAlias, |def| {
1046
+ assert_string_eq!(&context, def.new_name_str_id(), "bar()");
1047
+ assert_string_eq!(&context, def.old_name_str_id(), "baz()");
1048
+ assert_def_comments_eq!(&context, def, ["# Some documentation"]);
1049
+ assert_eq!(class_def.id(), def.lexical_nesting_id().unwrap());
1050
+ });
1051
+ });
1052
+ }
1053
+
1054
+ #[test]
1055
+ fn index_alias_node_with_deprecation() {
1056
+ let context = index_source({
1057
+ "
1058
+ class Foo
1059
+ %a{deprecated}
1060
+ alias bar baz
1061
+ end
1062
+ "
1063
+ });
1064
+
1065
+ assert_no_local_diagnostics!(&context);
1066
+
1067
+ assert_definition_at!(&context, "3:3-3:16", MethodAlias, |def| {
1068
+ assert!(def.flags().contains(DefinitionFlags::DEPRECATED));
1069
+ });
1070
+ }
1071
+
1072
+ #[test]
1073
+ fn index_alias_node_singleton() {
1074
+ let context = index_source({
1075
+ "
1076
+ class Foo
1077
+ alias self.bar self.baz
1078
+ end
1079
+ "
1080
+ });
1081
+
1082
+ assert_no_local_diagnostics!(&context);
1083
+ assert_eq!(context.graph().definitions().len(), 2);
1084
+
1085
+ assert_definition_at!(&context, "2:3-2:26", MethodAlias, |def| {
1086
+ assert_string_eq!(&context, def.new_name_str_id(), "bar()");
1087
+ assert_string_eq!(&context, def.old_name_str_id(), "baz()");
1088
+ assert_method_has_receiver!(&context, def, "Foo");
1089
+ });
1090
+ }
1091
+
1092
+ #[test]
1093
+ fn mixed_singleton_instance_alias_is_not_indexed() {
1094
+ // Mixed aliases (`alias self.x y` and `alias x self.y`) are not valid RBS.
1095
+ // Verify that no alias definitions are produced for these inputs.
1096
+ for source in [
1097
+ "
1098
+ class Foo
1099
+ alias self.bar baz
1100
+ end
1101
+ ",
1102
+ "
1103
+ class Foo
1104
+ alias bar self.baz
1105
+ end
1106
+ ",
1107
+ ] {
1108
+ let context = index_source(source);
1109
+ let has_alias = context
1110
+ .graph()
1111
+ .definitions()
1112
+ .values()
1113
+ .any(|d| matches!(d, Definition::MethodAlias(_)));
1114
+ assert!(!has_alias, "Expected no alias definitions for: {source}");
1115
+ }
1116
+ }
1117
+
1118
+ #[test]
1119
+ fn split_multiline_comments() {
1120
+ let context = index_source({
1121
+ "
1122
+ # First line
1123
+ # Second line
1124
+ # Third line
1125
+ class Foo
1126
+ # A comment for Bar
1127
+ # Another line for Bar
1128
+ # One more line for Bar
1129
+ module Bar
1130
+ end
1131
+ end
1132
+
1133
+ # splits strings at the \\n char
1134
+ BAZ: Integer
1135
+ "
1136
+ });
1137
+
1138
+ assert_no_local_diagnostics!(&context);
1139
+
1140
+ assert_definition_at!(&context, "4:1-10:4", Class, |def| {
1141
+ assert_def_name_eq!(&context, def, "Foo");
1142
+ assert_def_comments_eq!(&context, def, ["# First line", "# Second line", "# Third line"]);
1143
+ });
1144
+
1145
+ assert_definition_at!(&context, "8:3-9:6", Module, |def| {
1146
+ assert_def_name_eq!(&context, def, "Bar");
1147
+ assert_def_comments_eq!(
1148
+ &context,
1149
+ def,
1150
+ [
1151
+ "# A comment for Bar",
1152
+ "# Another line for Bar",
1153
+ "# One more line for Bar"
1154
+ ]
1155
+ );
1156
+ });
1157
+
1158
+ assert_definition_at!(&context, "13:1-13:13", Constant, |def| {
1159
+ assert_def_name_eq!(&context, def, "BAZ");
1160
+ assert_def_comments_eq!(&context, def, ["# splits strings at the \\n char"]);
1161
+ });
1162
+ }
1163
+
1164
+ #[test]
1165
+ fn split_multiline_comments_crlf() {
1166
+ // Build the indexer directly to bypass normalize_indentation, which strips \r
1167
+ let source = "# First line\r\n# Second line\r\nclass Foo\r\nend\r\n";
1168
+ let mut indexer = RBSIndexer::new("file:///foo.rbs".to_string(), source);
1169
+ indexer.index();
1170
+ let context = LocalGraphTest::from_local_graph("file:///foo.rbs", source, indexer.local_graph());
1171
+
1172
+ assert_no_local_diagnostics!(&context);
1173
+
1174
+ assert_definition_at!(&context, "3:1-4:4", Class, |def| {
1175
+ assert_def_name_eq!(&context, def, "Foo");
1176
+ assert_def_comments_eq!(&context, def, ["# First line", "# Second line"]);
1177
+ });
1178
+ }
1179
+
1180
+ #[test]
1181
+ fn index_method_definition() {
1182
+ let context = index_source({
1183
+ "
1184
+ class Foo
1185
+ def foo: () -> void
1186
+
1187
+ def bar: (?) -> void
1188
+ end
1189
+ "
1190
+ });
1191
+
1192
+ assert_no_local_diagnostics!(&context);
1193
+
1194
+ assert_definition_at!(&context, "1:1-5:4", Class, |class_def| {
1195
+ assert_def_name_eq!(&context, class_def, "Foo");
1196
+ assert_eq!(2, class_def.members().len());
1197
+
1198
+ assert_definition_at!(&context, "2:3-2:22", Method, |def| {
1199
+ assert_def_str_eq!(&context, def, "foo()");
1200
+ assert!(def.receiver().is_none());
1201
+ assert_eq!(def.visibility(), &Visibility::Public);
1202
+ assert_eq!(class_def.id(), def.lexical_nesting_id().unwrap());
1203
+ assert_eq!(class_def.members()[0], def.id());
1204
+ });
1205
+
1206
+ assert_definition_at!(&context, "4:3-4:23", Method, |def| {
1207
+ assert_def_str_eq!(&context, def, "bar()");
1208
+ assert!(def.receiver().is_none());
1209
+ assert_eq!(def.visibility(), &Visibility::Public);
1210
+ assert_eq!(class_def.id(), def.lexical_nesting_id().unwrap());
1211
+ assert_eq!(class_def.members()[1], def.id());
1212
+ });
1213
+ });
1214
+ }
1215
+
1216
+ #[test]
1217
+ fn index_method_definition_with_parameters() {
1218
+ let context = index_source({
1219
+ "
1220
+ class Foo
1221
+ def foo: (String, ?Integer, *String, Symbol, name: String, ?age: Integer, **untyped) -> void
1222
+
1223
+ def bar: (String a, ?Integer b, *String c, Symbol d, name: String e, ?age: Integer f, **untyped rest) -> void
1224
+ end
1225
+ "
1226
+ });
1227
+
1228
+ assert_no_local_diagnostics!(&context);
1229
+
1230
+ // Method without parameter names
1231
+ assert_definition_at!(&context, "2:3-2:95", Method, |def| {
1232
+ assert_def_str_eq!(&context, def, "foo()");
1233
+ assert_eq!(def.signatures().as_slice()[0].len(), 7);
1234
+
1235
+ assert_parameter!(&def.signatures().as_slice()[0][0], RequiredPositional, |param| {
1236
+ assert_string_eq!(context, param.str(), "arg0");
1237
+ assert_offset_string!(context, param.offset(), "String");
1238
+ });
1239
+
1240
+ assert_parameter!(&def.signatures().as_slice()[0][1], OptionalPositional, |param| {
1241
+ assert_string_eq!(context, param.str(), "arg1");
1242
+ assert_offset_string!(context, param.offset(), "Integer");
1243
+ });
1244
+
1245
+ assert_parameter!(&def.signatures().as_slice()[0][2], RestPositional, |param| {
1246
+ assert_string_eq!(context, param.str(), "args");
1247
+ assert_offset_string!(context, param.offset(), "String");
1248
+ });
1249
+
1250
+ assert_parameter!(&def.signatures().as_slice()[0][3], Post, |param| {
1251
+ assert_string_eq!(context, param.str(), "arg2");
1252
+ assert_offset_string!(context, param.offset(), "Symbol");
1253
+ });
1254
+
1255
+ assert_parameter!(&def.signatures().as_slice()[0][4], RequiredKeyword, |param| {
1256
+ assert_string_eq!(context, param.str(), "name");
1257
+ assert_offset_string!(context, param.offset(), "name");
1258
+ });
1259
+
1260
+ assert_parameter!(&def.signatures().as_slice()[0][5], OptionalKeyword, |param| {
1261
+ assert_string_eq!(context, param.str(), "age");
1262
+ assert_offset_string!(context, param.offset(), "age");
1263
+ });
1264
+
1265
+ assert_parameter!(&def.signatures().as_slice()[0][6], RestKeyword, |param| {
1266
+ assert_string_eq!(context, param.str(), "kwargs");
1267
+ assert_offset_string!(context, param.offset(), "untyped");
1268
+ });
1269
+ });
1270
+
1271
+ // Method with parameter names
1272
+ assert_definition_at!(&context, "4:3-4:112", Method, |def| {
1273
+ assert_def_str_eq!(&context, def, "bar()");
1274
+ assert_eq!(def.signatures().as_slice()[0].len(), 7);
1275
+
1276
+ assert_parameter!(&def.signatures().as_slice()[0][0], RequiredPositional, |param| {
1277
+ assert_string_eq!(context, param.str(), "a");
1278
+ assert_offset_string!(context, param.offset(), "a");
1279
+ });
1280
+
1281
+ assert_parameter!(&def.signatures().as_slice()[0][1], OptionalPositional, |param| {
1282
+ assert_string_eq!(context, param.str(), "b");
1283
+ assert_offset_string!(context, param.offset(), "b");
1284
+ });
1285
+
1286
+ assert_parameter!(&def.signatures().as_slice()[0][2], RestPositional, |param| {
1287
+ assert_string_eq!(context, param.str(), "c");
1288
+ assert_offset_string!(context, param.offset(), "c");
1289
+ });
1290
+
1291
+ assert_parameter!(&def.signatures().as_slice()[0][3], Post, |param| {
1292
+ assert_string_eq!(context, param.str(), "d");
1293
+ assert_offset_string!(context, param.offset(), "d");
1294
+ });
1295
+
1296
+ assert_parameter!(&def.signatures().as_slice()[0][4], RequiredKeyword, |param| {
1297
+ assert_string_eq!(context, param.str(), "name");
1298
+ assert_offset_string!(context, param.offset(), "name");
1299
+ });
1300
+
1301
+ assert_parameter!(&def.signatures().as_slice()[0][5], OptionalKeyword, |param| {
1302
+ assert_string_eq!(context, param.str(), "age");
1303
+ assert_offset_string!(context, param.offset(), "age");
1304
+ });
1305
+
1306
+ assert_parameter!(&def.signatures().as_slice()[0][6], RestKeyword, |param| {
1307
+ assert_string_eq!(context, param.str(), "rest");
1308
+ assert_offset_string!(context, param.offset(), "rest");
1309
+ });
1310
+ });
1311
+ }
1312
+
1313
+ #[test]
1314
+ fn index_method_definition_with_multiple_overloads() {
1315
+ let context = index_source({
1316
+ "
1317
+ class Foo
1318
+ def foo: (String) -> Integer
1319
+ | (Integer) -> String
1320
+ | (Symbol, String) -> void
1321
+ end
1322
+ "
1323
+ });
1324
+
1325
+ assert_no_local_diagnostics!(&context);
1326
+
1327
+ assert_definition_at!(&context, "2:3-4:36", Method, |def| {
1328
+ assert_def_str_eq!(&context, def, "foo()");
1329
+ let sigs = def.signatures().as_slice();
1330
+ assert_eq!(sigs.len(), 3);
1331
+
1332
+ // First overload: (String) -> Integer
1333
+ assert_eq!(sigs[0].len(), 1);
1334
+ assert_parameter!(&sigs[0][0], RequiredPositional, |param| {
1335
+ assert_string_eq!(context, param.str(), "arg0");
1336
+ });
1337
+
1338
+ // Second overload: (Integer) -> String
1339
+ assert_eq!(sigs[1].len(), 1);
1340
+ assert_parameter!(&sigs[1][0], RequiredPositional, |param| {
1341
+ assert_string_eq!(context, param.str(), "arg0");
1342
+ });
1343
+
1344
+ // Third overload: (Symbol, String) -> void
1345
+ assert_eq!(sigs[2].len(), 2);
1346
+ assert_parameter!(&sigs[2][0], RequiredPositional, |param| {
1347
+ assert_string_eq!(context, param.str(), "arg0");
1348
+ });
1349
+ assert_parameter!(&sigs[2][1], RequiredPositional, |param| {
1350
+ assert_string_eq!(context, param.str(), "arg1");
1351
+ });
1352
+
1353
+ assert!(matches!(def.signatures(), Signatures::Overloaded(_)));
1354
+ });
1355
+ }
1356
+
1357
+ #[test]
1358
+ fn index_method_definition_with_dot_dot_dot() {
1359
+ let context = index_source({
1360
+ "
1361
+ class Foo
1362
+ def to_s: ...
1363
+ end
1364
+ "
1365
+ });
1366
+
1367
+ assert_no_local_diagnostics!(&context);
1368
+
1369
+ assert_definition_at!(&context, "2:3-2:16", Method, |def| {
1370
+ assert_def_str_eq!(&context, def, "to_s()");
1371
+ let sigs = def.signatures().as_slice();
1372
+ assert_eq!(sigs.len(), 1);
1373
+ assert_eq!(sigs[0].len(), 0);
1374
+ assert!(matches!(def.signatures(), Signatures::Simple(_)));
1375
+ });
1376
+ }
1377
+
1378
+ #[test]
1379
+ fn index_method_definition_module_function() {
1380
+ let context = index_source({
1381
+ "
1382
+ class Foo
1383
+ def self?.foo: () -> void
1384
+ end
1385
+ "
1386
+ });
1387
+
1388
+ assert_no_local_diagnostics!(&context);
1389
+
1390
+ let definitions = context.all_definitions_at("2:3-2:28");
1391
+ assert_eq!(definitions.len(), 2, "module_function should create two definitions");
1392
+
1393
+ let instance_method = definitions
1394
+ .iter()
1395
+ .find(|d| matches!(d, Definition::Method(m) if m.receiver().is_none()))
1396
+ .expect("should have instance method definition");
1397
+ let Definition::Method(instance_method) = instance_method else {
1398
+ panic!()
1399
+ };
1400
+ assert_def_str_eq!(&context, instance_method, "foo()");
1401
+ assert_eq!(instance_method.visibility(), &Visibility::Private);
1402
+
1403
+ let singleton_method = definitions
1404
+ .iter()
1405
+ .find(|d| matches!(d, Definition::Method(m) if m.receiver().is_some()))
1406
+ .expect("should have singleton method definition");
1407
+ let Definition::Method(singleton_method) = singleton_method else {
1408
+ panic!()
1409
+ };
1410
+ assert_def_str_eq!(&context, singleton_method, "foo()");
1411
+ assert_eq!(singleton_method.visibility(), &Visibility::Public);
1412
+ }
1413
+
1414
+ #[test]
1415
+ fn index_method_definition_singleton_method() {
1416
+ let context = index_source({
1417
+ "
1418
+ class Foo
1419
+ def self.foo: () -> void
1420
+ end
1421
+ "
1422
+ });
1423
+
1424
+ assert_no_local_diagnostics!(&context);
1425
+
1426
+ assert_definition_at!(&context, "2:3-2:27", Method, |def| {
1427
+ assert_def_str_eq!(&context, def, "foo()");
1428
+ let sigs = def.signatures().as_slice();
1429
+ assert_eq!(sigs.len(), 1);
1430
+ assert_eq!(sigs[0].len(), 0);
1431
+ assert!(matches!(def.signatures(), Signatures::Simple(_)));
1432
+ assert_method_has_receiver!(&context, def, "Foo");
1433
+ });
1434
+ }
1435
+
1436
+ #[test]
1437
+ fn index_method_definition_public_private_method() {
1438
+ let context = index_source({
1439
+ "
1440
+ class Foo
1441
+ public def foo: () -> void
1442
+
1443
+ private def bar: () -> void
1444
+ end
1445
+ "
1446
+ });
1447
+
1448
+ assert_no_local_diagnostics!(&context);
1449
+
1450
+ assert_definition_at!(&context, "2:3-2:29", Method, |def| {
1451
+ assert_def_str_eq!(&context, def, "foo()");
1452
+ let sigs = def.signatures().as_slice();
1453
+ assert_eq!(sigs.len(), 1);
1454
+ assert_eq!(sigs[0].len(), 0);
1455
+ assert!(matches!(def.signatures(), Signatures::Simple(_)));
1456
+ assert_eq!(def.visibility(), &Visibility::Public);
1457
+ });
1458
+
1459
+ assert_definition_at!(&context, "4:3-4:30", Method, |def| {
1460
+ assert_def_str_eq!(&context, def, "bar()");
1461
+ let sigs = def.signatures().as_slice();
1462
+ assert_eq!(sigs.len(), 1);
1463
+ assert_eq!(sigs[0].len(), 0);
1464
+ assert!(matches!(def.signatures(), Signatures::Simple(_)));
1465
+ assert_eq!(def.visibility(), &Visibility::Private);
1466
+ });
1467
+ }
1468
+
1469
+ #[test]
1470
+ fn index_method_definition_public_private_syntax() {
1471
+ let context = index_source({
1472
+ "
1473
+ class Foo
1474
+ def foo: () -> void
1475
+ def self.foo: () -> void
1476
+
1477
+ public
1478
+
1479
+ def bar: () -> void
1480
+ def self.bar: () -> void
1481
+
1482
+ private
1483
+
1484
+ def baz: () -> void
1485
+ def self.baz: () -> void
1486
+ end
1487
+ "
1488
+ });
1489
+
1490
+ assert_no_local_diagnostics!(&context);
1491
+
1492
+ // Instance method: default visibility is public
1493
+ assert_definition_at!(&context, "2:3-2:22", Method, |def| {
1494
+ assert_def_str_eq!(&context, def, "foo()");
1495
+ assert_eq!(def.visibility(), &Visibility::Public);
1496
+ });
1497
+
1498
+ // Singleton method: always public regardless of current_visibility
1499
+ assert_definition_at!(&context, "3:3-3:27", Method, |def| {
1500
+ assert_def_str_eq!(&context, def, "foo()");
1501
+ assert_eq!(def.visibility(), &Visibility::Public);
1502
+ });
1503
+
1504
+ // Instance method: public due to `public` modifier on line 5
1505
+ assert_definition_at!(&context, "7:3-7:22", Method, |def| {
1506
+ assert_def_str_eq!(&context, def, "bar()");
1507
+ assert_eq!(def.visibility(), &Visibility::Public);
1508
+ });
1509
+
1510
+ // Singleton method: always public regardless of current_visibility
1511
+ assert_definition_at!(&context, "8:3-8:27", Method, |def| {
1512
+ assert_def_str_eq!(&context, def, "bar()");
1513
+ assert_eq!(def.visibility(), &Visibility::Public);
1514
+ });
1515
+
1516
+ // Instance method: private due to `private` modifier on line 10
1517
+ assert_definition_at!(&context, "12:3-12:22", Method, |def| {
1518
+ assert_def_str_eq!(&context, def, "baz()");
1519
+ assert_eq!(def.visibility(), &Visibility::Private);
1520
+ });
1521
+
1522
+ // Singleton method: always public, ignores `private` modifier
1523
+ assert_definition_at!(&context, "13:3-13:27", Method, |def| {
1524
+ assert_def_str_eq!(&context, def, "baz()");
1525
+ assert_eq!(def.visibility(), &Visibility::Public);
1526
+ });
1527
+ }
1528
+
1529
+ #[test]
1530
+ fn index_method_definition_with_block() {
1531
+ let context = index_source({
1532
+ "
1533
+ class Foo
1534
+ def foo: () { (String) -> void } -> void
1535
+ end
1536
+ "
1537
+ });
1538
+
1539
+ assert_no_local_diagnostics!(&context);
1540
+
1541
+ // Method with block: should have 1 parameter (Block)
1542
+ assert_definition_at!(&context, "2:3-2:43", Method, |def| {
1543
+ assert_def_str_eq!(&context, def, "foo()");
1544
+ assert_eq!(def.signatures().as_slice()[0].len(), 1);
1545
+
1546
+ assert_parameter!(&def.signatures().as_slice()[0][0], Block, |param| {
1547
+ assert_string_eq!(context, param.str(), "block");
1548
+ });
1549
+ });
1550
+ }
1551
+ }