rubydex 0.2.9 → 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 +110 -6
- data/THIRD_PARTY_LICENSES.html +271 -2
- data/exe/rdx +2 -73
- data/ext/rubydex/config.c +140 -0
- data/ext/rubydex/config.h +16 -0
- data/ext/rubydex/declaration.c +1 -1
- data/ext/rubydex/definition.c +32 -4
- data/ext/rubydex/diagnostic.c +75 -1
- data/ext/rubydex/diagnostic.h +2 -0
- data/ext/rubydex/graph.c +27 -48
- data/ext/rubydex/graph.h +6 -0
- data/ext/rubydex/query.c +487 -0
- data/ext/rubydex/query.h +8 -0
- data/ext/rubydex/reference.c +60 -0
- data/ext/rubydex/rubydex.c +4 -0
- data/ext/rubydex/utils.c +23 -4
- data/ext/rubydex/utils.h +5 -0
- 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 +578 -15
- data/rust/Cargo.lock +7 -0
- data/rust/rubydex/Cargo.toml +1 -0
- data/rust/rubydex/benches/graph_memory.rs +20 -4
- data/rust/rubydex/src/compile_assertions.rs +15 -0
- 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 +284 -8
- data/rust/rubydex/src/indexing/ruby_indexer.rs +59 -70
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +195 -86
- data/rust/rubydex/src/lib.rs +1 -0
- data/rust/rubydex/src/listing.rs +26 -1
- data/rust/rubydex/src/main.rs +9 -128
- data/rust/rubydex/src/model/declaration.rs +301 -229
- data/rust/rubydex/src/model/definitions.rs +27 -26
- data/rust/rubydex/src/model/document.rs +43 -7
- data/rust/rubydex/src/model/graph.rs +67 -68
- data/rust/rubydex/src/model/id.rs +55 -0
- data/rust/rubydex/src/model/ids.rs +21 -9
- data/rust/rubydex/src/model/name.rs +88 -19
- data/rust/rubydex/src/model/references.rs +16 -13
- data/rust/rubydex/src/operation/ruby_builder.rs +78 -104
- data/rust/rubydex/src/path_helpers.rs +77 -0
- data/rust/rubydex/src/query/cypher/schema.rs +853 -0
- data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
- data/rust/rubydex/src/query/cypher/tests.rs +253 -0
- data/rust/rubydex/src/query/cypher.rs +54 -0
- data/rust/rubydex/src/query.rs +125 -43
- data/rust/rubydex/src/resolution.rs +368 -395
- data/rust/rubydex/src/resolution_tests.rs +504 -78
- 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/declaration_api.rs +6 -3
- data/rust/rubydex-sys/src/definition_api.rs +27 -7
- data/rust/rubydex-sys/src/diagnostic_api.rs +77 -8
- data/rust/rubydex-sys/src/graph_api.rs +31 -68
- data/rust/rubydex-sys/src/lib.rs +2 -0
- data/rust/rubydex-sys/src/name_api.rs +2 -6
- data/rust/rubydex-sys/src/reference_api.rs +58 -12
- data/rust/rubydex-sys/src/utils.rs +37 -0
- data/skills/send-private-method/SKILL.md +133 -0
- metadata +37 -2
|
@@ -2,7 +2,11 @@ use std::fmt::Display;
|
|
|
2
2
|
|
|
3
3
|
use crate::{
|
|
4
4
|
assert_mem_size,
|
|
5
|
-
model::
|
|
5
|
+
model::{
|
|
6
|
+
id::id_from_parts,
|
|
7
|
+
identity_maps::IdentityHashMap,
|
|
8
|
+
ids::{DeclarationId, NameId, StringId},
|
|
9
|
+
},
|
|
6
10
|
};
|
|
7
11
|
|
|
8
12
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
@@ -90,31 +94,58 @@ pub struct Name {
|
|
|
90
94
|
/// list of names to represent the nesting
|
|
91
95
|
nesting: Option<NameId>,
|
|
92
96
|
ref_count: u32,
|
|
97
|
+
/// The depth of the name, which combines the depths of the parent scope and nesting
|
|
98
|
+
depth: u16,
|
|
93
99
|
}
|
|
100
|
+
assert_mem_size!(Name, 40);
|
|
94
101
|
|
|
95
102
|
impl PartialEq for Name {
|
|
96
103
|
fn eq(&self, other: &Self) -> bool {
|
|
97
104
|
self.str == other.str && self.parent_scope == other.parent_scope && self.nesting == other.nesting
|
|
98
105
|
}
|
|
99
106
|
}
|
|
100
|
-
assert_mem_size!(Name, 40);
|
|
101
107
|
|
|
102
108
|
impl Name {
|
|
103
109
|
#[must_use]
|
|
104
|
-
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 {
|
|
105
116
|
Self {
|
|
106
117
|
str,
|
|
107
118
|
parent_scope,
|
|
108
119
|
nesting,
|
|
109
120
|
ref_count: 1,
|
|
121
|
+
depth: Self::name_depth(names, parent_scope, nesting),
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
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
|
+
|
|
113
139
|
#[must_use]
|
|
114
140
|
pub fn str(&self) -> &StringId {
|
|
115
141
|
&self.str
|
|
116
142
|
}
|
|
117
143
|
|
|
144
|
+
#[must_use]
|
|
145
|
+
pub fn depth(&self) -> u16 {
|
|
146
|
+
self.depth
|
|
147
|
+
}
|
|
148
|
+
|
|
118
149
|
#[must_use]
|
|
119
150
|
pub fn parent_scope(&self) -> &ParentScope {
|
|
120
151
|
&self.parent_scope
|
|
@@ -127,12 +158,19 @@ impl Name {
|
|
|
127
158
|
|
|
128
159
|
#[must_use]
|
|
129
160
|
pub fn id(&self) -> NameId {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
161
|
+
// We need to include both the variant and the bytes of the parent scope when present
|
|
162
|
+
let (parent_tag, parent_id): (u8, u64) = match self.parent_scope {
|
|
163
|
+
ParentScope::None => (0, 0),
|
|
164
|
+
ParentScope::TopLevel => (1, 0),
|
|
165
|
+
ParentScope::Some(id) => (2, id.get()),
|
|
166
|
+
ParentScope::Attached(id) => (3, id.get()),
|
|
167
|
+
};
|
|
168
|
+
let (nesting_tag, nesting_id): (u8, u64) = match self.nesting {
|
|
169
|
+
None => (0, 0),
|
|
170
|
+
Some(id) => (1, id.get()),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
id_from_parts!(NameId; self.str.get(), parent_tag, parent_id, nesting_tag, nesting_id)
|
|
136
174
|
}
|
|
137
175
|
}
|
|
138
176
|
|
|
@@ -217,6 +255,14 @@ impl NameRef {
|
|
|
217
255
|
}
|
|
218
256
|
}
|
|
219
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
|
+
|
|
220
266
|
/// # Panics
|
|
221
267
|
///
|
|
222
268
|
/// Panics if we exceed the maximum size of the reference count
|
|
@@ -271,30 +317,53 @@ mod tests {
|
|
|
271
317
|
|
|
272
318
|
#[test]
|
|
273
319
|
fn same_parent_scope_and_nesting() {
|
|
274
|
-
let
|
|
275
|
-
|
|
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);
|
|
276
324
|
assert_eq!(name_1.id(), name_2.id());
|
|
277
325
|
|
|
278
|
-
let name_3 = Name::new(StringId::from("Foo"), ParentScope::Some(name_1.id()), None);
|
|
279
|
-
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);
|
|
280
328
|
assert_eq!(name_3.id(), name_4.id());
|
|
281
329
|
|
|
282
|
-
let name_5 = Name::new(StringId::from("Foo"), ParentScope::None, Some(name_1.id()));
|
|
283
|
-
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()));
|
|
284
332
|
assert_eq!(name_5.id(), name_6.id());
|
|
285
333
|
assert_ne!(name_3.id(), name_5.id());
|
|
286
334
|
assert_ne!(name_4.id(), name_6.id());
|
|
287
335
|
|
|
288
336
|
let name_7 = Name::new(
|
|
337
|
+
&names,
|
|
289
338
|
StringId::from("Foo"),
|
|
290
|
-
ParentScope::Some(Name::new(StringId::from("Foo"), ParentScope::None, None).id()),
|
|
291
|
-
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()),
|
|
292
341
|
);
|
|
293
342
|
let name_8 = Name::new(
|
|
343
|
+
&names,
|
|
294
344
|
StringId::from("Foo"),
|
|
295
|
-
ParentScope::Some(Name::new(StringId::from("Foo"), ParentScope::None, None).id()),
|
|
296
|
-
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()),
|
|
297
347
|
);
|
|
298
348
|
assert_eq!(name_7.id(), name_8.id());
|
|
299
349
|
}
|
|
350
|
+
|
|
351
|
+
#[test]
|
|
352
|
+
fn parent_scope_variants_are_distinct() {
|
|
353
|
+
let names = IdentityHashMap::default();
|
|
354
|
+
let inner = Name::new(&names, StringId::from("Foo"), ParentScope::None, None).id();
|
|
355
|
+
|
|
356
|
+
let ids = [
|
|
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(),
|
|
361
|
+
];
|
|
362
|
+
|
|
363
|
+
for (i, a) in ids.iter().enumerate() {
|
|
364
|
+
for b in &ids[i + 1..] {
|
|
365
|
+
assert_ne!(a, b);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
300
369
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
use crate::{
|
|
2
2
|
assert_mem_size,
|
|
3
|
-
model::
|
|
3
|
+
model::{
|
|
4
|
+
id::id_from_parts,
|
|
5
|
+
ids::{ConstantReferenceId, MethodReferenceId, NameId, StringId, UriId},
|
|
6
|
+
},
|
|
4
7
|
offset::Offset,
|
|
5
8
|
};
|
|
6
9
|
|
|
@@ -43,13 +46,13 @@ impl ConstantReference {
|
|
|
43
46
|
|
|
44
47
|
#[must_use]
|
|
45
48
|
pub fn id(&self) -> ConstantReferenceId {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self.name_id,
|
|
49
|
-
self.uri_id,
|
|
49
|
+
id_from_parts!(
|
|
50
|
+
ConstantReferenceId;
|
|
51
|
+
self.name_id.get(),
|
|
52
|
+
self.uri_id.get(),
|
|
50
53
|
self.offset.start(),
|
|
51
|
-
self.offset.end()
|
|
52
|
-
)
|
|
54
|
+
self.offset.end(),
|
|
55
|
+
)
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -100,12 +103,12 @@ impl MethodRef {
|
|
|
100
103
|
|
|
101
104
|
#[must_use]
|
|
102
105
|
pub fn id(&self) -> MethodReferenceId {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
self.str,
|
|
106
|
-
self.uri_id,
|
|
106
|
+
id_from_parts!(
|
|
107
|
+
MethodReferenceId;
|
|
108
|
+
self.str.get(),
|
|
109
|
+
self.uri_id.get(),
|
|
107
110
|
self.offset.start(),
|
|
108
|
-
self.offset.end()
|
|
109
|
-
)
|
|
111
|
+
self.offset.end(),
|
|
112
|
+
)
|
|
110
113
|
}
|
|
111
114
|
}
|
|
@@ -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);
|
|
@@ -611,20 +608,8 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
611
608
|
{
|
|
612
609
|
if let Some(arguments) = node.arguments() {
|
|
613
610
|
for argument in &arguments.arguments() {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
let symbol = argument.as_symbol_node().unwrap();
|
|
617
|
-
if let Some(value_loc) = symbol.value_loc() {
|
|
618
|
-
let name = Self::location_to_string(&value_loc);
|
|
619
|
-
f(name, value_loc);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
ruby_prism::Node::StringNode { .. } => {
|
|
623
|
-
let string = argument.as_string_node().unwrap();
|
|
624
|
-
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
625
|
-
f(name, argument.location());
|
|
626
|
-
}
|
|
627
|
-
_ => {}
|
|
611
|
+
if let Some((name, location)) = Self::extract_literal_name(&argument) {
|
|
612
|
+
f(name, location);
|
|
628
613
|
}
|
|
629
614
|
}
|
|
630
615
|
}
|
|
@@ -701,10 +686,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
701
686
|
)
|
|
702
687
|
} else {
|
|
703
688
|
let string_id = self.intern_string(format!("{}:{}<anonymous>", self.uri_id, offset.start()));
|
|
704
|
-
(
|
|
705
|
-
Some(self.add_name(Name::new(string_id, ParentScope::None, None))),
|
|
706
|
-
offset.clone(),
|
|
707
|
-
)
|
|
689
|
+
(Some(self.add_name(string_id, ParentScope::None, None)), offset.clone())
|
|
708
690
|
};
|
|
709
691
|
|
|
710
692
|
if let Some(name_id) = name_id {
|
|
@@ -766,10 +748,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
766
748
|
)
|
|
767
749
|
} else {
|
|
768
750
|
let string_id = self.intern_string(format!("{}:{}<anonymous>", self.uri_id, offset.start()));
|
|
769
|
-
(
|
|
770
|
-
Some(self.add_name(Name::new(string_id, ParentScope::None, None))),
|
|
771
|
-
offset.clone(),
|
|
772
|
-
)
|
|
751
|
+
(Some(self.add_name(string_id, ParentScope::None, None)), offset.clone())
|
|
773
752
|
};
|
|
774
753
|
|
|
775
754
|
if let Some(name_id) = name_id {
|
|
@@ -861,7 +840,7 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
861
840
|
}
|
|
862
841
|
|
|
863
842
|
Some((
|
|
864
|
-
self.
|
|
843
|
+
self.current_owner_name_id().unwrap(),
|
|
865
844
|
Offset::from_prism_location(&arg.location()),
|
|
866
845
|
))
|
|
867
846
|
} else if let Some(name_id) = self.index_constant_reference(&arg, false) {
|
|
@@ -899,28 +878,29 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
899
878
|
|
|
900
879
|
fn handle_constant_visibility(&mut self, node: &ruby_prism::CallNode, visibility: Visibility) {
|
|
901
880
|
let receiver = node.receiver();
|
|
881
|
+
let call_name = String::from_utf8_lossy(node.name().as_slice());
|
|
902
882
|
|
|
903
|
-
let receiver_name_id = match receiver {
|
|
883
|
+
let receiver_name_id = match &receiver {
|
|
904
884
|
Some(ruby_prism::Node::ConstantPathNode { .. } | ruby_prism::Node::ConstantReadNode { .. }) => {
|
|
905
|
-
self.index_constant_reference(
|
|
885
|
+
self.index_constant_reference(receiver.as_ref().unwrap(), true)
|
|
906
886
|
}
|
|
907
887
|
Some(ruby_prism::Node::SelfNode { .. }) | None => match self.nesting_stack.last() {
|
|
908
888
|
Some(Nesting::Method { .. }) => return,
|
|
909
889
|
None => {
|
|
910
890
|
self.add_diagnostic(
|
|
911
|
-
Rule::
|
|
891
|
+
Rule::InvalidConstantVisibility,
|
|
912
892
|
Offset::from_prism_location(&node.location()),
|
|
913
|
-
"
|
|
893
|
+
format!("`{call_name}` called at top level"),
|
|
914
894
|
);
|
|
915
895
|
return;
|
|
916
896
|
}
|
|
917
897
|
_ => None,
|
|
918
898
|
},
|
|
919
|
-
|
|
899
|
+
Some(other) => {
|
|
920
900
|
self.add_diagnostic(
|
|
921
|
-
Rule::
|
|
922
|
-
Offset::from_prism_location(&
|
|
923
|
-
"Dynamic receiver for
|
|
901
|
+
Rule::InvalidConstantVisibility,
|
|
902
|
+
Offset::from_prism_location(&other.location()),
|
|
903
|
+
format!("Dynamic receiver for `{call_name}`"),
|
|
924
904
|
);
|
|
925
905
|
return;
|
|
926
906
|
}
|
|
@@ -931,28 +911,13 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
931
911
|
};
|
|
932
912
|
|
|
933
913
|
for argument in &arguments.arguments() {
|
|
934
|
-
let (name, location) =
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
ruby_prism::Node::StringNode { .. } => {
|
|
944
|
-
let string = argument.as_string_node().unwrap();
|
|
945
|
-
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
946
|
-
(name, argument.location())
|
|
947
|
-
}
|
|
948
|
-
_ => {
|
|
949
|
-
self.add_diagnostic(
|
|
950
|
-
Rule::InvalidPrivateConstant,
|
|
951
|
-
Offset::from_prism_location(&argument.location()),
|
|
952
|
-
"Private constant called with non-symbol argument".to_string(),
|
|
953
|
-
);
|
|
954
|
-
continue;
|
|
955
|
-
}
|
|
914
|
+
let Some((name, location)) = Self::extract_literal_name(&argument) else {
|
|
915
|
+
self.add_diagnostic(
|
|
916
|
+
Rule::InvalidConstantVisibility,
|
|
917
|
+
Offset::from_prism_location(&argument.location()),
|
|
918
|
+
format!("`{call_name}` called with a non-literal argument"),
|
|
919
|
+
);
|
|
920
|
+
continue;
|
|
956
921
|
};
|
|
957
922
|
|
|
958
923
|
let str_id = self.intern_string(name);
|
|
@@ -1067,6 +1032,22 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
1067
1032
|
Some(())
|
|
1068
1033
|
}
|
|
1069
1034
|
|
|
1035
|
+
fn extract_literal_name<'b>(arg: &ruby_prism::Node<'b>) -> Option<(String, ruby_prism::Location<'b>)> {
|
|
1036
|
+
match arg {
|
|
1037
|
+
ruby_prism::Node::SymbolNode { .. } => {
|
|
1038
|
+
let symbol = arg.as_symbol_node().unwrap();
|
|
1039
|
+
let value_loc = symbol.value_loc()?;
|
|
1040
|
+
Some((Self::location_to_string(&value_loc), value_loc))
|
|
1041
|
+
}
|
|
1042
|
+
ruby_prism::Node::StringNode { .. } => {
|
|
1043
|
+
let string = arg.as_string_node().unwrap();
|
|
1044
|
+
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
1045
|
+
Some((name, arg.location()))
|
|
1046
|
+
}
|
|
1047
|
+
_ => None,
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1070
1051
|
fn is_attr_call(arg: &ruby_prism::Node) -> bool {
|
|
1071
1052
|
arg.as_call_node().is_some_and(|call| {
|
|
1072
1053
|
let receiver = call.receiver();
|
|
@@ -1116,6 +1097,18 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
1116
1097
|
ruby_prism::Node::SymbolNode { .. } | ruby_prism::Node::StringNode { .. }
|
|
1117
1098
|
) {
|
|
1118
1099
|
self.create_method_visibility_operation(&arg, visibility, DefinitionFlags::empty());
|
|
1100
|
+
if visibility == Visibility::ModuleFunction {
|
|
1101
|
+
// `module_function` also creates a public singleton method, so we emit a
|
|
1102
|
+
// second def for the singleton side. The two defs stay separate (rather than
|
|
1103
|
+
// shared) so reverse-lookup invalidation can detach `Foo#bar` and
|
|
1104
|
+
// `Foo::<Foo>#bar` independently. The `SINGLETON_METHOD_VISIBILITY` flag
|
|
1105
|
+
// routes this one to the singleton class.
|
|
1106
|
+
self.create_method_visibility_operation(
|
|
1107
|
+
&arg,
|
|
1108
|
+
visibility,
|
|
1109
|
+
DefinitionFlags::SINGLETON_METHOD_VISIBILITY,
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1119
1112
|
} else {
|
|
1120
1113
|
let arg_offset = Offset::from_prism_location(&arg.location());
|
|
1121
1114
|
let message = if Self::is_attr_call(&arg) {
|
|
@@ -1135,21 +1128,8 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
1135
1128
|
visibility: Visibility,
|
|
1136
1129
|
flags: DefinitionFlags,
|
|
1137
1130
|
) {
|
|
1138
|
-
let (name, location) =
|
|
1139
|
-
|
|
1140
|
-
let symbol = arg.as_symbol_node().unwrap();
|
|
1141
|
-
if let Some(value_loc) = symbol.value_loc() {
|
|
1142
|
-
(Self::location_to_string(&value_loc), value_loc)
|
|
1143
|
-
} else {
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
ruby_prism::Node::StringNode { .. } => {
|
|
1148
|
-
let string = arg.as_string_node().unwrap();
|
|
1149
|
-
let name = String::from_utf8_lossy(string.unescaped()).to_string();
|
|
1150
|
-
(name, arg.location())
|
|
1151
|
-
}
|
|
1152
|
-
_ => return,
|
|
1131
|
+
let Some((name, location)) = Self::extract_literal_name(arg) else {
|
|
1132
|
+
return;
|
|
1153
1133
|
};
|
|
1154
1134
|
|
|
1155
1135
|
let str_id = self.intern_string(format!("{name}()"));
|
|
@@ -1372,6 +1352,14 @@ impl<'a> RubyOperationBuilder<'a> {
|
|
|
1372
1352
|
flags,
|
|
1373
1353
|
}));
|
|
1374
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
|
+
}
|
|
1375
1363
|
}
|
|
1376
1364
|
|
|
1377
1365
|
struct CommentGroup {
|
|
@@ -1494,7 +1482,7 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1494
1482
|
|
|
1495
1483
|
let string_id = self.intern_string(singleton_class_name);
|
|
1496
1484
|
let nesting = self.current_lexical_scope_name_id();
|
|
1497
|
-
let name_id = self.add_name(
|
|
1485
|
+
let name_id = self.add_name(string_id, ParentScope::Attached(attached_target), nesting);
|
|
1498
1486
|
self.operations
|
|
1499
1487
|
.push(Operation::EnterSingletonClass(op::EnterSingletonClass {
|
|
1500
1488
|
name_id,
|
|
@@ -1578,8 +1566,10 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1578
1566
|
};
|
|
1579
1567
|
|
|
1580
1568
|
if receiver.is_none() && visibility == Visibility::ModuleFunction {
|
|
1581
|
-
//
|
|
1582
|
-
//
|
|
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.
|
|
1583
1573
|
let singleton_receiver = Some(Target::ExplicitSelf);
|
|
1584
1574
|
let body = node.body();
|
|
1585
1575
|
|
|
@@ -1593,13 +1583,6 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1593
1583
|
signatures: Signatures::Simple(parameters.clone().into_boxed_slice()),
|
|
1594
1584
|
receiver: singleton_receiver,
|
|
1595
1585
|
}));
|
|
1596
|
-
self.nesting_stack.push(Nesting::Method {
|
|
1597
|
-
receiver: method_nesting_receiver,
|
|
1598
|
-
});
|
|
1599
|
-
if let Some(ref body) = body {
|
|
1600
|
-
self.visit(body);
|
|
1601
|
-
}
|
|
1602
|
-
self.nesting_stack.pop();
|
|
1603
1586
|
self.operations.push(Operation::ExitScope);
|
|
1604
1587
|
|
|
1605
1588
|
self.operations.push(Operation::EnterMethod(op::EnterMethod {
|
|
@@ -1612,13 +1595,7 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1612
1595
|
signatures: Signatures::Simple(parameters.into_boxed_slice()),
|
|
1613
1596
|
receiver,
|
|
1614
1597
|
}));
|
|
1615
|
-
self.
|
|
1616
|
-
receiver: method_nesting_receiver,
|
|
1617
|
-
});
|
|
1618
|
-
if let Some(ref body) = body {
|
|
1619
|
-
self.visit(body);
|
|
1620
|
-
}
|
|
1621
|
-
self.nesting_stack.pop();
|
|
1598
|
+
self.visit_method_body(method_nesting_receiver, body.as_ref());
|
|
1622
1599
|
self.operations.push(Operation::ExitScope);
|
|
1623
1600
|
} else {
|
|
1624
1601
|
// Singleton methods at top level have receiver=None (no class to point self to).
|
|
@@ -1647,13 +1624,8 @@ impl Visit<'_> for RubyOperationBuilder<'_> {
|
|
|
1647
1624
|
signatures: Signatures::Simple(parameters.into_boxed_slice()),
|
|
1648
1625
|
receiver,
|
|
1649
1626
|
}));
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
});
|
|
1653
|
-
if let Some(body) = node.body() {
|
|
1654
|
-
self.visit(&body);
|
|
1655
|
-
}
|
|
1656
|
-
self.nesting_stack.pop();
|
|
1627
|
+
let body = node.body();
|
|
1628
|
+
self.visit_method_body(method_nesting_receiver, body.as_ref());
|
|
1657
1629
|
self.operations.push(Operation::ExitScope);
|
|
1658
1630
|
|
|
1659
1631
|
if let Some(prev) = previous_visibility {
|
|
@@ -2475,14 +2447,15 @@ mod tests {
|
|
|
2475
2447
|
}
|
|
2476
2448
|
|
|
2477
2449
|
#[test]
|
|
2478
|
-
fn
|
|
2479
|
-
|
|
2450
|
+
fn build_module_function_with_body() {
|
|
2451
|
+
assert_operations_with_references(
|
|
2480
2452
|
"
|
|
2481
2453
|
module Foo
|
|
2482
2454
|
module_function
|
|
2483
2455
|
|
|
2484
2456
|
def bar
|
|
2485
|
-
@x =
|
|
2457
|
+
@x = CONST
|
|
2458
|
+
baz(1)
|
|
2486
2459
|
end
|
|
2487
2460
|
end
|
|
2488
2461
|
",
|
|
@@ -2490,10 +2463,11 @@ mod tests {
|
|
|
2490
2463
|
EnterModule(Foo)
|
|
2491
2464
|
SetDefaultVisibility(module_function)
|
|
2492
2465
|
EnterMethod(self.bar())
|
|
2493
|
-
DefineInstanceVariable(@x)
|
|
2494
2466
|
ExitScope
|
|
2495
2467
|
EnterMethod(bar())
|
|
2496
2468
|
DefineInstanceVariable(@x)
|
|
2469
|
+
ReferenceConstant(CONST)
|
|
2470
|
+
ReferenceMethod(baz)
|
|
2497
2471
|
ExitScope
|
|
2498
2472
|
ExitScope
|
|
2499
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
|
+
}
|