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,263 +1,264 @@
1
- use std::collections::HashSet;
2
- use std::io::Write;
3
-
4
- use crate::model::declaration::Declaration;
5
- use crate::model::definitions::Definition;
6
- use crate::model::graph::Graph;
7
- use crate::model::ids::{DefinitionId, NameId, StringId};
8
- use crate::model::name::{NameRef, ParentScope};
9
-
10
- impl Graph {
11
- /// Writes a report of orphan definitions (definitions not linked to any declaration).
12
- ///
13
- /// Format: `type\tconcatenated_name\tlocation` (TSV)
14
- ///
15
- /// # Errors
16
- ///
17
- /// Returns an error if writing fails.
18
- pub fn write_orphan_report(&self, writer: &mut impl Write) -> std::io::Result<()> {
19
- // Collect all definition IDs that are linked to declarations
20
- let linked_definition_ids: HashSet<&DefinitionId> = self
21
- .declarations()
22
- .values()
23
- .flat_map(Declaration::definitions)
24
- .collect();
25
-
26
- // Find orphan definitions
27
- let mut orphans: Vec<_> = self
28
- .definitions()
29
- .iter()
30
- .filter(|(id, _)| !linked_definition_ids.contains(id))
31
- .collect();
32
-
33
- // Sort by type, then by location for consistent output
34
- orphans.sort_by(|(_, a), (_, b)| {
35
- a.kind()
36
- .cmp(b.kind())
37
- .then_with(|| a.uri_id().cmp(b.uri_id()))
38
- .then_with(|| a.offset().cmp(b.offset()))
39
- });
40
-
41
- for (_, definition) in orphans {
42
- let kind = definition.kind();
43
- let name = match definition.name_id().copied() {
44
- Some(id) => self.build_concatenated_name_from_name(id),
45
- None => self.build_concatenated_name_from_lexical_nesting(definition),
46
- };
47
- let location = self.definition_location(definition);
48
-
49
- writeln!(writer, "{kind}\t{name}\t{location}")?;
50
- }
51
-
52
- Ok(())
53
- }
54
-
55
- /// Walks the Name system's `parent_scope` chain to reconstruct the constant path.
56
- /// Falls back to `nesting` for enclosing scope context when there is no explicit parent scope.
57
- ///
58
- /// Note: this produces a concatenated name by piecing together name parts, not a properly
59
- /// resolved qualified name.
60
- pub(crate) fn build_concatenated_name_from_name(&self, name_id: NameId) -> String {
61
- let Some(name_ref) = self.names().get(&name_id) else {
62
- return "<unknown>".to_string();
63
- };
64
- let simple_name = self.string_id_to_string(*name_ref.str());
65
-
66
- match name_ref.parent_scope() {
67
- ParentScope::Some(parent_id) | ParentScope::Attached(parent_id) => {
68
- let parent_name = self.build_concatenated_name_from_name(*parent_id);
69
- format!("{parent_name}::{simple_name}")
70
- }
71
- ParentScope::TopLevel => format!("::{simple_name}"),
72
- ParentScope::None => {
73
- let prefix = name_ref
74
- .nesting()
75
- .as_ref()
76
- .map(|nesting_id| self.build_nesting_prefix(*nesting_id))
77
- .unwrap_or_default();
78
-
79
- if prefix.is_empty() {
80
- simple_name
81
- } else {
82
- format!("{prefix}::{simple_name}")
83
- }
84
- }
85
- }
86
- }
87
-
88
- /// Resolves the enclosing nesting `NameId` to a string prefix.
89
- /// For resolved names, uses the declaration's fully qualified name.
90
- /// For unresolved names, recursively walks the name chain.
91
- fn build_nesting_prefix(&self, nesting_id: NameId) -> String {
92
- let Some(name_ref) = self.names().get(&nesting_id) else {
93
- return String::new();
94
- };
95
- match name_ref {
96
- NameRef::Resolved(resolved) => self
97
- .declarations()
98
- .get(resolved.declaration_id())
99
- .map_or_else(String::new, |decl| decl.name().to_string()),
100
- NameRef::Unresolved(_) => self.build_concatenated_name_from_name(nesting_id),
101
- }
102
- }
103
-
104
- /// Builds a concatenated name for non-constant definitions by walking the `lexical_nesting_id` chain.
105
- ///
106
- /// Note: this pieces together name parts from the lexical nesting, not a properly resolved
107
- /// qualified name.
108
- pub(crate) fn build_concatenated_name_from_lexical_nesting(&self, definition: &Definition) -> String {
109
- let simple_name = self.string_id_to_string(self.definition_string_id(definition));
110
-
111
- // Collect enclosing nesting names from inner to outer
112
- let mut nesting_parts = Vec::new();
113
- let mut current_nesting = *definition.lexical_nesting_id();
114
-
115
- while let Some(nesting_id) = current_nesting {
116
- let Some(nesting_def) = self.definitions().get(&nesting_id) else {
117
- break;
118
- };
119
- nesting_parts.push(self.string_id_to_string(self.definition_string_id(nesting_def)));
120
- current_nesting = *nesting_def.lexical_nesting_id();
121
- }
122
-
123
- if nesting_parts.is_empty() {
124
- return simple_name;
125
- }
126
-
127
- // Reverse to get outer-to-inner order for the prefix
128
- nesting_parts.reverse();
129
- let prefix = nesting_parts.join("::");
130
-
131
- let separator = match definition {
132
- Definition::Method(_)
133
- | Definition::AttrAccessor(_)
134
- | Definition::AttrReader(_)
135
- | Definition::AttrWriter(_)
136
- | Definition::MethodAlias(_)
137
- | Definition::InstanceVariable(_) => "#",
138
- Definition::Class(_)
139
- | Definition::SingletonClass(_)
140
- | Definition::Module(_)
141
- | Definition::Constant(_)
142
- | Definition::ConstantAlias(_)
143
- | Definition::ConstantVisibility(_)
144
- | Definition::GlobalVariable(_)
145
- | Definition::ClassVariable(_)
146
- | Definition::GlobalVariableAlias(_) => "::",
147
- };
148
-
149
- format!("{prefix}{separator}{simple_name}")
150
- }
151
-
152
- /// Converts a `StringId` to its string value.
153
- fn string_id_to_string(&self, string_id: StringId) -> String {
154
- self.strings().get(&string_id).unwrap().to_string()
155
- }
156
-
157
- /// Get location in the format of `uri#L<line>` for a definition.
158
- /// The format is clickable in VS Code.
159
- pub(crate) fn definition_location(&self, definition: &Definition) -> String {
160
- let uri_id = definition.uri_id();
161
-
162
- let Some(document) = self.documents().get(uri_id) else {
163
- return format!("{uri_id}:<unknown>");
164
- };
165
-
166
- let uri = document.uri();
167
- let line_index = document.line_index();
168
- let start = line_index.line_col(definition.offset().start().into());
169
- format!("{uri}#L{}", start.line + 1)
170
- }
171
- }
172
-
173
- #[cfg(test)]
174
- mod tests {
175
- use crate::test_utils::GraphTest;
176
-
177
- #[test]
178
- fn build_concatenated_name_from_name_for_constants() {
179
- let cases = vec![
180
- ("class Foo; end", "Foo"),
181
- ("module Foo; class Bar; end; end", "Foo::Bar"),
182
- ("module Foo; module Bar; class Baz; end; end; end", "Foo::Bar::Baz"),
183
- ];
184
-
185
- for (source, expected_name) in cases {
186
- let mut context = GraphTest::new();
187
- context.index_uri("file:///test.rb", source);
188
- context.resolve();
189
-
190
- let definitions = context.graph().get(expected_name).unwrap();
191
- let definition = definitions.first().unwrap();
192
- let name_id = *definition.name_id().unwrap();
193
- let actual = context.graph().build_concatenated_name_from_name(name_id);
194
-
195
- assert_eq!(actual, expected_name, "For source: {source}");
196
- }
197
- }
198
-
199
- #[test]
200
- fn build_concatenated_name_from_lexical_nesting_for_methods() {
201
- let cases = vec![
202
- ("class Foo; def bar; end; end", "Foo#bar()"),
203
- ("module Foo; class Bar; def baz; end; end; end", "Foo::Bar#baz()"),
204
- ("def bar; end", "bar()"),
205
- ];
206
-
207
- for (source, expected_name) in cases {
208
- let mut context = GraphTest::new();
209
- // Index without resolution so methods remain orphans
210
- context.index_uri("file:///test.rb", source);
211
-
212
- let definition = context
213
- .graph()
214
- .definitions()
215
- .values()
216
- .find(|d| d.kind() == "Method" && d.name_id().is_none())
217
- .unwrap_or_else(|| panic!("No Method definition without name_id found for source: {source}"));
218
-
219
- let actual = context.graph().build_concatenated_name_from_lexical_nesting(definition);
220
- assert_eq!(actual, expected_name, "For source: {source}");
221
- }
222
- }
223
-
224
- #[test]
225
- fn build_concatenated_name_from_lexical_nesting_for_instance_variables() {
226
- let mut context = GraphTest::new();
227
- context.index_uri("file:///test.rb", "class Foo; def initialize; @ivar = 1; end; end");
228
-
229
- let definition = context
230
- .graph()
231
- .definitions()
232
- .values()
233
- .find(|d| d.kind() == "InstanceVariable")
234
- .unwrap();
235
-
236
- let actual = context.graph().build_concatenated_name_from_lexical_nesting(definition);
237
- assert_eq!(actual, "Foo::initialize()#@ivar");
238
- }
239
-
240
- #[test]
241
- fn definition_location_uses_clickable_uri_fragment() {
242
- let mut context = GraphTest::new();
243
- context.index_uri(
244
- "file:///foo.rb",
245
- "
246
- class Foo
247
- def bar
248
- end
249
- end
250
- ",
251
- );
252
-
253
- let definition = context
254
- .graph()
255
- .definitions()
256
- .values()
257
- .find(|d| d.kind() == "Method")
258
- .unwrap();
259
-
260
- let actual = context.graph().definition_location(definition);
261
- assert_eq!(actual, "file:///foo.rb#L2");
262
- }
263
- }
1
+ use std::collections::HashSet;
2
+ use std::io::Write;
3
+
4
+ use crate::model::declaration::Declaration;
5
+ use crate::model::definitions::Definition;
6
+ use crate::model::graph::Graph;
7
+ use crate::model::ids::{DefinitionId, NameId, StringId};
8
+ use crate::model::name::{NameRef, ParentScope};
9
+
10
+ impl Graph {
11
+ /// Writes a report of orphan definitions (definitions not linked to any declaration).
12
+ ///
13
+ /// Format: `type\tconcatenated_name\tlocation` (TSV)
14
+ ///
15
+ /// # Errors
16
+ ///
17
+ /// Returns an error if writing fails.
18
+ pub fn write_orphan_report(&self, writer: &mut impl Write) -> std::io::Result<()> {
19
+ // Collect all definition IDs that are linked to declarations
20
+ let linked_definition_ids: HashSet<&DefinitionId> = self
21
+ .declarations()
22
+ .values()
23
+ .flat_map(Declaration::definitions)
24
+ .collect();
25
+
26
+ // Find orphan definitions
27
+ let mut orphans: Vec<_> = self
28
+ .definitions()
29
+ .iter()
30
+ .filter(|(id, _)| !linked_definition_ids.contains(id))
31
+ .collect();
32
+
33
+ // Sort by type, then by location for consistent output
34
+ orphans.sort_by(|(_, a), (_, b)| {
35
+ a.kind()
36
+ .cmp(b.kind())
37
+ .then_with(|| a.uri_id().cmp(b.uri_id()))
38
+ .then_with(|| a.offset().cmp(b.offset()))
39
+ });
40
+
41
+ for (_, definition) in orphans {
42
+ let kind = definition.kind();
43
+ let name = match definition.name_id().copied() {
44
+ Some(id) => self.build_concatenated_name_from_name(id),
45
+ None => self.build_concatenated_name_from_lexical_nesting(definition),
46
+ };
47
+ let location = self.definition_location(definition);
48
+
49
+ writeln!(writer, "{kind}\t{name}\t{location}")?;
50
+ }
51
+
52
+ Ok(())
53
+ }
54
+
55
+ /// Walks the Name system's `parent_scope` chain to reconstruct the constant path.
56
+ /// Falls back to `nesting` for enclosing scope context when there is no explicit parent scope.
57
+ ///
58
+ /// Note: this produces a concatenated name by piecing together name parts, not a properly
59
+ /// resolved qualified name.
60
+ pub(crate) fn build_concatenated_name_from_name(&self, name_id: NameId) -> String {
61
+ let Some(name_ref) = self.names().get(&name_id) else {
62
+ return "<unknown>".to_string();
63
+ };
64
+ let simple_name = self.string_id_to_string(*name_ref.str());
65
+
66
+ match name_ref.parent_scope() {
67
+ ParentScope::Some(parent_id) | ParentScope::Attached(parent_id) => {
68
+ let parent_name = self.build_concatenated_name_from_name(*parent_id);
69
+ format!("{parent_name}::{simple_name}")
70
+ }
71
+ ParentScope::TopLevel => format!("::{simple_name}"),
72
+ ParentScope::None => {
73
+ let prefix = name_ref
74
+ .nesting()
75
+ .as_ref()
76
+ .map(|nesting_id| self.build_nesting_prefix(*nesting_id))
77
+ .unwrap_or_default();
78
+
79
+ if prefix.is_empty() {
80
+ simple_name
81
+ } else {
82
+ format!("{prefix}::{simple_name}")
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ /// Resolves the enclosing nesting `NameId` to a string prefix.
89
+ /// For resolved names, uses the declaration's fully qualified name.
90
+ /// For unresolved names, recursively walks the name chain.
91
+ fn build_nesting_prefix(&self, nesting_id: NameId) -> String {
92
+ let Some(name_ref) = self.names().get(&nesting_id) else {
93
+ return String::new();
94
+ };
95
+ match name_ref {
96
+ NameRef::Resolved(resolved) => self
97
+ .declarations()
98
+ .get(resolved.declaration_id())
99
+ .map_or_else(String::new, |decl| decl.name().to_string()),
100
+ NameRef::Unresolved(_) => self.build_concatenated_name_from_name(nesting_id),
101
+ }
102
+ }
103
+
104
+ /// Builds a concatenated name for non-constant definitions by walking the `lexical_nesting_id` chain.
105
+ ///
106
+ /// Note: this pieces together name parts from the lexical nesting, not a properly resolved
107
+ /// qualified name.
108
+ pub(crate) fn build_concatenated_name_from_lexical_nesting(&self, definition: &Definition) -> String {
109
+ let simple_name = self.string_id_to_string(self.definition_string_id(definition));
110
+
111
+ // Collect enclosing nesting names from inner to outer
112
+ let mut nesting_parts = Vec::new();
113
+ let mut current_nesting = *definition.lexical_nesting_id();
114
+
115
+ while let Some(nesting_id) = current_nesting {
116
+ let Some(nesting_def) = self.definitions().get(&nesting_id) else {
117
+ break;
118
+ };
119
+ nesting_parts.push(self.string_id_to_string(self.definition_string_id(nesting_def)));
120
+ current_nesting = *nesting_def.lexical_nesting_id();
121
+ }
122
+
123
+ if nesting_parts.is_empty() {
124
+ return simple_name;
125
+ }
126
+
127
+ // Reverse to get outer-to-inner order for the prefix
128
+ nesting_parts.reverse();
129
+ let prefix = nesting_parts.join("::");
130
+
131
+ let separator = match definition {
132
+ Definition::Method(_)
133
+ | Definition::AttrAccessor(_)
134
+ | Definition::AttrReader(_)
135
+ | Definition::AttrWriter(_)
136
+ | Definition::MethodAlias(_)
137
+ | Definition::MethodVisibility(_)
138
+ | Definition::InstanceVariable(_) => "#",
139
+ Definition::Class(_)
140
+ | Definition::SingletonClass(_)
141
+ | Definition::Module(_)
142
+ | Definition::Constant(_)
143
+ | Definition::ConstantAlias(_)
144
+ | Definition::ConstantVisibility(_)
145
+ | Definition::GlobalVariable(_)
146
+ | Definition::ClassVariable(_)
147
+ | Definition::GlobalVariableAlias(_) => "::",
148
+ };
149
+
150
+ format!("{prefix}{separator}{simple_name}")
151
+ }
152
+
153
+ /// Converts a `StringId` to its string value.
154
+ fn string_id_to_string(&self, string_id: StringId) -> String {
155
+ self.strings().get(&string_id).unwrap().to_string()
156
+ }
157
+
158
+ /// Get location in the format of `uri#L<line>` for a definition.
159
+ /// The format is clickable in VS Code.
160
+ pub(crate) fn definition_location(&self, definition: &Definition) -> String {
161
+ let uri_id = definition.uri_id();
162
+
163
+ let Some(document) = self.documents().get(uri_id) else {
164
+ return format!("{uri_id}:<unknown>");
165
+ };
166
+
167
+ let uri = document.uri();
168
+ let line_index = document.line_index();
169
+ let start = line_index.line_col(definition.offset().start().into());
170
+ format!("{uri}#L{}", start.line + 1)
171
+ }
172
+ }
173
+
174
+ #[cfg(test)]
175
+ mod tests {
176
+ use crate::test_utils::GraphTest;
177
+
178
+ #[test]
179
+ fn build_concatenated_name_from_name_for_constants() {
180
+ let cases = vec![
181
+ ("class Foo; end", "Foo"),
182
+ ("module Foo; class Bar; end; end", "Foo::Bar"),
183
+ ("module Foo; module Bar; class Baz; end; end; end", "Foo::Bar::Baz"),
184
+ ];
185
+
186
+ for (source, expected_name) in cases {
187
+ let mut context = GraphTest::new();
188
+ context.index_uri("file:///test.rb", source);
189
+ context.resolve();
190
+
191
+ let definitions = context.graph().get(expected_name).unwrap();
192
+ let definition = definitions.first().unwrap();
193
+ let name_id = *definition.name_id().unwrap();
194
+ let actual = context.graph().build_concatenated_name_from_name(name_id);
195
+
196
+ assert_eq!(actual, expected_name, "For source: {source}");
197
+ }
198
+ }
199
+
200
+ #[test]
201
+ fn build_concatenated_name_from_lexical_nesting_for_methods() {
202
+ let cases = vec![
203
+ ("class Foo; def bar; end; end", "Foo#bar()"),
204
+ ("module Foo; class Bar; def baz; end; end; end", "Foo::Bar#baz()"),
205
+ ("def bar; end", "bar()"),
206
+ ];
207
+
208
+ for (source, expected_name) in cases {
209
+ let mut context = GraphTest::new();
210
+ // Index without resolution so methods remain orphans
211
+ context.index_uri("file:///test.rb", source);
212
+
213
+ let definition = context
214
+ .graph()
215
+ .definitions()
216
+ .values()
217
+ .find(|d| d.kind() == "Method" && d.name_id().is_none())
218
+ .unwrap_or_else(|| panic!("No Method definition without name_id found for source: {source}"));
219
+
220
+ let actual = context.graph().build_concatenated_name_from_lexical_nesting(definition);
221
+ assert_eq!(actual, expected_name, "For source: {source}");
222
+ }
223
+ }
224
+
225
+ #[test]
226
+ fn build_concatenated_name_from_lexical_nesting_for_instance_variables() {
227
+ let mut context = GraphTest::new();
228
+ context.index_uri("file:///test.rb", "class Foo; def initialize; @ivar = 1; end; end");
229
+
230
+ let definition = context
231
+ .graph()
232
+ .definitions()
233
+ .values()
234
+ .find(|d| d.kind() == "InstanceVariable")
235
+ .unwrap();
236
+
237
+ let actual = context.graph().build_concatenated_name_from_lexical_nesting(definition);
238
+ assert_eq!(actual, "Foo::initialize()#@ivar");
239
+ }
240
+
241
+ #[test]
242
+ fn definition_location_uses_clickable_uri_fragment() {
243
+ let mut context = GraphTest::new();
244
+ context.index_uri(
245
+ "file:///foo.rb",
246
+ "
247
+ class Foo
248
+ def bar
249
+ end
250
+ end
251
+ ",
252
+ );
253
+
254
+ let definition = context
255
+ .graph()
256
+ .definitions()
257
+ .values()
258
+ .find(|d| d.kind() == "Method")
259
+ .unwrap();
260
+
261
+ let actual = context.graph().definition_location(definition);
262
+ assert_eq!(actual, "file:///foo.rb#L2");
263
+ }
264
+ }