rubydex 0.3.0 → 0.4.0
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.
- checksums.yaml +4 -4
- data/README.md +45 -7
- data/THIRD_PARTY_LICENSES.html +238 -2
- data/exe/rdx +2 -149
- data/ext/rubydex/config.c +140 -0
- data/ext/rubydex/config.h +16 -0
- data/ext/rubydex/diagnostic.c +75 -1
- data/ext/rubydex/diagnostic.h +2 -0
- data/ext/rubydex/graph.c +13 -45
- data/ext/rubydex/graph.h +6 -0
- data/ext/rubydex/query.c +398 -16
- data/ext/rubydex/rubydex.c +2 -0
- data/ext/rubydex/utils.c +11 -4
- data/lib/ruby_lsp/rubydex/addon.rb +211 -0
- data/lib/rubydex/cli/command/console.rb +55 -0
- data/lib/rubydex/cli/command/lint/explain.rb +74 -0
- data/lib/rubydex/cli/command/lint.rb +202 -0
- data/lib/rubydex/cli/command/mcp.rb +30 -0
- data/lib/rubydex/cli/command/query.rb +70 -0
- data/lib/rubydex/cli/command/skill.rb +69 -0
- data/lib/rubydex/cli/command.rb +168 -0
- data/lib/rubydex/cli.rb +93 -0
- data/lib/rubydex/config.rb +59 -0
- data/lib/rubydex/diagnostic.rb +12 -3
- data/lib/rubydex/errors.rb +42 -1
- data/lib/rubydex/graph.rb +10 -3
- data/lib/rubydex/linter/custom_rule.rb +97 -0
- data/lib/rubydex/linter/helpers/path_helpers.rb +78 -0
- data/lib/rubydex/linter/helpers/source_access_helpers.rb +31 -0
- data/lib/rubydex/linter/rule_loader.rb +36 -0
- data/lib/rubydex/linter/rule_test_case.rb +343 -0
- data/lib/rubydex/linter/runner.rb +56 -0
- data/lib/rubydex/linter.rb +19 -0
- data/lib/rubydex/location.rb +3 -0
- data/lib/rubydex/mcp_server.rb +1 -2
- data/lib/rubydex/related_information.rb +17 -0
- data/lib/rubydex/rule.rb +33 -0
- data/lib/rubydex/severity.rb +70 -0
- data/lib/rubydex/skill.rb +88 -0
- data/lib/rubydex/skill_registry.rb +62 -0
- data/lib/rubydex/version.rb +1 -1
- data/lib/rubydex.rb +6 -0
- data/lib/rubydex_linter/rules/rule_structure.rb +125 -0
- data/rbi/rubydex.rbi +558 -17
- data/rust/Cargo.lock +2 -2
- data/rust/rubydex/Cargo.toml +1 -1
- data/rust/rubydex/benches/graph_memory.rs +3 -5
- data/rust/rubydex/src/config.rs +538 -157
- data/rust/rubydex/src/diagnostic.rs +66 -40
- data/rust/rubydex/src/errors.rs +0 -1
- data/rust/rubydex/src/indexing/local_graph.rs +6 -5
- data/rust/rubydex/src/indexing/rbs_indexer.rs +270 -6
- data/rust/rubydex/src/indexing/ruby_indexer.rs +10 -12
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +134 -81
- data/rust/rubydex/src/lib.rs +1 -0
- data/rust/rubydex/src/listing.rs +26 -1
- data/rust/rubydex/src/main.rs +7 -4
- data/rust/rubydex/src/model/declaration.rs +302 -219
- data/rust/rubydex/src/model/graph.rs +27 -40
- data/rust/rubydex/src/model/name.rs +58 -17
- data/rust/rubydex/src/operation/ruby_builder.rs +30 -45
- data/rust/rubydex/src/path_helpers.rs +77 -0
- data/rust/rubydex/src/query/cypher/schema.rs +63 -0
- data/rust/rubydex/src/query/cypher/tests.rs +26 -1
- data/rust/rubydex/src/query/cypher.rs +8 -11
- data/rust/rubydex/src/query.rs +123 -43
- data/rust/rubydex/src/resolution.rs +139 -187
- data/rust/rubydex/src/resolution_tests.rs +247 -19
- data/rust/rubydex/src/test_utils/context.rs +2 -1
- data/rust/rubydex/src/test_utils/graph_test.rs +26 -12
- data/rust/rubydex/src/test_utils/local_graph_test.rs +19 -0
- data/rust/rubydex/tests/cli.rs +4 -4
- data/rust/rubydex-sys/src/config_api.rs +205 -0
- data/rust/rubydex-sys/src/cypher_api.rs +791 -0
- data/rust/rubydex-sys/src/diagnostic_api.rs +77 -8
- data/rust/rubydex-sys/src/graph_api.rs +13 -194
- data/rust/rubydex-sys/src/lib.rs +2 -0
- data/rust/rubydex-sys/src/name_api.rs +2 -6
- data/rust/rubydex-sys/src/utils.rs +37 -0
- data/skills/send-private-method/SKILL.md +133 -0
- metadata +31 -2
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
use std::collections::HashSet;
|
|
2
2
|
use std::collections::hash_map::Entry;
|
|
3
|
-
use std::path::
|
|
3
|
+
use std::path::Path;
|
|
4
4
|
|
|
5
5
|
use crate::config::Config;
|
|
6
6
|
use crate::diagnostic::Diagnostic;
|
|
7
|
-
use crate::errors::Errors;
|
|
8
7
|
use crate::indexing::local_graph::LocalGraph;
|
|
9
8
|
use crate::model::built_in::{OBJECT_ID, add_built_in_data};
|
|
10
9
|
use crate::model::declaration::{Ancestor, Declaration, Namespace};
|
|
@@ -93,7 +92,7 @@ pub struct Graph {
|
|
|
93
92
|
/// Project configuration
|
|
94
93
|
config: Config,
|
|
95
94
|
}
|
|
96
|
-
assert_mem_size!(Graph,
|
|
95
|
+
assert_mem_size!(Graph, 368);
|
|
97
96
|
assert_send_sync!(Graph);
|
|
98
97
|
|
|
99
98
|
impl Graph {
|
|
@@ -110,7 +109,7 @@ impl Graph {
|
|
|
110
109
|
position_encoding: Encoding::default(),
|
|
111
110
|
name_dependents: IdentityHashMap::default(),
|
|
112
111
|
pending_work: Vec::default(),
|
|
113
|
-
config: Config::
|
|
112
|
+
config: Config::default(),
|
|
114
113
|
};
|
|
115
114
|
|
|
116
115
|
add_built_in_data(&mut graph);
|
|
@@ -147,25 +146,9 @@ impl Graph {
|
|
|
147
146
|
self.config.workspace_path()
|
|
148
147
|
}
|
|
149
148
|
|
|
150
|
-
///
|
|
151
|
-
pub fn
|
|
152
|
-
self.config.
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/// Loads a configuration file. Pass `None` to load the default `rubydex.toml` configuration file if it exists
|
|
156
|
-
///
|
|
157
|
-
/// # Errors
|
|
158
|
-
///
|
|
159
|
-
/// Returns an [`Errors::ConfigNotFound`] if an explicitly requested file does not exist or an
|
|
160
|
-
/// [`Errors::ConfigError`] if a file cannot otherwise be read or its contents are malformed.
|
|
161
|
-
pub fn load_config(&mut self, config_path: Option<&Path>) -> Result<(), Errors> {
|
|
162
|
-
match config_path {
|
|
163
|
-
Some(path) => {
|
|
164
|
-
let path = self.config.workspace_path().join(path);
|
|
165
|
-
self.config.load_file(&path)
|
|
166
|
-
}
|
|
167
|
-
None => self.config.load_default(),
|
|
168
|
-
}
|
|
149
|
+
/// Loads a config for the graph
|
|
150
|
+
pub fn load_config(&mut self, config: &Config) {
|
|
151
|
+
self.config = config.clone();
|
|
169
152
|
}
|
|
170
153
|
|
|
171
154
|
/// # Panics
|
|
@@ -207,7 +190,15 @@ impl Graph {
|
|
|
207
190
|
if should_promote {
|
|
208
191
|
let mut new_declaration = constructor(fully_qualified_name);
|
|
209
192
|
let removed_declaration = occupied_entry.remove();
|
|
210
|
-
|
|
193
|
+
let singleton_class_id = removed_declaration
|
|
194
|
+
.as_namespace()
|
|
195
|
+
.and_then(Namespace::singleton_class)
|
|
196
|
+
.copied();
|
|
197
|
+
let new_namespace = new_declaration.as_namespace_mut().unwrap();
|
|
198
|
+
new_namespace.extend(removed_declaration);
|
|
199
|
+
if let Some(singleton_class_id) = singleton_class_id {
|
|
200
|
+
new_namespace.set_singleton_class_id(singleton_class_id);
|
|
201
|
+
}
|
|
211
202
|
new_declaration.add_definition(definition_id);
|
|
212
203
|
self.declarations.insert(declaration_id, new_declaration);
|
|
213
204
|
} else {
|
|
@@ -540,7 +531,8 @@ impl Graph {
|
|
|
540
531
|
/// Registers a name in the graph unless already registered. In regular indexing, this only happens in the local
|
|
541
532
|
/// graph. This method is only used to back the `Graph#resolve_constant` Ruby API because every name must be
|
|
542
533
|
/// registered in the graph to properly resolve
|
|
543
|
-
pub fn add_name(&mut self,
|
|
534
|
+
pub fn add_name(&mut self, str: StringId, parent_scope: ParentScope, nesting: Option<NameId>) -> NameId {
|
|
535
|
+
let name = Name::new(&self.names, str, parent_scope, nesting);
|
|
544
536
|
let name_id = name.id();
|
|
545
537
|
|
|
546
538
|
match self.names.entry(name_id) {
|
|
@@ -893,12 +885,7 @@ impl Graph {
|
|
|
893
885
|
) {
|
|
894
886
|
if let Some(declaration) = self.declarations.get_mut(owner_id) {
|
|
895
887
|
match declaration {
|
|
896
|
-
Declaration::Namespace(
|
|
897
|
-
Declaration::Namespace(Namespace::Module(it)) => it.add_member(member_str_id, member_declaration_id),
|
|
898
|
-
Declaration::Namespace(Namespace::SingletonClass(it)) => {
|
|
899
|
-
it.add_member(member_str_id, member_declaration_id);
|
|
900
|
-
}
|
|
901
|
-
Declaration::Namespace(Namespace::Todo(it)) => it.add_member(member_str_id, member_declaration_id),
|
|
888
|
+
Declaration::Namespace(namespace) => namespace.add_member(member_str_id, member_declaration_id),
|
|
902
889
|
Declaration::Constant(_) => {
|
|
903
890
|
// TODO: temporary hack to avoid crashing on `Struct.new`, `Class.new` and `Module.new`
|
|
904
891
|
}
|
|
@@ -1293,7 +1280,7 @@ impl Graph {
|
|
|
1293
1280
|
&& let Some(anc_decl) = self.declarations.get_mut(&ancestor_id)
|
|
1294
1281
|
&& let Some(ns) = anc_decl.as_namespace_mut()
|
|
1295
1282
|
{
|
|
1296
|
-
ns.remove_descendant(
|
|
1283
|
+
ns.remove_descendant(decl_id);
|
|
1297
1284
|
}
|
|
1298
1285
|
}
|
|
1299
1286
|
}
|
|
@@ -1317,7 +1304,7 @@ impl Graph {
|
|
|
1317
1304
|
&& let Some(anc_decl) = self.declarations.get_mut(ancestor_id)
|
|
1318
1305
|
&& let Some(ns) = anc_decl.as_namespace_mut()
|
|
1319
1306
|
{
|
|
1320
|
-
ns.remove_descendant(
|
|
1307
|
+
ns.remove_descendant(decl_id);
|
|
1321
1308
|
}
|
|
1322
1309
|
}
|
|
1323
1310
|
|
|
@@ -1811,7 +1798,7 @@ mod tests {
|
|
|
1811
1798
|
context.index_uri("file:///a.rb", "");
|
|
1812
1799
|
|
|
1813
1800
|
{
|
|
1814
|
-
let Declaration::Namespace(Namespace::Class(
|
|
1801
|
+
let Declaration::Namespace(foo @ Namespace::Class(_)) =
|
|
1815
1802
|
context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap()
|
|
1816
1803
|
else {
|
|
1817
1804
|
panic!("Expected Foo to be a class");
|
|
@@ -1819,7 +1806,7 @@ mod tests {
|
|
|
1819
1806
|
assert!(matches!(foo.ancestors(), Ancestors::Partial(a) if a.is_empty()));
|
|
1820
1807
|
assert!(foo.descendants().is_empty());
|
|
1821
1808
|
|
|
1822
|
-
let Declaration::Namespace(Namespace::Class(
|
|
1809
|
+
let Declaration::Namespace(baz @ Namespace::Class(_)) =
|
|
1823
1810
|
context.graph().declarations().get(&DeclarationId::from("Baz")).unwrap()
|
|
1824
1811
|
else {
|
|
1825
1812
|
panic!("Expected Baz to be a class");
|
|
@@ -1827,7 +1814,7 @@ mod tests {
|
|
|
1827
1814
|
assert!(matches!(baz.ancestors(), Ancestors::Partial(a) if a.is_empty()));
|
|
1828
1815
|
assert!(baz.descendants().is_empty());
|
|
1829
1816
|
|
|
1830
|
-
let Declaration::Namespace(Namespace::Module(
|
|
1817
|
+
let Declaration::Namespace(bar @ Namespace::Module(_)) =
|
|
1831
1818
|
context.graph().declarations().get(&DeclarationId::from("Bar")).unwrap()
|
|
1832
1819
|
else {
|
|
1833
1820
|
panic!("Expected Bar to be a module");
|
|
@@ -2198,9 +2185,9 @@ mod tests {
|
|
|
2198
2185
|
|
|
2199
2186
|
assert_eq!(
|
|
2200
2187
|
vec![
|
|
2201
|
-
"
|
|
2202
|
-
"
|
|
2203
|
-
"
|
|
2188
|
+
"ParseError: expected an `end` to close the `class` statement (file:///foo1.rb)",
|
|
2189
|
+
"ParseError: unexpected end-of-input, assuming it is closing the parent top level context (file:///foo1.rb)",
|
|
2190
|
+
"ParseWarning: assigned but unused variable - foo (file:///foo2.rb)",
|
|
2204
2191
|
],
|
|
2205
2192
|
diagnostics,
|
|
2206
2193
|
);
|
|
@@ -2522,7 +2509,7 @@ mod tests {
|
|
|
2522
2509
|
);
|
|
2523
2510
|
|
|
2524
2511
|
// Delete bar.rb — the Bar name should be fully removed
|
|
2525
|
-
let bar_name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
|
|
2512
|
+
let bar_name_id = Name::new(context.graph().names(), StringId::from("Bar"), ParentScope::None, None).id();
|
|
2526
2513
|
context.index_uri("file:///bar.rb", "");
|
|
2527
2514
|
context.resolve();
|
|
2528
2515
|
|
|
@@ -4,6 +4,7 @@ use crate::{
|
|
|
4
4
|
assert_mem_size,
|
|
5
5
|
model::{
|
|
6
6
|
id::id_from_parts,
|
|
7
|
+
identity_maps::IdentityHashMap,
|
|
7
8
|
ids::{DeclarationId, NameId, StringId},
|
|
8
9
|
},
|
|
9
10
|
};
|
|
@@ -93,31 +94,58 @@ pub struct Name {
|
|
|
93
94
|
/// list of names to represent the nesting
|
|
94
95
|
nesting: Option<NameId>,
|
|
95
96
|
ref_count: u32,
|
|
97
|
+
/// The depth of the name, which combines the depths of the parent scope and nesting
|
|
98
|
+
depth: u16,
|
|
96
99
|
}
|
|
100
|
+
assert_mem_size!(Name, 40);
|
|
97
101
|
|
|
98
102
|
impl PartialEq for Name {
|
|
99
103
|
fn eq(&self, other: &Self) -> bool {
|
|
100
104
|
self.str == other.str && self.parent_scope == other.parent_scope && self.nesting == other.nesting
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
|
-
assert_mem_size!(Name, 40);
|
|
104
107
|
|
|
105
108
|
impl Name {
|
|
106
109
|
#[must_use]
|
|
107
|
-
pub fn new(
|
|
110
|
+
pub fn new(
|
|
111
|
+
names: &IdentityHashMap<NameId, NameRef>,
|
|
112
|
+
str: StringId,
|
|
113
|
+
parent_scope: ParentScope,
|
|
114
|
+
nesting: Option<NameId>,
|
|
115
|
+
) -> Self {
|
|
108
116
|
Self {
|
|
109
117
|
str,
|
|
110
118
|
parent_scope,
|
|
111
119
|
nesting,
|
|
112
120
|
ref_count: 1,
|
|
121
|
+
depth: Self::name_depth(names, parent_scope, nesting),
|
|
113
122
|
}
|
|
114
123
|
}
|
|
115
124
|
|
|
125
|
+
#[must_use]
|
|
126
|
+
fn name_depth(names: &IdentityHashMap<NameId, NameRef>, parent_scope: ParentScope, nesting: Option<NameId>) -> u16 {
|
|
127
|
+
if parent_scope.is_top_level() {
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let parent_depth = parent_scope
|
|
132
|
+
.as_ref()
|
|
133
|
+
.map_or(0, |id| names.get(id).map_or(0, NameRef::depth));
|
|
134
|
+
let nesting_depth = nesting.map_or(0, |id| names.get(&id).map_or(0, NameRef::depth));
|
|
135
|
+
|
|
136
|
+
parent_depth.saturating_add(nesting_depth).saturating_add(1)
|
|
137
|
+
}
|
|
138
|
+
|
|
116
139
|
#[must_use]
|
|
117
140
|
pub fn str(&self) -> &StringId {
|
|
118
141
|
&self.str
|
|
119
142
|
}
|
|
120
143
|
|
|
144
|
+
#[must_use]
|
|
145
|
+
pub fn depth(&self) -> u16 {
|
|
146
|
+
self.depth
|
|
147
|
+
}
|
|
148
|
+
|
|
121
149
|
#[must_use]
|
|
122
150
|
pub fn parent_scope(&self) -> &ParentScope {
|
|
123
151
|
&self.parent_scope
|
|
@@ -227,6 +255,14 @@ impl NameRef {
|
|
|
227
255
|
}
|
|
228
256
|
}
|
|
229
257
|
|
|
258
|
+
#[must_use]
|
|
259
|
+
pub fn depth(&self) -> u16 {
|
|
260
|
+
match self {
|
|
261
|
+
NameRef::Unresolved(name) => name.depth(),
|
|
262
|
+
NameRef::Resolved(resolved_name) => resolved_name.name.depth(),
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
230
266
|
/// # Panics
|
|
231
267
|
///
|
|
232
268
|
/// Panics if we exceed the maximum size of the reference count
|
|
@@ -281,42 +317,47 @@ mod tests {
|
|
|
281
317
|
|
|
282
318
|
#[test]
|
|
283
319
|
fn same_parent_scope_and_nesting() {
|
|
284
|
-
let
|
|
285
|
-
|
|
320
|
+
let names = IdentityHashMap::default();
|
|
321
|
+
|
|
322
|
+
let name_1 = Name::new(&names, StringId::from("Foo"), ParentScope::None, None);
|
|
323
|
+
let name_2 = Name::new(&names, StringId::from("Foo"), ParentScope::None, None);
|
|
286
324
|
assert_eq!(name_1.id(), name_2.id());
|
|
287
325
|
|
|
288
|
-
let name_3 = Name::new(StringId::from("Foo"), ParentScope::Some(name_1.id()), None);
|
|
289
|
-
let name_4 = Name::new(StringId::from("Foo"), ParentScope::Some(name_2.id()), None);
|
|
326
|
+
let name_3 = Name::new(&names, StringId::from("Foo"), ParentScope::Some(name_1.id()), None);
|
|
327
|
+
let name_4 = Name::new(&names, StringId::from("Foo"), ParentScope::Some(name_2.id()), None);
|
|
290
328
|
assert_eq!(name_3.id(), name_4.id());
|
|
291
329
|
|
|
292
|
-
let name_5 = Name::new(StringId::from("Foo"), ParentScope::None, Some(name_1.id()));
|
|
293
|
-
let name_6 = Name::new(StringId::from("Foo"), ParentScope::None, Some(name_2.id()));
|
|
330
|
+
let name_5 = Name::new(&names, StringId::from("Foo"), ParentScope::None, Some(name_1.id()));
|
|
331
|
+
let name_6 = Name::new(&names, StringId::from("Foo"), ParentScope::None, Some(name_2.id()));
|
|
294
332
|
assert_eq!(name_5.id(), name_6.id());
|
|
295
333
|
assert_ne!(name_3.id(), name_5.id());
|
|
296
334
|
assert_ne!(name_4.id(), name_6.id());
|
|
297
335
|
|
|
298
336
|
let name_7 = Name::new(
|
|
337
|
+
&names,
|
|
299
338
|
StringId::from("Foo"),
|
|
300
|
-
ParentScope::Some(Name::new(StringId::from("Foo"), ParentScope::None, None).id()),
|
|
301
|
-
Some(Name::new(StringId::from("Foo"), ParentScope::None, None).id()),
|
|
339
|
+
ParentScope::Some(Name::new(&names, StringId::from("Foo"), ParentScope::None, None).id()),
|
|
340
|
+
Some(Name::new(&names, StringId::from("Foo"), ParentScope::None, None).id()),
|
|
302
341
|
);
|
|
303
342
|
let name_8 = Name::new(
|
|
343
|
+
&names,
|
|
304
344
|
StringId::from("Foo"),
|
|
305
|
-
ParentScope::Some(Name::new(StringId::from("Foo"), ParentScope::None, None).id()),
|
|
306
|
-
Some(Name::new(StringId::from("Foo"), ParentScope::None, None).id()),
|
|
345
|
+
ParentScope::Some(Name::new(&names, StringId::from("Foo"), ParentScope::None, None).id()),
|
|
346
|
+
Some(Name::new(&names, StringId::from("Foo"), ParentScope::None, None).id()),
|
|
307
347
|
);
|
|
308
348
|
assert_eq!(name_7.id(), name_8.id());
|
|
309
349
|
}
|
|
310
350
|
|
|
311
351
|
#[test]
|
|
312
352
|
fn parent_scope_variants_are_distinct() {
|
|
313
|
-
let
|
|
353
|
+
let names = IdentityHashMap::default();
|
|
354
|
+
let inner = Name::new(&names, StringId::from("Foo"), ParentScope::None, None).id();
|
|
314
355
|
|
|
315
356
|
let ids = [
|
|
316
|
-
Name::new(StringId::from("Foo"), ParentScope::None, None).id(),
|
|
317
|
-
Name::new(StringId::from("Foo"), ParentScope::TopLevel, None).id(),
|
|
318
|
-
Name::new(StringId::from("Foo"), ParentScope::Some(inner), None).id(),
|
|
319
|
-
Name::new(StringId::from("Foo"), ParentScope::Attached(inner), None).id(),
|
|
357
|
+
Name::new(&names, StringId::from("Foo"), ParentScope::None, None).id(),
|
|
358
|
+
Name::new(&names, StringId::from("Foo"), ParentScope::TopLevel, None).id(),
|
|
359
|
+
Name::new(&names, StringId::from("Foo"), ParentScope::Some(inner), None).id(),
|
|
360
|
+
Name::new(&names, StringId::from("Foo"), ParentScope::Attached(inner), None).id(),
|
|
320
361
|
];
|
|
321
362
|
|
|
322
363
|
for (i, a) in ids.iter().enumerate() {
|
|
@@ -154,7 +154,8 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
154
154
|
string_id
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
fn add_name(&mut self,
|
|
157
|
+
fn add_name(&mut self, str: StringId, parent_scope: ParentScope, nesting: Option<NameId>) -> NameId {
|
|
158
|
+
let name = Name::new(&self.names, str, parent_scope, nesting);
|
|
158
159
|
let name_id = name.id();
|
|
159
160
|
|
|
160
161
|
match self.names.entry(name_id) {
|
|
@@ -360,11 +361,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
360
361
|
let offset = Offset::from_prism_location(&location);
|
|
361
362
|
let name = Self::location_to_string(&location);
|
|
362
363
|
let string_id = self.intern_string(name);
|
|
363
|
-
let name_id = self.add_name(
|
|
364
|
-
string_id,
|
|
365
|
-
parent_scope_id,
|
|
366
|
-
self.current_lexical_scope_name_id(),
|
|
367
|
-
));
|
|
364
|
+
let name_id = self.add_name(string_id, parent_scope_id, self.current_lexical_scope_name_id());
|
|
368
365
|
|
|
369
366
|
if push_final_reference {
|
|
370
367
|
self.operations
|
|
@@ -443,7 +440,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
443
440
|
}
|
|
444
441
|
None => {
|
|
445
442
|
let str_id = self.intern_string("Object".into());
|
|
446
|
-
Some(self.add_name(
|
|
443
|
+
Some(self.add_name(str_id, ParentScope::None, None))
|
|
447
444
|
}
|
|
448
445
|
},
|
|
449
446
|
Some(ruby_prism::Node::CallNode { .. }) => {
|
|
@@ -477,7 +474,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
477
474
|
};
|
|
478
475
|
|
|
479
476
|
let string_id = self.intern_string(singleton_class_name);
|
|
480
|
-
let new_name_id = self.add_name(
|
|
477
|
+
let new_name_id = self.add_name(string_id, ParentScope::Attached(name_id), None);
|
|
481
478
|
|
|
482
479
|
let location = receiver.map_or(fallback_location, ruby_prism::Node::location);
|
|
483
480
|
let offset = Offset::from_prism_location(&location);
|
|
@@ -689,10 +686,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
689
686
|
)
|
|
690
687
|
} else {
|
|
691
688
|
let string_id = self.intern_string(format!("{}:{}<anonymous>", self.uri_id, offset.start()));
|
|
692
|
-
(
|
|
693
|
-
Some(self.add_name(Name::new(string_id, ParentScope::None, None))),
|
|
694
|
-
offset.clone(),
|
|
695
|
-
)
|
|
689
|
+
(Some(self.add_name(string_id, ParentScope::None, None)), offset.clone())
|
|
696
690
|
};
|
|
697
691
|
|
|
698
692
|
if let Some(name_id) = name_id {
|
|
@@ -754,10 +748,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
754
748
|
)
|
|
755
749
|
} else {
|
|
756
750
|
let string_id = self.intern_string(format!("{}:{}<anonymous>", self.uri_id, offset.start()));
|
|
757
|
-
(
|
|
758
|
-
Some(self.add_name(Name::new(string_id, ParentScope::None, None))),
|
|
759
|
-
offset.clone(),
|
|
760
|
-
)
|
|
751
|
+
(Some(self.add_name(string_id, ParentScope::None, None)), offset.clone())
|
|
761
752
|
};
|
|
762
753
|
|
|
763
754
|
if let Some(name_id) = name_id {
|
|
@@ -849,7 +840,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
849
840
|
}
|
|
850
841
|
|
|
851
842
|
Some((
|
|
852
|
-
self.
|
|
843
|
+
self.current_owner_name_id().unwrap(),
|
|
853
844
|
Offset::from_prism_location(&arg.location()),
|
|
854
845
|
))
|
|
855
846
|
} else if let Some(name_id) = self.index_constant_reference(&arg, false) {
|
|
@@ -1361,6 +1352,14 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
1361
1352
|
flags,
|
|
1362
1353
|
}));
|
|
1363
1354
|
}
|
|
1355
|
+
|
|
1356
|
+
fn visit_method_body(&mut self, receiver: Option<NestingReceiver>, body: Option<&ruby_prism::Node<'_>>) {
|
|
1357
|
+
self.nesting_stack.push(Nesting::Method { receiver });
|
|
1358
|
+
if let Some(body) = body {
|
|
1359
|
+
self.visit(body);
|
|
1360
|
+
}
|
|
1361
|
+
self.nesting_stack.pop();
|
|
1362
|
+
}
|
|
1364
1363
|
}
|
|
1365
1364
|
|
|
1366
1365
|
struct CommentGroup {
|
|
@@ -1483,7 +1482,7 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1483
1482
|
|
|
1484
1483
|
let string_id = self.intern_string(singleton_class_name);
|
|
1485
1484
|
let nesting = self.current_lexical_scope_name_id();
|
|
1486
|
-
let name_id = self.add_name(
|
|
1485
|
+
let name_id = self.add_name(string_id, ParentScope::Attached(attached_target), nesting);
|
|
1487
1486
|
self.operations
|
|
1488
1487
|
.push(Operation::EnterSingletonClass(op::EnterSingletonClass {
|
|
1489
1488
|
name_id,
|
|
@@ -1567,8 +1566,10 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1567
1566
|
};
|
|
1568
1567
|
|
|
1569
1568
|
if receiver.is_none() && visibility == Visibility::ModuleFunction {
|
|
1570
|
-
//
|
|
1571
|
-
//
|
|
1569
|
+
// Each EnterMethod creates a method definition, so emit one for the public singleton
|
|
1570
|
+
// method and one for the private instance method. Emit body definitions and references
|
|
1571
|
+
// only under the instance method because emitting them in both scopes would duplicate
|
|
1572
|
+
// their source-based IDs.
|
|
1572
1573
|
let singleton_receiver = Some(Target::ExplicitSelf);
|
|
1573
1574
|
let body = node.body();
|
|
1574
1575
|
|
|
@@ -1582,13 +1583,6 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1582
1583
|
signatures: Signatures::Simple(parameters.clone().into_boxed_slice()),
|
|
1583
1584
|
receiver: singleton_receiver,
|
|
1584
1585
|
}));
|
|
1585
|
-
self.nesting_stack.push(Nesting::Method {
|
|
1586
|
-
receiver: method_nesting_receiver,
|
|
1587
|
-
});
|
|
1588
|
-
if let Some(ref body) = body {
|
|
1589
|
-
self.visit(body);
|
|
1590
|
-
}
|
|
1591
|
-
self.nesting_stack.pop();
|
|
1592
1586
|
self.operations.push(Operation::ExitScope);
|
|
1593
1587
|
|
|
1594
1588
|
self.operations.push(Operation::EnterMethod(op::EnterMethod {
|
|
@@ -1601,13 +1595,7 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1601
1595
|
signatures: Signatures::Simple(parameters.into_boxed_slice()),
|
|
1602
1596
|
receiver,
|
|
1603
1597
|
}));
|
|
1604
|
-
self.
|
|
1605
|
-
receiver: method_nesting_receiver,
|
|
1606
|
-
});
|
|
1607
|
-
if let Some(ref body) = body {
|
|
1608
|
-
self.visit(body);
|
|
1609
|
-
}
|
|
1610
|
-
self.nesting_stack.pop();
|
|
1598
|
+
self.visit_method_body(method_nesting_receiver, body.as_ref());
|
|
1611
1599
|
self.operations.push(Operation::ExitScope);
|
|
1612
1600
|
} else {
|
|
1613
1601
|
// Singleton methods at top level have receiver=None (no class to point self to).
|
|
@@ -1636,13 +1624,8 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1636
1624
|
signatures: Signatures::Simple(parameters.into_boxed_slice()),
|
|
1637
1625
|
receiver,
|
|
1638
1626
|
}));
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
});
|
|
1642
|
-
if let Some(body) = node.body() {
|
|
1643
|
-
self.visit(&body);
|
|
1644
|
-
}
|
|
1645
|
-
self.nesting_stack.pop();
|
|
1627
|
+
let body = node.body();
|
|
1628
|
+
self.visit_method_body(method_nesting_receiver, body.as_ref());
|
|
1646
1629
|
self.operations.push(Operation::ExitScope);
|
|
1647
1630
|
|
|
1648
1631
|
if let Some(prev) = previous_visibility {
|
|
@@ -2464,14 +2447,15 @@ mod tests {
|
|
|
2464
2447
|
}
|
|
2465
2448
|
|
|
2466
2449
|
#[test]
|
|
2467
|
-
fn
|
|
2468
|
-
|
|
2450
|
+
fn build_module_function_with_body() {
|
|
2451
|
+
assert_operations_with_references(
|
|
2469
2452
|
"
|
|
2470
2453
|
module Foo
|
|
2471
2454
|
module_function
|
|
2472
2455
|
|
|
2473
2456
|
def bar
|
|
2474
|
-
@x =
|
|
2457
|
+
@x = CONST
|
|
2458
|
+
baz(1)
|
|
2475
2459
|
end
|
|
2476
2460
|
end
|
|
2477
2461
|
",
|
|
@@ -2479,10 +2463,11 @@ mod tests {
|
|
|
2479
2463
|
EnterModule(Foo)
|
|
2480
2464
|
SetDefaultVisibility(module_function)
|
|
2481
2465
|
EnterMethod(self.bar())
|
|
2482
|
-
DefineInstanceVariable(@x)
|
|
2483
2466
|
ExitScope
|
|
2484
2467
|
EnterMethod(bar())
|
|
2485
2468
|
DefineInstanceVariable(@x)
|
|
2469
|
+
ReferenceConstant(CONST)
|
|
2470
|
+
ReferenceMethod(baz)
|
|
2486
2471
|
ExitScope
|
|
2487
2472
|
ExitScope
|
|
2488
2473
|
",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::io;
|
|
3
|
+
use std::path::{Path, PathBuf};
|
|
4
|
+
|
|
5
|
+
/// Resolves `path` into the spelling rubydex knows a path by: absolute, with symlinks expanded and, on Windows, spelled
|
|
6
|
+
/// plainly instead of verbatim.
|
|
7
|
+
///
|
|
8
|
+
/// # Errors
|
|
9
|
+
///
|
|
10
|
+
/// Returns an error if the path cannot be canonicalized, as for one that does not exist.
|
|
11
|
+
pub(crate) fn resolved(path: &Path) -> io::Result<PathBuf> {
|
|
12
|
+
Ok(simplified(fs::canonicalize(path)?))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Only Windows spells a path verbatim, so everywhere else there is nothing to simplify.
|
|
16
|
+
#[cfg(not(windows))]
|
|
17
|
+
pub(crate) fn simplified(path: PathBuf) -> PathBuf {
|
|
18
|
+
path
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Since we canonicalize paths, we must simplify Windows paths in order to use them correctly with glob patterns, like
|
|
22
|
+
/// stripping the `\\?\` prefix.
|
|
23
|
+
#[cfg(windows)]
|
|
24
|
+
pub(crate) fn simplified(path: PathBuf) -> PathBuf {
|
|
25
|
+
use std::path::Component;
|
|
26
|
+
use std::path::Prefix;
|
|
27
|
+
|
|
28
|
+
let mut components = path.components();
|
|
29
|
+
let Some(Component::Prefix(prefix)) = components.next() else {
|
|
30
|
+
return path;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Only a verbatim drive or share has a plain equivalent: `\\?\D:\dir` is `D:\dir`, and `\\?\UNC\server\share` is
|
|
34
|
+
// the share `\\server\share`. Any other verbatim prefix names a device, such as `\\?\pipe\name`, which has none.
|
|
35
|
+
let root = match prefix.kind() {
|
|
36
|
+
Prefix::VerbatimDisk(drive) => format!("{}:\\", char::from(drive)),
|
|
37
|
+
Prefix::VerbatimUNC(server, share) => {
|
|
38
|
+
format!(r"\\{}\{}\", server.to_string_lossy(), share.to_string_lossy())
|
|
39
|
+
}
|
|
40
|
+
_ => return path,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// The root already carries the separator that follows it, so the one spelled as its own component is dropped rather
|
|
44
|
+
// than pushed, which would otherwise reset the path back to the root it was just given.
|
|
45
|
+
let mut simplified = PathBuf::from(root);
|
|
46
|
+
simplified.extend(components.filter(|component| !matches!(component, Component::RootDir)));
|
|
47
|
+
simplified
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[cfg(all(test, windows))]
|
|
51
|
+
mod tests {
|
|
52
|
+
use super::*;
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn simplified_rewrites_a_verbatim_path_into_its_plain_spelling() {
|
|
56
|
+
assert_eq!(
|
|
57
|
+
simplified(PathBuf::from(r"\\?\D:\a\project")),
|
|
58
|
+
PathBuf::from(r"D:\a\project")
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#[test]
|
|
63
|
+
fn simplified_keeps_a_verbatim_share_rooted_on_that_share() {
|
|
64
|
+
assert_eq!(
|
|
65
|
+
simplified(PathBuf::from(r"\\?\UNC\server\share\project")),
|
|
66
|
+
PathBuf::from(r"\\server\share\project")
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[test]
|
|
71
|
+
fn simplified_leaves_a_path_with_no_plain_equivalent_alone() {
|
|
72
|
+
// A device path is not addressable without the prefix, and a plain path has nothing to simplify.
|
|
73
|
+
for path in [r"\\?\pipe\rubydex", r"D:\a\project", r"\\server\share\project"] {
|
|
74
|
+
assert_eq!(simplified(PathBuf::from(path)), PathBuf::from(path));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -39,6 +39,22 @@ pub enum NodeRef {
|
|
|
39
39
|
Document(UriId),
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
impl NodeRef {
|
|
43
|
+
/// Decodes the opaque id produced by [`GraphProvider::node_id`] back into a `NodeRef`. Returns
|
|
44
|
+
/// `None` if the string is not a recognized `tag:id` form.
|
|
45
|
+
#[must_use]
|
|
46
|
+
pub fn decode(encoded: &str) -> Option<NodeRef> {
|
|
47
|
+
let (tag, rest) = encoded.split_once(':')?;
|
|
48
|
+
let value: u64 = rest.parse().ok()?;
|
|
49
|
+
match tag {
|
|
50
|
+
"decl" => Some(NodeRef::Declaration(DeclarationId::new(value))),
|
|
51
|
+
"def" => Some(NodeRef::Definition(DefinitionId::new(value))),
|
|
52
|
+
"doc" => Some(NodeRef::Document(UriId::new(value))),
|
|
53
|
+
_ => None,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
42
58
|
/// A relationship type.
|
|
43
59
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
44
60
|
pub enum RelType {
|
|
@@ -377,6 +393,10 @@ impl GraphProvider for Graph {
|
|
|
377
393
|
RelType::parse(rel_type).map_or_else(Vec::new, |rel| rel_source_nodes(self, rel))
|
|
378
394
|
}
|
|
379
395
|
|
|
396
|
+
fn expand_in(&self, node: NodeRef, rel_type: &str) -> Option<Vec<NodeRef>> {
|
|
397
|
+
RelType::parse(rel_type).and_then(|rel| expand_in(self, node, rel))
|
|
398
|
+
}
|
|
399
|
+
|
|
380
400
|
fn property(&self, node: NodeRef, prop: &str) -> CypherValue {
|
|
381
401
|
property(self, node, prop)
|
|
382
402
|
}
|
|
@@ -388,6 +408,17 @@ impl GraphProvider for Graph {
|
|
|
388
408
|
fn name(&self, node: NodeRef) -> String {
|
|
389
409
|
node_name(self, node)
|
|
390
410
|
}
|
|
411
|
+
|
|
412
|
+
fn node_id(&self, node: NodeRef) -> String {
|
|
413
|
+
// Encode the node category plus the underlying hashed id so a consumer can decode it back to
|
|
414
|
+
// the right handle. The category tag is required because labels alone are ambiguous
|
|
415
|
+
// (a class declaration and a class definition both have the label "Class").
|
|
416
|
+
match node {
|
|
417
|
+
NodeRef::Declaration(id) => format!("decl:{}", *id),
|
|
418
|
+
NodeRef::Definition(id) => format!("def:{}", *id),
|
|
419
|
+
NodeRef::Document(id) => format!("doc:{}", *id),
|
|
420
|
+
}
|
|
421
|
+
}
|
|
391
422
|
}
|
|
392
423
|
|
|
393
424
|
/// Returns all nodes matching the given labels. An empty slice matches every node; otherwise a node
|
|
@@ -582,6 +613,38 @@ pub fn rel_source_nodes(graph: &Graph, rel: RelType) -> Vec<NodeRef> {
|
|
|
582
613
|
}
|
|
583
614
|
}
|
|
584
615
|
|
|
616
|
+
/// Expands the *incoming* edges of `node` for the given relationship type — the targeted reverse of
|
|
617
|
+
/// [`expand_out`], returning `Some` only when the graph stores the reverse cheaply and exactly.
|
|
618
|
+
/// Returning `None` lets the executor fall back to `rel_sources` + `expand` (always correct, but an
|
|
619
|
+
/// O(all sources) whole-graph build).
|
|
620
|
+
///
|
|
621
|
+
/// Only the Document → Definition → Declaration spine is answered directly:
|
|
622
|
+
/// - reverse `DEFINES`: a definition's document (each definition belongs to exactly one).
|
|
623
|
+
/// - reverse `DECLARES`: the per-file definitions composing a declaration.
|
|
624
|
+
///
|
|
625
|
+
/// The remaining edges either have no stored reverse (e.g. direct subclasses of `HAS_PARENT`) or a
|
|
626
|
+
/// reverse that isn't an exact set match (the `HAS_DESCENDANT` set is self-inclusive, unlike the
|
|
627
|
+
/// `HAS_ANCESTOR` walk), so they fall through to the default.
|
|
628
|
+
#[must_use]
|
|
629
|
+
pub fn expand_in(graph: &Graph, node: NodeRef, rel: RelType) -> Option<Vec<NodeRef>> {
|
|
630
|
+
match (node, rel) {
|
|
631
|
+
(NodeRef::Definition(def_id), RelType::Defines) => {
|
|
632
|
+
let uri_id = *graph.definitions().get(&def_id)?.uri_id();
|
|
633
|
+
Some(vec![NodeRef::Document(uri_id)])
|
|
634
|
+
}
|
|
635
|
+
(NodeRef::Declaration(decl_id), RelType::Declares) => Some(
|
|
636
|
+
graph
|
|
637
|
+
.declarations()
|
|
638
|
+
.get(&decl_id)?
|
|
639
|
+
.definitions()
|
|
640
|
+
.iter()
|
|
641
|
+
.map(|id| NodeRef::Definition(*id))
|
|
642
|
+
.collect(),
|
|
643
|
+
),
|
|
644
|
+
_ => None,
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
585
648
|
/// Expands the outgoing edges of `node` for the given relationship type.
|
|
586
649
|
#[must_use]
|
|
587
650
|
pub fn expand_out(graph: &Graph, node: NodeRef, rel: RelType) -> Vec<NodeRef> {
|