rubydex 0.1.0.beta11 → 0.1.0.beta13

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