rubydex 0.1.0.beta1-x86_64-linux → 0.1.0.beta2-x86_64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/rubydex/declaration.c +146 -0
- data/ext/rubydex/declaration.h +10 -0
- data/ext/rubydex/definition.c +234 -0
- data/ext/rubydex/definition.h +28 -0
- data/ext/rubydex/diagnostic.c +6 -0
- data/ext/rubydex/diagnostic.h +11 -0
- data/ext/rubydex/document.c +98 -0
- data/ext/rubydex/document.h +10 -0
- data/ext/rubydex/extconf.rb +36 -15
- data/ext/rubydex/graph.c +405 -0
- data/ext/rubydex/graph.h +10 -0
- data/ext/rubydex/handle.h +44 -0
- data/ext/rubydex/location.c +22 -0
- data/ext/rubydex/location.h +15 -0
- data/ext/rubydex/reference.c +104 -0
- data/ext/rubydex/reference.h +16 -0
- data/ext/rubydex/rubydex.c +22 -0
- data/ext/rubydex/utils.c +27 -0
- data/ext/rubydex/utils.h +13 -0
- data/lib/rubydex/3.2/rubydex.so +0 -0
- data/lib/rubydex/3.3/rubydex.so +0 -0
- data/lib/rubydex/3.4/rubydex.so +0 -0
- data/lib/rubydex/4.0/rubydex.so +0 -0
- data/lib/rubydex/librubydex_sys.so +0 -0
- data/lib/rubydex/version.rb +1 -1
- data/rust/Cargo.lock +1275 -0
- data/rust/Cargo.toml +23 -0
- data/rust/about.hbs +78 -0
- data/rust/about.toml +9 -0
- data/rust/rubydex/Cargo.toml +41 -0
- data/rust/rubydex/src/diagnostic.rs +108 -0
- data/rust/rubydex/src/errors.rs +28 -0
- data/rust/rubydex/src/indexing/local_graph.rs +172 -0
- data/rust/rubydex/src/indexing/ruby_indexer.rs +5397 -0
- data/rust/rubydex/src/indexing.rs +128 -0
- data/rust/rubydex/src/job_queue.rs +186 -0
- data/rust/rubydex/src/lib.rs +15 -0
- data/rust/rubydex/src/listing.rs +249 -0
- data/rust/rubydex/src/main.rs +116 -0
- data/rust/rubydex/src/model/comment.rs +24 -0
- data/rust/rubydex/src/model/declaration.rs +541 -0
- data/rust/rubydex/src/model/definitions.rs +1475 -0
- data/rust/rubydex/src/model/document.rs +111 -0
- data/rust/rubydex/src/model/encoding.rs +22 -0
- data/rust/rubydex/src/model/graph.rs +1387 -0
- data/rust/rubydex/src/model/id.rs +90 -0
- data/rust/rubydex/src/model/identity_maps.rs +54 -0
- data/rust/rubydex/src/model/ids.rs +32 -0
- data/rust/rubydex/src/model/name.rs +188 -0
- data/rust/rubydex/src/model/references.rs +129 -0
- data/rust/rubydex/src/model/string_ref.rs +44 -0
- data/rust/rubydex/src/model/visibility.rs +41 -0
- data/rust/rubydex/src/model.rs +13 -0
- data/rust/rubydex/src/offset.rs +70 -0
- data/rust/rubydex/src/position.rs +6 -0
- data/rust/rubydex/src/query.rs +103 -0
- data/rust/rubydex/src/resolution.rs +4421 -0
- data/rust/rubydex/src/stats/memory.rs +71 -0
- data/rust/rubydex/src/stats/timer.rs +126 -0
- data/rust/rubydex/src/stats.rs +9 -0
- data/rust/rubydex/src/test_utils/context.rs +226 -0
- data/rust/rubydex/src/test_utils/graph_test.rs +229 -0
- data/rust/rubydex/src/test_utils/local_graph_test.rs +166 -0
- data/rust/rubydex/src/test_utils.rs +52 -0
- data/rust/rubydex/src/visualization/dot.rs +176 -0
- data/rust/rubydex/src/visualization.rs +6 -0
- data/rust/rubydex/tests/cli.rs +167 -0
- data/rust/rubydex-sys/Cargo.toml +20 -0
- data/rust/rubydex-sys/build.rs +14 -0
- data/rust/rubydex-sys/cbindgen.toml +12 -0
- data/rust/rubydex-sys/src/declaration_api.rs +114 -0
- data/rust/rubydex-sys/src/definition_api.rs +350 -0
- data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
- data/rust/rubydex-sys/src/document_api.rs +54 -0
- data/rust/rubydex-sys/src/graph_api.rs +493 -0
- data/rust/rubydex-sys/src/lib.rs +9 -0
- data/rust/rubydex-sys/src/location_api.rs +79 -0
- data/rust/rubydex-sys/src/name_api.rs +81 -0
- data/rust/rubydex-sys/src/reference_api.rs +191 -0
- data/rust/rubydex-sys/src/utils.rs +50 -0
- data/rust/rustfmt.toml +2 -0
- metadata +77 -2
|
@@ -0,0 +1,1387 @@
|
|
|
1
|
+
use std::collections::hash_map::Entry;
|
|
2
|
+
use std::sync::LazyLock;
|
|
3
|
+
|
|
4
|
+
use crate::diagnostic::Diagnostic;
|
|
5
|
+
use crate::indexing::local_graph::LocalGraph;
|
|
6
|
+
use crate::model::declaration::{Ancestor, Declaration, Namespace};
|
|
7
|
+
use crate::model::definitions::Definition;
|
|
8
|
+
use crate::model::document::Document;
|
|
9
|
+
use crate::model::encoding::Encoding;
|
|
10
|
+
use crate::model::identity_maps::{IdentityHashMap, IdentityHashSet};
|
|
11
|
+
use crate::model::ids::{DeclarationId, DefinitionId, NameId, ReferenceId, StringId, UriId};
|
|
12
|
+
use crate::model::name::{Name, NameRef, ResolvedName};
|
|
13
|
+
use crate::model::references::{ConstantReference, MethodRef};
|
|
14
|
+
use crate::model::string_ref::StringRef;
|
|
15
|
+
use crate::stats;
|
|
16
|
+
|
|
17
|
+
pub static OBJECT_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("Object"));
|
|
18
|
+
pub static MODULE_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("Module"));
|
|
19
|
+
pub static CLASS_ID: LazyLock<DeclarationId> = LazyLock::new(|| DeclarationId::from("Class"));
|
|
20
|
+
|
|
21
|
+
// The `Graph` is the global representation of the entire Ruby codebase. It contains all declarations and their
|
|
22
|
+
// relationships
|
|
23
|
+
#[derive(Default, Debug)]
|
|
24
|
+
pub struct Graph {
|
|
25
|
+
// Map of declaration nodes
|
|
26
|
+
declarations: IdentityHashMap<DeclarationId, Declaration>,
|
|
27
|
+
// Map of document nodes
|
|
28
|
+
documents: IdentityHashMap<UriId, Document>,
|
|
29
|
+
// Map of definition nodes
|
|
30
|
+
definitions: IdentityHashMap<DefinitionId, Definition>,
|
|
31
|
+
|
|
32
|
+
definitions_to_declarations: IdentityHashMap<DefinitionId, DeclarationId>,
|
|
33
|
+
|
|
34
|
+
// Map of unqualified names
|
|
35
|
+
strings: IdentityHashMap<StringId, StringRef>,
|
|
36
|
+
// Map of names
|
|
37
|
+
names: IdentityHashMap<NameId, NameRef>,
|
|
38
|
+
// Map of constant references
|
|
39
|
+
constant_references: IdentityHashMap<ReferenceId, ConstantReference>,
|
|
40
|
+
// Map of method references that still need to be resolved
|
|
41
|
+
method_references: IdentityHashMap<ReferenceId, MethodRef>,
|
|
42
|
+
|
|
43
|
+
/// The position encoding used for LSP line/column locations. Not related to the actual encoding of the file
|
|
44
|
+
position_encoding: Encoding,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
impl Graph {
|
|
48
|
+
#[must_use]
|
|
49
|
+
pub fn new() -> Self {
|
|
50
|
+
Self {
|
|
51
|
+
declarations: IdentityHashMap::default(),
|
|
52
|
+
definitions: IdentityHashMap::default(),
|
|
53
|
+
definitions_to_declarations: IdentityHashMap::default(),
|
|
54
|
+
documents: IdentityHashMap::default(),
|
|
55
|
+
strings: IdentityHashMap::default(),
|
|
56
|
+
names: IdentityHashMap::default(),
|
|
57
|
+
constant_references: IdentityHashMap::default(),
|
|
58
|
+
method_references: IdentityHashMap::default(),
|
|
59
|
+
position_encoding: Encoding::default(),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Returns an immutable reference to the declarations map
|
|
64
|
+
#[must_use]
|
|
65
|
+
pub fn declarations(&self) -> &IdentityHashMap<DeclarationId, Declaration> {
|
|
66
|
+
&self.declarations
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Returns a mutable reference to the declarations map
|
|
70
|
+
#[must_use]
|
|
71
|
+
pub fn declarations_mut(&mut self) -> &mut IdentityHashMap<DeclarationId, Declaration> {
|
|
72
|
+
&mut self.declarations
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// # Panics
|
|
76
|
+
///
|
|
77
|
+
/// Panics if acquiring a write lock fails
|
|
78
|
+
pub fn add_declaration<F>(&mut self, declaration_id: DeclarationId, definition_id: DefinitionId, constructor: F)
|
|
79
|
+
where
|
|
80
|
+
F: FnOnce() -> Declaration,
|
|
81
|
+
{
|
|
82
|
+
self.definitions_to_declarations.insert(definition_id, declaration_id);
|
|
83
|
+
|
|
84
|
+
let declaration = self.declarations.entry(declaration_id).or_insert_with(constructor);
|
|
85
|
+
declaration.add_definition(definition_id);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// # Panics
|
|
89
|
+
///
|
|
90
|
+
/// Panics if acquiring a write lock fails
|
|
91
|
+
pub fn clear_declarations(&mut self) {
|
|
92
|
+
self.definitions_to_declarations.clear();
|
|
93
|
+
self.declarations.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Returns an immutable reference to the definitions map
|
|
97
|
+
#[must_use]
|
|
98
|
+
pub fn definitions(&self) -> &IdentityHashMap<DefinitionId, Definition> {
|
|
99
|
+
&self.definitions
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Returns the ID of the unqualified name of a definition
|
|
103
|
+
///
|
|
104
|
+
/// # Panics
|
|
105
|
+
///
|
|
106
|
+
/// This will panic if there's inconsistent data in the graph
|
|
107
|
+
#[must_use]
|
|
108
|
+
pub fn definition_string_id(&self, definition: &Definition) -> StringId {
|
|
109
|
+
let id = match definition {
|
|
110
|
+
Definition::Class(it) => {
|
|
111
|
+
let name = self.names.get(it.name_id()).unwrap();
|
|
112
|
+
name.str()
|
|
113
|
+
}
|
|
114
|
+
Definition::SingletonClass(it) => {
|
|
115
|
+
let name = self.names.get(it.name_id()).unwrap();
|
|
116
|
+
name.str()
|
|
117
|
+
}
|
|
118
|
+
Definition::Module(it) => {
|
|
119
|
+
let name = self.names.get(it.name_id()).unwrap();
|
|
120
|
+
name.str()
|
|
121
|
+
}
|
|
122
|
+
Definition::Constant(it) => {
|
|
123
|
+
let name = self.names.get(it.name_id()).unwrap();
|
|
124
|
+
name.str()
|
|
125
|
+
}
|
|
126
|
+
Definition::ConstantAlias(it) => {
|
|
127
|
+
let name = self.names.get(it.name_id()).unwrap();
|
|
128
|
+
name.str()
|
|
129
|
+
}
|
|
130
|
+
Definition::GlobalVariable(it) => it.str_id(),
|
|
131
|
+
Definition::InstanceVariable(it) => it.str_id(),
|
|
132
|
+
Definition::ClassVariable(it) => it.str_id(),
|
|
133
|
+
Definition::AttrAccessor(it) => it.str_id(),
|
|
134
|
+
Definition::AttrReader(it) => it.str_id(),
|
|
135
|
+
Definition::AttrWriter(it) => it.str_id(),
|
|
136
|
+
Definition::Method(it) => it.str_id(),
|
|
137
|
+
Definition::MethodAlias(it) => it.new_name_str_id(),
|
|
138
|
+
Definition::GlobalVariableAlias(it) => it.new_name_str_id(),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
*id
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Returns an immutable reference to the strings map
|
|
145
|
+
#[must_use]
|
|
146
|
+
pub fn strings(&self) -> &IdentityHashMap<StringId, StringRef> {
|
|
147
|
+
&self.strings
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Returns an immutable reference to the URI pool map
|
|
151
|
+
#[must_use]
|
|
152
|
+
pub fn documents(&self) -> &IdentityHashMap<UriId, Document> {
|
|
153
|
+
&self.documents
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#[must_use]
|
|
157
|
+
pub fn definitions_to_declarations(&self) -> &IdentityHashMap<DefinitionId, DeclarationId> {
|
|
158
|
+
&self.definitions_to_declarations
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Returns an immutable reference to the constant references map
|
|
162
|
+
#[must_use]
|
|
163
|
+
pub fn constant_references(&self) -> &IdentityHashMap<ReferenceId, ConstantReference> {
|
|
164
|
+
&self.constant_references
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Returns an immutable reference to the method references map
|
|
168
|
+
#[must_use]
|
|
169
|
+
pub fn method_references(&self) -> &IdentityHashMap<ReferenceId, MethodRef> {
|
|
170
|
+
&self.method_references
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#[must_use]
|
|
174
|
+
pub fn all_diagnostics(&self) -> Vec<&Diagnostic> {
|
|
175
|
+
let document_diagnostics = self.documents.values().flat_map(Document::diagnostics);
|
|
176
|
+
let declaration_diagnostics = self.declarations.values().flat_map(Declaration::diagnostics);
|
|
177
|
+
let definition_diagnostics = self.definitions.values().flat_map(Definition::diagnostics);
|
|
178
|
+
let constant_reference_diagnostics = self
|
|
179
|
+
.constant_references
|
|
180
|
+
.values()
|
|
181
|
+
.flat_map(ConstantReference::diagnostics);
|
|
182
|
+
let method_reference_diagnostics = self.method_references.values().flat_map(MethodRef::diagnostics);
|
|
183
|
+
|
|
184
|
+
document_diagnostics
|
|
185
|
+
.chain(declaration_diagnostics)
|
|
186
|
+
.chain(definition_diagnostics)
|
|
187
|
+
.chain(constant_reference_diagnostics)
|
|
188
|
+
.chain(method_reference_diagnostics)
|
|
189
|
+
.collect()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Interns a string in the graph unless already interned. This method is only used to back the
|
|
193
|
+
/// `Graph#resolve_constant` Ruby API because every string must be interned in the graph to properly resolve.
|
|
194
|
+
pub fn intern_string(&mut self, string: String) -> StringId {
|
|
195
|
+
let string_id = StringId::from(&string);
|
|
196
|
+
match self.strings.entry(string_id) {
|
|
197
|
+
Entry::Occupied(mut entry) => {
|
|
198
|
+
entry.get_mut().increment_ref_count(1);
|
|
199
|
+
}
|
|
200
|
+
Entry::Vacant(entry) => {
|
|
201
|
+
entry.insert(StringRef::new(string));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
string_id
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Registers a name in the graph unless already registered. In regular indexing, this only happens in the local
|
|
208
|
+
/// graph. This method is only used to back the `Graph#resolve_constant` Ruby API because every name must be
|
|
209
|
+
/// registered in the graph to properly resolve
|
|
210
|
+
pub fn add_name(&mut self, name: Name) -> NameId {
|
|
211
|
+
let name_id = name.id();
|
|
212
|
+
|
|
213
|
+
match self.names.entry(name_id) {
|
|
214
|
+
Entry::Occupied(mut entry) => {
|
|
215
|
+
entry.get_mut().increment_ref_count(1);
|
|
216
|
+
}
|
|
217
|
+
Entry::Vacant(entry) => {
|
|
218
|
+
entry.insert(NameRef::Unresolved(Box::new(name)));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
name_id
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// # Panics
|
|
226
|
+
///
|
|
227
|
+
/// Panics if acquiring a read lock fails
|
|
228
|
+
#[must_use]
|
|
229
|
+
pub fn get(&self, name: &str) -> Option<Vec<&Definition>> {
|
|
230
|
+
let declaration_id = DeclarationId::from(name);
|
|
231
|
+
let declaration = self.declarations.get(&declaration_id)?;
|
|
232
|
+
|
|
233
|
+
Some(
|
|
234
|
+
declaration
|
|
235
|
+
.definitions()
|
|
236
|
+
.iter()
|
|
237
|
+
.filter_map(|id| self.definitions.get(id))
|
|
238
|
+
.collect(),
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Returns all target declaration IDs for a constant alias.
|
|
243
|
+
///
|
|
244
|
+
/// A constant alias can have multiple definitions (e.g., conditional assignment in different files),
|
|
245
|
+
/// each potentially pointing to a different target. This method collects all resolved targets.
|
|
246
|
+
///
|
|
247
|
+
/// Returns `None` if the declaration doesn't exist or is not a constant alias.
|
|
248
|
+
/// Returns `Some(vec![])` if no targets have been resolved yet.
|
|
249
|
+
#[must_use]
|
|
250
|
+
pub fn alias_targets(&self, declaration_id: &DeclarationId) -> Option<Vec<DeclarationId>> {
|
|
251
|
+
let declaration = self.declarations.get(declaration_id)?;
|
|
252
|
+
|
|
253
|
+
let Declaration::ConstantAlias(_) = declaration else {
|
|
254
|
+
return None;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
let mut targets = Vec::new();
|
|
258
|
+
for definition_id in declaration.definitions() {
|
|
259
|
+
let Some(Definition::ConstantAlias(alias_def)) = self.definitions.get(definition_id) else {
|
|
260
|
+
continue;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
let target_name_id = alias_def.target_name_id();
|
|
264
|
+
let Some(name_ref) = self.names.get(target_name_id) else {
|
|
265
|
+
continue;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
if let NameRef::Resolved(resolved) = name_ref {
|
|
269
|
+
let target_id = *resolved.declaration_id();
|
|
270
|
+
if !targets.contains(&target_id) {
|
|
271
|
+
targets.push(target_id);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
Some(targets)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#[must_use]
|
|
280
|
+
pub fn names(&self) -> &IdentityHashMap<NameId, NameRef> {
|
|
281
|
+
&self.names
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// Decrements the ref count for a name and removes it if the count reaches zero.
|
|
285
|
+
///
|
|
286
|
+
/// This does not recursively untrack `parent_scope` or `nesting` names.
|
|
287
|
+
pub fn untrack_name(&mut self, name_id: NameId) {
|
|
288
|
+
if let Some(name_ref) = self.names.get_mut(&name_id) {
|
|
289
|
+
let string_id = *name_ref.str();
|
|
290
|
+
if !name_ref.decrement_ref_count() {
|
|
291
|
+
self.names.remove(&name_id);
|
|
292
|
+
}
|
|
293
|
+
self.untrack_string(string_id);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
fn untrack_string(&mut self, string_id: StringId) {
|
|
298
|
+
if let Some(string_ref) = self.strings.get_mut(&string_id)
|
|
299
|
+
&& !string_ref.decrement_ref_count()
|
|
300
|
+
{
|
|
301
|
+
self.strings.remove(&string_id);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
fn untrack_definition_strings(&mut self, definition: &Definition) {
|
|
306
|
+
match definition {
|
|
307
|
+
Definition::Class(_)
|
|
308
|
+
| Definition::SingletonClass(_)
|
|
309
|
+
| Definition::Module(_)
|
|
310
|
+
| Definition::Constant(_)
|
|
311
|
+
| Definition::ConstantAlias(_) => {}
|
|
312
|
+
Definition::Method(d) => self.untrack_string(*d.str_id()),
|
|
313
|
+
Definition::AttrAccessor(d) => self.untrack_string(*d.str_id()),
|
|
314
|
+
Definition::AttrReader(d) => self.untrack_string(*d.str_id()),
|
|
315
|
+
Definition::AttrWriter(d) => self.untrack_string(*d.str_id()),
|
|
316
|
+
Definition::GlobalVariable(d) => self.untrack_string(*d.str_id()),
|
|
317
|
+
Definition::InstanceVariable(d) => self.untrack_string(*d.str_id()),
|
|
318
|
+
Definition::ClassVariable(d) => self.untrack_string(*d.str_id()),
|
|
319
|
+
Definition::MethodAlias(d) => {
|
|
320
|
+
self.untrack_string(*d.new_name_str_id());
|
|
321
|
+
self.untrack_string(*d.old_name_str_id());
|
|
322
|
+
}
|
|
323
|
+
Definition::GlobalVariableAlias(d) => {
|
|
324
|
+
self.untrack_string(*d.new_name_str_id());
|
|
325
|
+
self.untrack_string(*d.old_name_str_id());
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/// Decrements the ref count for a name and removes it if the count reaches zero.
|
|
331
|
+
///
|
|
332
|
+
/// This recursively untracks `parent_scope` and `nesting` names.
|
|
333
|
+
pub fn untrack_name_recursive(&mut self, name_id: NameId) {
|
|
334
|
+
let Some(name_ref) = self.names.get(&name_id) else {
|
|
335
|
+
return;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
let parent_scope = *name_ref.parent_scope();
|
|
339
|
+
let nesting = *name_ref.nesting();
|
|
340
|
+
|
|
341
|
+
self.untrack_name(name_id);
|
|
342
|
+
|
|
343
|
+
if let Some(parent_scope_id) = parent_scope {
|
|
344
|
+
self.untrack_name_recursive(parent_scope_id);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if let Some(nesting_id) = nesting {
|
|
348
|
+
self.untrack_name_recursive(nesting_id);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/// Register a member relationship from a declaration to another declaration through its unqualified name id. For example, in
|
|
353
|
+
///
|
|
354
|
+
/// ```ruby
|
|
355
|
+
/// module Foo
|
|
356
|
+
/// class Bar; end
|
|
357
|
+
/// def baz; end
|
|
358
|
+
/// end
|
|
359
|
+
/// ```
|
|
360
|
+
///
|
|
361
|
+
/// `Foo` has two members:
|
|
362
|
+
/// ```ruby
|
|
363
|
+
/// {
|
|
364
|
+
/// NameId(Bar) => DeclarationId(Bar)
|
|
365
|
+
/// NameId(baz) => DeclarationId(baz)
|
|
366
|
+
/// }
|
|
367
|
+
/// ```
|
|
368
|
+
///
|
|
369
|
+
/// # Panics
|
|
370
|
+
///
|
|
371
|
+
/// Will panic if the declaration ID passed doesn't belong to a namespace declaration
|
|
372
|
+
pub fn add_member(
|
|
373
|
+
&mut self,
|
|
374
|
+
owner_id: &DeclarationId,
|
|
375
|
+
member_declaration_id: DeclarationId,
|
|
376
|
+
member_str_id: StringId,
|
|
377
|
+
) {
|
|
378
|
+
if let Some(declaration) = self.declarations.get_mut(owner_id) {
|
|
379
|
+
match declaration {
|
|
380
|
+
Declaration::Namespace(Namespace::Class(it)) => it.add_member(member_str_id, member_declaration_id),
|
|
381
|
+
Declaration::Namespace(Namespace::Module(it)) => it.add_member(member_str_id, member_declaration_id),
|
|
382
|
+
Declaration::Namespace(Namespace::SingletonClass(it)) => {
|
|
383
|
+
it.add_member(member_str_id, member_declaration_id);
|
|
384
|
+
}
|
|
385
|
+
Declaration::Constant(_) => {
|
|
386
|
+
// TODO: temporary hack to avoid crashing on `Struct.new`, `Class.new` and `Module.new`
|
|
387
|
+
}
|
|
388
|
+
_ => panic!("Tried to add member to a declaration that isn't a namespace"),
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/// # Panics
|
|
394
|
+
///
|
|
395
|
+
/// This function will panic when trying to record a resolve name for a name ID that does not exist
|
|
396
|
+
pub fn record_resolved_name(&mut self, name_id: NameId, declaration_id: DeclarationId) {
|
|
397
|
+
match self.names.entry(name_id) {
|
|
398
|
+
Entry::Occupied(entry) => match entry.get() {
|
|
399
|
+
NameRef::Unresolved(_) => {
|
|
400
|
+
if let NameRef::Unresolved(unresolved) = entry.remove() {
|
|
401
|
+
let resolved_name = NameRef::Resolved(Box::new(ResolvedName::new(*unresolved, declaration_id)));
|
|
402
|
+
self.names.insert(name_id, resolved_name);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
NameRef::Resolved(_) => {
|
|
406
|
+
// TODO: consider if this is a valid scenario with the resolution phase design. Either collect
|
|
407
|
+
// metrics here or panic if it's never supposed to occur
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
Entry::Vacant(_) => panic!("Trying to record resolved name for a name ID that does not exist"),
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/// # Panics
|
|
415
|
+
///
|
|
416
|
+
/// Panics if acquiring a write lock fails
|
|
417
|
+
pub fn record_resolved_reference(&mut self, reference_id: ReferenceId, declaration_id: DeclarationId) {
|
|
418
|
+
if let Some(declaration) = self.declarations.get_mut(&declaration_id) {
|
|
419
|
+
declaration.add_reference(reference_id);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
//// Handles the deletion of a document identified by `uri`
|
|
424
|
+
pub fn delete_uri(&mut self, uri: &str) {
|
|
425
|
+
let uri_id = UriId::from(uri);
|
|
426
|
+
self.remove_definitions_for_uri(uri_id);
|
|
427
|
+
self.documents.remove(&uri_id);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/// Merges everything in `other` into this Graph. This method is meant to merge all graph representations from
|
|
431
|
+
/// different threads, but not meant to handle updates to the existing global representation
|
|
432
|
+
pub fn extend(&mut self, local_graph: LocalGraph) {
|
|
433
|
+
let (uri_id, document, definitions, strings, names, constant_references, method_references) =
|
|
434
|
+
local_graph.into_parts();
|
|
435
|
+
|
|
436
|
+
self.documents.insert(uri_id, document);
|
|
437
|
+
self.definitions.extend(definitions);
|
|
438
|
+
for (string_id, string_ref) in strings {
|
|
439
|
+
match self.strings.entry(string_id) {
|
|
440
|
+
Entry::Occupied(mut entry) => {
|
|
441
|
+
entry.get_mut().increment_ref_count(string_ref.ref_count());
|
|
442
|
+
}
|
|
443
|
+
Entry::Vacant(entry) => {
|
|
444
|
+
entry.insert(string_ref);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
for (name_id, name_ref) in names {
|
|
449
|
+
match self.names.entry(name_id) {
|
|
450
|
+
Entry::Occupied(mut entry) => {
|
|
451
|
+
entry.get_mut().increment_ref_count(name_ref.ref_count());
|
|
452
|
+
}
|
|
453
|
+
Entry::Vacant(entry) => {
|
|
454
|
+
entry.insert(name_ref);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
self.constant_references.extend(constant_references);
|
|
459
|
+
self.method_references.extend(method_references);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/// Updates the global representation with the information contained in `other`, handling deletions, insertions and
|
|
463
|
+
/// updates to existing entries
|
|
464
|
+
pub fn update(&mut self, other: LocalGraph) {
|
|
465
|
+
// For each URI that was indexed through `other`, check what was discovered and update our current global
|
|
466
|
+
// representation
|
|
467
|
+
let uri_id = other.uri_id();
|
|
468
|
+
self.remove_definitions_for_uri(uri_id);
|
|
469
|
+
|
|
470
|
+
self.extend(other);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Removes all nodes and relationships associated to the given URI. This is used to clean up stale data when a
|
|
474
|
+
// document (identified by `uri_id`) changes or when a document is closed and we need to clean up the memory
|
|
475
|
+
fn remove_definitions_for_uri(&mut self, uri_id: UriId) {
|
|
476
|
+
let Some(document) = self.documents.remove(&uri_id) else {
|
|
477
|
+
return;
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// TODO: Remove method references from method declarations once method inference is implemented
|
|
481
|
+
for ref_id in document.method_references() {
|
|
482
|
+
if let Some(method_ref) = self.method_references.remove(ref_id) {
|
|
483
|
+
self.untrack_string(*method_ref.str());
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
for ref_id in document.constant_references() {
|
|
488
|
+
if let Some(constant_ref) = self.constant_references.remove(ref_id) {
|
|
489
|
+
if let Some(NameRef::Resolved(resolved)) = self.names.get(constant_ref.name_id())
|
|
490
|
+
&& let Some(declaration) = self.declarations.get_mut(resolved.declaration_id())
|
|
491
|
+
{
|
|
492
|
+
declaration.remove_reference(ref_id);
|
|
493
|
+
}
|
|
494
|
+
self.untrack_name(*constant_ref.name_id());
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Vector of (owner_declaration_id, member_name_id) to delete after processing all definitions
|
|
499
|
+
let mut members_to_delete: Vec<(DeclarationId, StringId)> = Vec::new();
|
|
500
|
+
let mut declarations_to_delete: Vec<DeclarationId> = Vec::new();
|
|
501
|
+
let mut declarations_to_invalidate_ancestor_chains: Vec<DeclarationId> = Vec::new();
|
|
502
|
+
|
|
503
|
+
for def_id in document.definitions() {
|
|
504
|
+
if let Some(definition) = self.definitions.remove(def_id) {
|
|
505
|
+
if let Some(declaration_id) = self.definitions_to_declarations.remove(def_id)
|
|
506
|
+
&& let Some(declaration) = self.declarations.get_mut(&declaration_id)
|
|
507
|
+
&& declaration.remove_definition(def_id)
|
|
508
|
+
{
|
|
509
|
+
declaration.clear_diagnostics();
|
|
510
|
+
if declaration.as_namespace().is_some() {
|
|
511
|
+
declarations_to_invalidate_ancestor_chains.push(declaration_id);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if declaration.has_no_definitions() {
|
|
515
|
+
let unqualified_str_id = StringId::from(&declaration.unqualified_name());
|
|
516
|
+
members_to_delete.push((*declaration.owner_id(), unqualified_str_id));
|
|
517
|
+
declarations_to_delete.push(declaration_id);
|
|
518
|
+
|
|
519
|
+
if let Some(namespace) = declaration.as_namespace()
|
|
520
|
+
&& let Some(singleton_id) = namespace.singleton_class()
|
|
521
|
+
{
|
|
522
|
+
declarations_to_delete.push(*singleton_id);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if let Some(name_id) = definition.name_id() {
|
|
528
|
+
self.untrack_name(*name_id);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
self.untrack_definition_strings(&definition);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
self.invalidate_ancestor_chains(declarations_to_invalidate_ancestor_chains);
|
|
536
|
+
|
|
537
|
+
for declaration_id in declarations_to_delete {
|
|
538
|
+
self.declarations.remove(&declaration_id);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Clean up any members that pointed to declarations that were removed
|
|
542
|
+
for (owner_id, member_str_id) in members_to_delete {
|
|
543
|
+
// Remove the `if` and use `unwrap` once we are indexing RBS files to have `Object`
|
|
544
|
+
if let Some(owner) = self.declarations.get_mut(&owner_id) {
|
|
545
|
+
match owner {
|
|
546
|
+
Declaration::Namespace(Namespace::Class(owner)) => {
|
|
547
|
+
owner.remove_member(&member_str_id);
|
|
548
|
+
}
|
|
549
|
+
Declaration::Namespace(Namespace::SingletonClass(owner)) => {
|
|
550
|
+
owner.remove_member(&member_str_id);
|
|
551
|
+
}
|
|
552
|
+
Declaration::Namespace(Namespace::Module(owner)) => {
|
|
553
|
+
owner.remove_member(&member_str_id);
|
|
554
|
+
}
|
|
555
|
+
_ => {} // Nothing happens
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
fn invalidate_ancestor_chains(&mut self, initial_ids: Vec<DeclarationId>) {
|
|
562
|
+
let mut queue = initial_ids;
|
|
563
|
+
let mut visited = IdentityHashSet::<DeclarationId>::default();
|
|
564
|
+
|
|
565
|
+
while let Some(declaration_id) = queue.pop() {
|
|
566
|
+
if !visited.insert(declaration_id) {
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
let namespace = self
|
|
571
|
+
.declarations_mut()
|
|
572
|
+
.get_mut(&declaration_id)
|
|
573
|
+
.unwrap()
|
|
574
|
+
.as_namespace_mut()
|
|
575
|
+
.expect("expected namespace declaration");
|
|
576
|
+
|
|
577
|
+
for ancestor in &namespace.ancestors() {
|
|
578
|
+
if let Ancestor::Complete(ancestor_id) = ancestor {
|
|
579
|
+
self.declarations_mut()
|
|
580
|
+
.get_mut(ancestor_id)
|
|
581
|
+
.unwrap()
|
|
582
|
+
.as_namespace_mut()
|
|
583
|
+
.unwrap()
|
|
584
|
+
.remove_descendant(&declaration_id);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
let namespace = self
|
|
589
|
+
.declarations_mut()
|
|
590
|
+
.get_mut(&declaration_id)
|
|
591
|
+
.unwrap()
|
|
592
|
+
.as_namespace_mut()
|
|
593
|
+
.unwrap();
|
|
594
|
+
|
|
595
|
+
namespace.for_each_descendant(|descendant_id| {
|
|
596
|
+
queue.push(*descendant_id);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
namespace.clear_ancestors();
|
|
600
|
+
namespace.clear_descendants();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/// Sets the encoding that should be used for transforming byte offsets into LSP code unit line/column positions
|
|
605
|
+
pub fn set_encoding(&mut self, encoding: Encoding) {
|
|
606
|
+
self.position_encoding = encoding;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
#[must_use]
|
|
610
|
+
pub fn encoding(&self) -> &Encoding {
|
|
611
|
+
&self.position_encoding
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/// # Panics
|
|
615
|
+
///
|
|
616
|
+
/// Panics if acquiring a read lock fails
|
|
617
|
+
#[allow(clippy::cast_precision_loss)]
|
|
618
|
+
pub fn print_query_statistics(&self) {
|
|
619
|
+
use std::collections::HashMap;
|
|
620
|
+
|
|
621
|
+
let mut declarations_with_docs = 0;
|
|
622
|
+
let mut total_doc_size = 0;
|
|
623
|
+
let mut declarations_types: HashMap<&str, usize> = HashMap::new();
|
|
624
|
+
let mut definition_types: HashMap<&str, usize> = HashMap::new();
|
|
625
|
+
let mut multi_definition_count = 0;
|
|
626
|
+
|
|
627
|
+
for declaration in self.declarations.values() {
|
|
628
|
+
// Check documentation
|
|
629
|
+
if let Some(definitions) = self.get(declaration.name()) {
|
|
630
|
+
let has_docs = definitions.iter().any(|def| !def.comments().is_empty());
|
|
631
|
+
if has_docs {
|
|
632
|
+
declarations_with_docs += 1;
|
|
633
|
+
let doc_size: usize = definitions.iter().map(|def| def.comments().len()).sum();
|
|
634
|
+
total_doc_size += doc_size;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
*declarations_types.entry(declaration.kind()).or_insert(0) += 1;
|
|
639
|
+
|
|
640
|
+
// Count definitions by type
|
|
641
|
+
let definition_count = declaration.definitions().len();
|
|
642
|
+
if definition_count > 1 {
|
|
643
|
+
multi_definition_count += 1;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
for def_id in declaration.definitions() {
|
|
647
|
+
if let Some(def) = self.definitions().get(def_id) {
|
|
648
|
+
*definition_types.entry(def.kind()).or_insert(0) += 1;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
println!();
|
|
654
|
+
println!("Query statistics");
|
|
655
|
+
let total_declarations = self.declarations.len();
|
|
656
|
+
println!(" Total declarations: {total_declarations}");
|
|
657
|
+
println!(
|
|
658
|
+
" With documentation: {} ({:.1}%)",
|
|
659
|
+
declarations_with_docs,
|
|
660
|
+
stats::percentage(declarations_with_docs, total_declarations)
|
|
661
|
+
);
|
|
662
|
+
println!(
|
|
663
|
+
" Without documentation: {} ({:.1}%)",
|
|
664
|
+
total_declarations - declarations_with_docs,
|
|
665
|
+
stats::percentage(total_declarations - declarations_with_docs, total_declarations)
|
|
666
|
+
);
|
|
667
|
+
println!(" Total documentation size: {total_doc_size} bytes");
|
|
668
|
+
println!(
|
|
669
|
+
" Multi-definition names: {} ({:.1}%)",
|
|
670
|
+
multi_definition_count,
|
|
671
|
+
stats::percentage(multi_definition_count, total_declarations)
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
println!();
|
|
675
|
+
println!("Declaration breakdown:");
|
|
676
|
+
let mut types: Vec<_> = declarations_types.iter().collect();
|
|
677
|
+
types.sort_by_key(|(_, count)| std::cmp::Reverse(**count));
|
|
678
|
+
for (kind, count) in types {
|
|
679
|
+
println!(" {kind:20} {count:6}");
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
println!();
|
|
683
|
+
println!("Definition breakdown:");
|
|
684
|
+
let mut types: Vec<_> = definition_types.iter().collect();
|
|
685
|
+
types.sort_by_key(|(_, count)| std::cmp::Reverse(**count));
|
|
686
|
+
for (kind, count) in types {
|
|
687
|
+
println!(" {kind:20} {count:6}");
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
#[cfg(test)]
|
|
693
|
+
mod tests {
|
|
694
|
+
use super::*;
|
|
695
|
+
use crate::model::comment::Comment;
|
|
696
|
+
use crate::model::declaration::Ancestors;
|
|
697
|
+
use crate::test_utils::GraphTest;
|
|
698
|
+
|
|
699
|
+
#[test]
|
|
700
|
+
fn deleting_a_uri() {
|
|
701
|
+
let mut context = GraphTest::new();
|
|
702
|
+
|
|
703
|
+
context.index_uri("file:///foo.rb", "module Foo; end");
|
|
704
|
+
context.delete_uri("file:///foo.rb");
|
|
705
|
+
context.resolve();
|
|
706
|
+
|
|
707
|
+
assert!(context.graph().documents.is_empty());
|
|
708
|
+
assert!(context.graph().definitions.is_empty());
|
|
709
|
+
// Object is left
|
|
710
|
+
assert!(
|
|
711
|
+
context
|
|
712
|
+
.graph()
|
|
713
|
+
.declarations()
|
|
714
|
+
.get(&DeclarationId::from("Foo"))
|
|
715
|
+
.is_none()
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
#[test]
|
|
720
|
+
fn updating_index_with_deleted_definitions() {
|
|
721
|
+
let mut context = GraphTest::new();
|
|
722
|
+
|
|
723
|
+
context.index_uri("file:///foo.rb", "module Foo; end");
|
|
724
|
+
|
|
725
|
+
assert_eq!(context.graph().definitions.len(), 1);
|
|
726
|
+
assert_eq!(context.graph().documents.len(), 1);
|
|
727
|
+
|
|
728
|
+
// Update with empty content to remove definitions but keep the URI
|
|
729
|
+
context.index_uri("file:///foo.rb", "");
|
|
730
|
+
|
|
731
|
+
assert!(context.graph().definitions.is_empty());
|
|
732
|
+
|
|
733
|
+
// URI remains if the file was not deleted, but definitions got erased
|
|
734
|
+
assert_eq!(context.graph().documents.len(), 1);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
#[test]
|
|
738
|
+
fn updating_index_with_deleted_definitions_after_resolution() {
|
|
739
|
+
let mut context = GraphTest::new();
|
|
740
|
+
|
|
741
|
+
context.index_uri("file:///foo.rb", "module Foo; end");
|
|
742
|
+
context.resolve();
|
|
743
|
+
|
|
744
|
+
assert_eq!(context.graph().definitions.len(), 1);
|
|
745
|
+
assert_eq!(context.graph().definitions_to_declarations().len(), 1);
|
|
746
|
+
assert_eq!(context.graph().documents.len(), 1);
|
|
747
|
+
|
|
748
|
+
{
|
|
749
|
+
assert!(
|
|
750
|
+
context
|
|
751
|
+
.graph()
|
|
752
|
+
.declarations()
|
|
753
|
+
.get(&DeclarationId::from("Foo"))
|
|
754
|
+
.is_some()
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Update with empty content to remove definitions but keep the URI
|
|
759
|
+
context.index_uri("file:///foo.rb", "");
|
|
760
|
+
|
|
761
|
+
assert!(context.graph().definitions.is_empty());
|
|
762
|
+
assert!(context.graph().definitions_to_declarations().is_empty());
|
|
763
|
+
// URI remains if the file was not deleted, but definitions and declarations got erased
|
|
764
|
+
assert_eq!(context.graph().documents.len(), 1);
|
|
765
|
+
|
|
766
|
+
{
|
|
767
|
+
assert!(
|
|
768
|
+
context
|
|
769
|
+
.graph()
|
|
770
|
+
.declarations()
|
|
771
|
+
.get(&DeclarationId::from("Foo"))
|
|
772
|
+
.is_none()
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
#[test]
|
|
778
|
+
fn updating_index_with_deleted_references() {
|
|
779
|
+
let mut context = GraphTest::new();
|
|
780
|
+
|
|
781
|
+
context.index_uri("file:///definition.rb", "module Foo; end");
|
|
782
|
+
context.index_uri(
|
|
783
|
+
"file:///references.rb",
|
|
784
|
+
r"
|
|
785
|
+
Foo
|
|
786
|
+
bar
|
|
787
|
+
BAZ
|
|
788
|
+
",
|
|
789
|
+
);
|
|
790
|
+
context.resolve();
|
|
791
|
+
|
|
792
|
+
assert_eq!(context.graph().documents.len(), 2);
|
|
793
|
+
assert_eq!(context.graph().method_references.len(), 1);
|
|
794
|
+
assert_eq!(context.graph().constant_references.len(), 2);
|
|
795
|
+
{
|
|
796
|
+
let declaration = context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap();
|
|
797
|
+
assert_eq!(declaration.references().len(), 1);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Update with empty content to remove definitions but keep the URI
|
|
801
|
+
context.index_uri("file:///references.rb", "");
|
|
802
|
+
|
|
803
|
+
// URI remains if the file was not deleted, but references got erased
|
|
804
|
+
assert_eq!(context.graph().documents.len(), 2);
|
|
805
|
+
assert!(context.graph().method_references.is_empty());
|
|
806
|
+
assert!(context.graph().constant_references.is_empty());
|
|
807
|
+
{
|
|
808
|
+
let declaration = context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap();
|
|
809
|
+
assert!(declaration.references().is_empty());
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
#[test]
|
|
814
|
+
fn invalidating_ancestor_chains_when_document_changes() {
|
|
815
|
+
let mut context = GraphTest::new();
|
|
816
|
+
|
|
817
|
+
context.index_uri("file:///a.rb", "class Foo; include Bar; def method_name; end; end");
|
|
818
|
+
context.index_uri("file:///b.rb", "class Foo; end");
|
|
819
|
+
context.index_uri("file:///c.rb", "module Bar; end");
|
|
820
|
+
context.index_uri("file:///d.rb", "class Baz < Foo; end");
|
|
821
|
+
context.resolve();
|
|
822
|
+
|
|
823
|
+
let foo_declaration = context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap();
|
|
824
|
+
assert!(matches!(
|
|
825
|
+
foo_declaration.as_namespace().unwrap().ancestors(),
|
|
826
|
+
Ancestors::Complete(_)
|
|
827
|
+
));
|
|
828
|
+
|
|
829
|
+
let baz_declaration = context.graph().declarations().get(&DeclarationId::from("Baz")).unwrap();
|
|
830
|
+
assert!(matches!(
|
|
831
|
+
baz_declaration.as_namespace().unwrap().ancestors(),
|
|
832
|
+
Ancestors::Complete(_)
|
|
833
|
+
));
|
|
834
|
+
|
|
835
|
+
{
|
|
836
|
+
let Declaration::Namespace(Namespace::Module(bar)) =
|
|
837
|
+
context.graph().declarations().get(&DeclarationId::from("Bar")).unwrap()
|
|
838
|
+
else {
|
|
839
|
+
panic!("Expected Bar to be a module");
|
|
840
|
+
};
|
|
841
|
+
assert!(bar.descendants().contains(&DeclarationId::from("Foo")));
|
|
842
|
+
|
|
843
|
+
let Declaration::Namespace(Namespace::Class(foo)) =
|
|
844
|
+
context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap()
|
|
845
|
+
else {
|
|
846
|
+
panic!("Expected Foo to be a class");
|
|
847
|
+
};
|
|
848
|
+
assert!(foo.descendants().contains(&DeclarationId::from("Baz")));
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
context.index_uri("file:///a.rb", "");
|
|
852
|
+
|
|
853
|
+
{
|
|
854
|
+
let Declaration::Namespace(Namespace::Class(foo)) =
|
|
855
|
+
context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap()
|
|
856
|
+
else {
|
|
857
|
+
panic!("Expected Foo to be a class");
|
|
858
|
+
};
|
|
859
|
+
assert!(matches!(foo.clone_ancestors(), Ancestors::Partial(a) if a.is_empty()));
|
|
860
|
+
assert!(foo.descendants().is_empty());
|
|
861
|
+
|
|
862
|
+
let Declaration::Namespace(Namespace::Class(baz)) =
|
|
863
|
+
context.graph().declarations().get(&DeclarationId::from("Baz")).unwrap()
|
|
864
|
+
else {
|
|
865
|
+
panic!("Expected Baz to be a class");
|
|
866
|
+
};
|
|
867
|
+
assert!(matches!(baz.clone_ancestors(), Ancestors::Partial(a) if a.is_empty()));
|
|
868
|
+
assert!(baz.descendants().is_empty());
|
|
869
|
+
|
|
870
|
+
let Declaration::Namespace(Namespace::Module(bar)) =
|
|
871
|
+
context.graph().declarations().get(&DeclarationId::from("Bar")).unwrap()
|
|
872
|
+
else {
|
|
873
|
+
panic!("Expected Bar to be a module");
|
|
874
|
+
};
|
|
875
|
+
assert!(!bar.descendants().contains(&DeclarationId::from("Foo")));
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
context.resolve();
|
|
879
|
+
|
|
880
|
+
let baz_declaration = context.graph().declarations().get(&DeclarationId::from("Baz")).unwrap();
|
|
881
|
+
assert!(matches!(
|
|
882
|
+
baz_declaration.as_namespace().unwrap().ancestors(),
|
|
883
|
+
Ancestors::Complete(_)
|
|
884
|
+
));
|
|
885
|
+
|
|
886
|
+
{
|
|
887
|
+
let Declaration::Namespace(Namespace::Class(foo)) =
|
|
888
|
+
context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap()
|
|
889
|
+
else {
|
|
890
|
+
panic!("Expected Foo to be a class");
|
|
891
|
+
};
|
|
892
|
+
assert!(foo.descendants().contains(&DeclarationId::from("Baz")));
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
#[test]
|
|
897
|
+
fn name_count_increments_for_duplicates() {
|
|
898
|
+
let mut context = GraphTest::new();
|
|
899
|
+
|
|
900
|
+
context.index_uri("file:///foo.rb", "module Foo; end");
|
|
901
|
+
context.index_uri("file:///foo2.rb", "module Foo; end");
|
|
902
|
+
context.index_uri("file:///foo3.rb", "Foo");
|
|
903
|
+
context.resolve();
|
|
904
|
+
|
|
905
|
+
assert_eq!(context.graph().names().len(), 1);
|
|
906
|
+
let name_ref = context.graph().names().values().next().unwrap();
|
|
907
|
+
assert_eq!(name_ref.ref_count(), 3);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
#[test]
|
|
911
|
+
fn string_ref_count_increments_for_duplicate_definitions() {
|
|
912
|
+
let mut context = GraphTest::new();
|
|
913
|
+
|
|
914
|
+
context.index_uri(
|
|
915
|
+
"file:///foo.rb",
|
|
916
|
+
"
|
|
917
|
+
def method_name; end
|
|
918
|
+
attr_accessor :accessor_name
|
|
919
|
+
attr_reader :reader_name
|
|
920
|
+
attr_writer :writer_name
|
|
921
|
+
$global_var = 1
|
|
922
|
+
@@class_var = 1
|
|
923
|
+
class Foo
|
|
924
|
+
def initialize
|
|
925
|
+
@instance_var = 1
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
def old_method; end
|
|
929
|
+
alias_method :new_method, :old_method
|
|
930
|
+
$old_global = 1
|
|
931
|
+
alias $new_global $old_global
|
|
932
|
+
",
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
context.resolve();
|
|
936
|
+
|
|
937
|
+
let strings = context.graph().strings();
|
|
938
|
+
assert_eq!(strings.get(&StringId::from("method_name()")).unwrap().ref_count(), 1);
|
|
939
|
+
assert_eq!(strings.get(&StringId::from("accessor_name()")).unwrap().ref_count(), 1);
|
|
940
|
+
assert_eq!(strings.get(&StringId::from("reader_name()")).unwrap().ref_count(), 1);
|
|
941
|
+
assert_eq!(strings.get(&StringId::from("writer_name()")).unwrap().ref_count(), 1);
|
|
942
|
+
assert_eq!(strings.get(&StringId::from("$global_var")).unwrap().ref_count(), 1);
|
|
943
|
+
assert_eq!(strings.get(&StringId::from("@@class_var")).unwrap().ref_count(), 1);
|
|
944
|
+
assert_eq!(strings.get(&StringId::from("@instance_var")).unwrap().ref_count(), 1);
|
|
945
|
+
assert_eq!(strings.get(&StringId::from("old_method()")).unwrap().ref_count(), 2);
|
|
946
|
+
assert_eq!(strings.get(&StringId::from("new_method()")).unwrap().ref_count(), 1);
|
|
947
|
+
assert_eq!(strings.get(&StringId::from("$old_global")).unwrap().ref_count(), 2);
|
|
948
|
+
assert_eq!(strings.get(&StringId::from("$new_global")).unwrap().ref_count(), 1);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
#[test]
|
|
952
|
+
fn updating_index_with_deleted_names() {
|
|
953
|
+
let mut context = GraphTest::new();
|
|
954
|
+
|
|
955
|
+
context.index_uri("file:///foo.rb", "module Foo; end");
|
|
956
|
+
context.index_uri("file:///bar.rb", "Foo");
|
|
957
|
+
context.resolve();
|
|
958
|
+
|
|
959
|
+
assert_eq!(context.graph().names().len(), 1);
|
|
960
|
+
assert_eq!(context.graph().names().values().next().unwrap().ref_count(), 2);
|
|
961
|
+
|
|
962
|
+
context.delete_uri("file:///foo.rb");
|
|
963
|
+
assert_eq!(context.graph().names().len(), 1);
|
|
964
|
+
assert_eq!(context.graph().names().values().next().unwrap().ref_count(), 1);
|
|
965
|
+
|
|
966
|
+
context.delete_uri("file:///bar.rb");
|
|
967
|
+
assert!(context.graph().names().is_empty());
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
#[test]
|
|
971
|
+
fn updating_index_with_deleted_strings() {
|
|
972
|
+
let mut context = GraphTest::new();
|
|
973
|
+
|
|
974
|
+
context.index_uri(
|
|
975
|
+
"file:///foo.rb",
|
|
976
|
+
"
|
|
977
|
+
Foo
|
|
978
|
+
foo.method_call
|
|
979
|
+
def method_name; end
|
|
980
|
+
",
|
|
981
|
+
);
|
|
982
|
+
context.resolve();
|
|
983
|
+
|
|
984
|
+
let strings = context.graph().strings();
|
|
985
|
+
assert!(strings.get(&StringId::from("Foo")).is_some());
|
|
986
|
+
assert!(strings.get(&StringId::from("method_call")).is_some());
|
|
987
|
+
assert!(strings.get(&StringId::from("method_name()")).is_some());
|
|
988
|
+
|
|
989
|
+
context.delete_uri("file:///foo.rb");
|
|
990
|
+
let strings = context.graph().strings();
|
|
991
|
+
assert!(strings.get(&StringId::from("Foo")).is_none());
|
|
992
|
+
assert!(strings.get(&StringId::from("method_call")).is_none());
|
|
993
|
+
assert!(strings.get(&StringId::from("method_name()")).is_none());
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
#[test]
|
|
997
|
+
fn updating_index_with_new_definitions() {
|
|
998
|
+
let mut context = GraphTest::new();
|
|
999
|
+
|
|
1000
|
+
context.index_uri("file:///foo.rb", "module Foo; end");
|
|
1001
|
+
context.resolve();
|
|
1002
|
+
|
|
1003
|
+
assert_eq!(context.graph().definitions.len(), 1);
|
|
1004
|
+
let declaration = context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap();
|
|
1005
|
+
assert_eq!(declaration.name(), "Foo");
|
|
1006
|
+
let document = context.graph().documents.get(&UriId::from("file:///foo.rb")).unwrap();
|
|
1007
|
+
assert_eq!(document.uri(), "file:///foo.rb");
|
|
1008
|
+
assert_eq!(declaration.definitions().len(), 1);
|
|
1009
|
+
assert_eq!(document.definitions().len(), 1);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
#[test]
|
|
1013
|
+
fn updating_existing_definitions() {
|
|
1014
|
+
let mut context = GraphTest::new();
|
|
1015
|
+
|
|
1016
|
+
context.index_uri("file:///foo.rb", "module Foo; end");
|
|
1017
|
+
// Update with the same definition but at a different position (with content before it)
|
|
1018
|
+
context.index_uri("file:///foo.rb", "\n\n\n\n\n\nmodule Foo; end");
|
|
1019
|
+
context.resolve();
|
|
1020
|
+
|
|
1021
|
+
assert_eq!(context.graph().definitions.len(), 1);
|
|
1022
|
+
let declaration = context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap();
|
|
1023
|
+
assert_eq!(declaration.name(), "Foo");
|
|
1024
|
+
assert_eq!(
|
|
1025
|
+
context
|
|
1026
|
+
.graph()
|
|
1027
|
+
.documents()
|
|
1028
|
+
.get(&UriId::from("file:///foo.rb"))
|
|
1029
|
+
.unwrap()
|
|
1030
|
+
.uri(),
|
|
1031
|
+
"file:///foo.rb"
|
|
1032
|
+
);
|
|
1033
|
+
|
|
1034
|
+
let definitions = context.graph().get("Foo").unwrap();
|
|
1035
|
+
assert_eq!(definitions.len(), 1);
|
|
1036
|
+
assert_eq!(definitions[0].offset().start(), 6);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
#[test]
|
|
1040
|
+
fn adding_another_definition_from_a_different_uri() {
|
|
1041
|
+
let mut context = GraphTest::new();
|
|
1042
|
+
|
|
1043
|
+
context.index_uri("file:///foo.rb", "module Foo; end");
|
|
1044
|
+
context.index_uri("file:///foo2.rb", "\n\n\n\n\nmodule Foo; end");
|
|
1045
|
+
context.resolve();
|
|
1046
|
+
|
|
1047
|
+
let definitions = context.graph().get("Foo").unwrap();
|
|
1048
|
+
let mut offsets = definitions.iter().map(|d| d.offset().start()).collect::<Vec<_>>();
|
|
1049
|
+
offsets.sort_unstable();
|
|
1050
|
+
assert_eq!(definitions.len(), 2);
|
|
1051
|
+
assert_eq!(vec![0, 5], offsets);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
#[test]
|
|
1055
|
+
fn adding_a_second_definition_from_the_same_uri() {
|
|
1056
|
+
let mut context = GraphTest::new();
|
|
1057
|
+
|
|
1058
|
+
context.index_uri("file:///foo.rb", "module Foo; end");
|
|
1059
|
+
|
|
1060
|
+
// Update with multiple definitions of the same module in one file
|
|
1061
|
+
context.index_uri("file:///foo.rb", {
|
|
1062
|
+
"
|
|
1063
|
+
module Foo; end
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
module Foo; end
|
|
1067
|
+
"
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
context.resolve();
|
|
1071
|
+
|
|
1072
|
+
let definitions = context.graph().get("Foo").unwrap();
|
|
1073
|
+
assert_eq!(definitions.len(), 2);
|
|
1074
|
+
|
|
1075
|
+
let mut offsets = definitions
|
|
1076
|
+
.iter()
|
|
1077
|
+
.map(|d| [d.offset().start(), d.offset().end()])
|
|
1078
|
+
.collect::<Vec<_>>();
|
|
1079
|
+
offsets.sort_unstable();
|
|
1080
|
+
assert_eq!([0, 15], offsets[0]);
|
|
1081
|
+
assert_eq!([18, 33], offsets[1]);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
#[test]
|
|
1085
|
+
fn get_documentation() {
|
|
1086
|
+
let mut context = GraphTest::new();
|
|
1087
|
+
|
|
1088
|
+
context.index_uri("file:///foo.rb", {
|
|
1089
|
+
"
|
|
1090
|
+
# This is a class comment
|
|
1091
|
+
# Multi-line comment
|
|
1092
|
+
class CommentedClass; end
|
|
1093
|
+
|
|
1094
|
+
# Module comment
|
|
1095
|
+
module CommentedModule; end
|
|
1096
|
+
|
|
1097
|
+
class NoCommentClass; end
|
|
1098
|
+
"
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
context.resolve();
|
|
1102
|
+
|
|
1103
|
+
let definitions = context.graph().get("CommentedClass").unwrap();
|
|
1104
|
+
let def = definitions.first().unwrap();
|
|
1105
|
+
assert_eq!(
|
|
1106
|
+
def.comments().iter().map(Comment::string).collect::<Vec<&String>>(),
|
|
1107
|
+
vec!["# This is a class comment", "# Multi-line comment"]
|
|
1108
|
+
);
|
|
1109
|
+
|
|
1110
|
+
let definitions = context.graph().get("CommentedModule").unwrap();
|
|
1111
|
+
let def = definitions.first().unwrap();
|
|
1112
|
+
assert_eq!(
|
|
1113
|
+
def.comments().iter().map(Comment::string).collect::<Vec<&String>>(),
|
|
1114
|
+
vec!["# Module comment"]
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
let definitions = context.graph().get("NoCommentClass").unwrap();
|
|
1118
|
+
let def = definitions.first().unwrap();
|
|
1119
|
+
assert!(def.comments().is_empty());
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
#[test]
|
|
1123
|
+
fn members_are_updated_when_definitions_get_deleted() {
|
|
1124
|
+
let mut context = GraphTest::new();
|
|
1125
|
+
// Initially, have `Foo` defined twice with a member called `Bar`
|
|
1126
|
+
context.index_uri("file:///foo.rb", {
|
|
1127
|
+
r"
|
|
1128
|
+
module Foo
|
|
1129
|
+
end
|
|
1130
|
+
"
|
|
1131
|
+
});
|
|
1132
|
+
context.index_uri("file:///foo2.rb", {
|
|
1133
|
+
r"
|
|
1134
|
+
module Foo
|
|
1135
|
+
class Bar; end
|
|
1136
|
+
end
|
|
1137
|
+
"
|
|
1138
|
+
});
|
|
1139
|
+
context.resolve();
|
|
1140
|
+
|
|
1141
|
+
{
|
|
1142
|
+
if let Declaration::Namespace(Namespace::Module(foo)) =
|
|
1143
|
+
context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap()
|
|
1144
|
+
{
|
|
1145
|
+
assert!(foo.members().contains_key(&StringId::from("Bar")));
|
|
1146
|
+
} else {
|
|
1147
|
+
panic!("Expected Foo to be a module");
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Delete `Bar`
|
|
1152
|
+
context.index_uri("file:///foo2.rb", {
|
|
1153
|
+
r"
|
|
1154
|
+
module Foo
|
|
1155
|
+
end
|
|
1156
|
+
"
|
|
1157
|
+
});
|
|
1158
|
+
context.resolve();
|
|
1159
|
+
|
|
1160
|
+
if let Declaration::Namespace(Namespace::Module(foo)) =
|
|
1161
|
+
context.graph().declarations().get(&DeclarationId::from("Foo")).unwrap()
|
|
1162
|
+
{
|
|
1163
|
+
assert!(!foo.members().contains_key(&StringId::from("Bar")));
|
|
1164
|
+
} else {
|
|
1165
|
+
panic!("Expected Foo to be a module");
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
#[test]
|
|
1170
|
+
fn updating_index_with_deleted_diagnostics() {
|
|
1171
|
+
let mut context = GraphTest::new();
|
|
1172
|
+
|
|
1173
|
+
// TODO: Add resolution error to test diagnostics attached to declarations
|
|
1174
|
+
context.index_uri("file:///foo.rb", "class Foo");
|
|
1175
|
+
assert!(!context.graph().all_diagnostics().is_empty());
|
|
1176
|
+
|
|
1177
|
+
context.index_uri("file:///foo.rb", "class Foo; end");
|
|
1178
|
+
assert!(context.graph().all_diagnostics().is_empty());
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
#[test]
|
|
1182
|
+
fn diagnostics_are_collected() {
|
|
1183
|
+
let mut context = GraphTest::new();
|
|
1184
|
+
|
|
1185
|
+
context.index_uri("file:///foo1.rb", {
|
|
1186
|
+
r"
|
|
1187
|
+
class Foo
|
|
1188
|
+
"
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
context.index_uri("file:///foo2.rb", {
|
|
1192
|
+
r"
|
|
1193
|
+
foo = 42
|
|
1194
|
+
"
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
let mut diagnostics: Vec<String> = context
|
|
1198
|
+
.graph()
|
|
1199
|
+
.all_diagnostics()
|
|
1200
|
+
.iter()
|
|
1201
|
+
.map(|d| {
|
|
1202
|
+
format!(
|
|
1203
|
+
"{}: {} ({})",
|
|
1204
|
+
d.rule(),
|
|
1205
|
+
d.message(),
|
|
1206
|
+
context.graph().documents().get(d.uri_id()).unwrap().uri()
|
|
1207
|
+
)
|
|
1208
|
+
})
|
|
1209
|
+
.collect();
|
|
1210
|
+
|
|
1211
|
+
diagnostics.sort();
|
|
1212
|
+
|
|
1213
|
+
assert_eq!(
|
|
1214
|
+
vec![
|
|
1215
|
+
"parse-error: expected an `end` to close the `class` statement (file:///foo1.rb)",
|
|
1216
|
+
"parse-error: unexpected end-of-input, assuming it is closing the parent top level context (file:///foo1.rb)",
|
|
1217
|
+
"parse-warning: assigned but unused variable - foo (file:///foo2.rb)",
|
|
1218
|
+
],
|
|
1219
|
+
diagnostics,
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
#[test]
|
|
1224
|
+
fn removing_method_def_with_conflicting_constant_name() {
|
|
1225
|
+
let mut context = GraphTest::new();
|
|
1226
|
+
context.index_uri("file:///foo.rb", {
|
|
1227
|
+
"
|
|
1228
|
+
class Foo
|
|
1229
|
+
class Array; end
|
|
1230
|
+
end
|
|
1231
|
+
"
|
|
1232
|
+
});
|
|
1233
|
+
context.index_uri("file:///foo2.rb", {
|
|
1234
|
+
"
|
|
1235
|
+
class Foo
|
|
1236
|
+
def Array; end
|
|
1237
|
+
end
|
|
1238
|
+
"
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
context.resolve();
|
|
1242
|
+
// Removing the method should not remove the constant
|
|
1243
|
+
context.index_uri("file:///foo2.rb", "");
|
|
1244
|
+
|
|
1245
|
+
let foo = context
|
|
1246
|
+
.graph()
|
|
1247
|
+
.declarations()
|
|
1248
|
+
.get(&DeclarationId::from("Foo"))
|
|
1249
|
+
.unwrap()
|
|
1250
|
+
.as_namespace()
|
|
1251
|
+
.unwrap();
|
|
1252
|
+
|
|
1253
|
+
assert!(foo.member(&StringId::from("Array")).is_some());
|
|
1254
|
+
assert!(foo.member(&StringId::from("Array()")).is_none());
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
#[test]
|
|
1258
|
+
fn removing_constant_with_conflicting_method_name() {
|
|
1259
|
+
let mut context = GraphTest::new();
|
|
1260
|
+
context.index_uri("file:///foo.rb", {
|
|
1261
|
+
"
|
|
1262
|
+
class Foo
|
|
1263
|
+
class Array; end
|
|
1264
|
+
end
|
|
1265
|
+
"
|
|
1266
|
+
});
|
|
1267
|
+
context.index_uri("file:///foo2.rb", {
|
|
1268
|
+
"
|
|
1269
|
+
class Foo
|
|
1270
|
+
def Array; end
|
|
1271
|
+
end
|
|
1272
|
+
"
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
context.resolve();
|
|
1276
|
+
// Removing the method should not remove the constant
|
|
1277
|
+
context.index_uri("file:///foo.rb", "");
|
|
1278
|
+
|
|
1279
|
+
let foo = context
|
|
1280
|
+
.graph()
|
|
1281
|
+
.declarations()
|
|
1282
|
+
.get(&DeclarationId::from("Foo"))
|
|
1283
|
+
.unwrap()
|
|
1284
|
+
.as_namespace()
|
|
1285
|
+
.unwrap();
|
|
1286
|
+
assert!(foo.member(&StringId::from("Array()")).is_some());
|
|
1287
|
+
assert!(foo.member(&StringId::from("Array")).is_none());
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
#[test]
|
|
1291
|
+
fn deleting_class_also_deletes_singleton_class() {
|
|
1292
|
+
let mut context = GraphTest::new();
|
|
1293
|
+
|
|
1294
|
+
context.index_uri("file:///foo.rb", {
|
|
1295
|
+
r"
|
|
1296
|
+
class Foo
|
|
1297
|
+
def self.hello; end
|
|
1298
|
+
end
|
|
1299
|
+
"
|
|
1300
|
+
});
|
|
1301
|
+
context.resolve();
|
|
1302
|
+
|
|
1303
|
+
assert!(context.graph().get("Foo").is_some());
|
|
1304
|
+
assert!(context.graph().get("Foo::<Foo>").is_some());
|
|
1305
|
+
|
|
1306
|
+
context.delete_uri("file:///foo.rb");
|
|
1307
|
+
|
|
1308
|
+
assert!(context.graph().get("Foo").is_none());
|
|
1309
|
+
assert!(context.graph().get("Foo::<Foo>").is_none());
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
#[test]
|
|
1313
|
+
fn deleting_module_also_deletes_singleton_class() {
|
|
1314
|
+
let mut context = GraphTest::new();
|
|
1315
|
+
|
|
1316
|
+
context.index_uri("file:///bar.rb", {
|
|
1317
|
+
r"
|
|
1318
|
+
module Bar
|
|
1319
|
+
def self.greet; end
|
|
1320
|
+
end
|
|
1321
|
+
"
|
|
1322
|
+
});
|
|
1323
|
+
context.resolve();
|
|
1324
|
+
|
|
1325
|
+
assert!(context.graph().get("Bar").is_some());
|
|
1326
|
+
assert!(context.graph().get("Bar::<Bar>").is_some());
|
|
1327
|
+
|
|
1328
|
+
context.delete_uri("file:///bar.rb");
|
|
1329
|
+
|
|
1330
|
+
assert!(context.graph().get("Bar").is_none());
|
|
1331
|
+
assert!(context.graph().get("Bar::<Bar>").is_none());
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
#[test]
|
|
1335
|
+
fn deleting_nested_class_also_deletes_singleton_class() {
|
|
1336
|
+
let mut context = GraphTest::new();
|
|
1337
|
+
|
|
1338
|
+
context.index_uri(
|
|
1339
|
+
"file:///nested.rb",
|
|
1340
|
+
r"
|
|
1341
|
+
class Outer
|
|
1342
|
+
class Inner
|
|
1343
|
+
def self.method; end
|
|
1344
|
+
end
|
|
1345
|
+
end
|
|
1346
|
+
",
|
|
1347
|
+
);
|
|
1348
|
+
context.resolve();
|
|
1349
|
+
|
|
1350
|
+
assert!(context.graph().get("Outer").is_some());
|
|
1351
|
+
assert!(context.graph().get("Outer::Inner").is_some());
|
|
1352
|
+
assert!(context.graph().get("Outer::Inner::<Inner>").is_some());
|
|
1353
|
+
|
|
1354
|
+
context.delete_uri("file:///nested.rb");
|
|
1355
|
+
|
|
1356
|
+
assert!(context.graph().get("Outer").is_none());
|
|
1357
|
+
assert!(context.graph().get("Outer::Inner").is_none());
|
|
1358
|
+
assert!(context.graph().get("Outer::Inner::<Inner>").is_none());
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
#[test]
|
|
1362
|
+
fn deleting_singleton_class_also_deletes_its_singleton_class() {
|
|
1363
|
+
let mut context = GraphTest::new();
|
|
1364
|
+
|
|
1365
|
+
context.index_uri(
|
|
1366
|
+
"file:///foo.rb",
|
|
1367
|
+
r"
|
|
1368
|
+
class Foo
|
|
1369
|
+
class << self
|
|
1370
|
+
def self.hello; end
|
|
1371
|
+
end
|
|
1372
|
+
end
|
|
1373
|
+
",
|
|
1374
|
+
);
|
|
1375
|
+
context.resolve();
|
|
1376
|
+
|
|
1377
|
+
assert!(context.graph().get("Foo").is_some());
|
|
1378
|
+
assert!(context.graph().get("Foo::<Foo>").is_some());
|
|
1379
|
+
assert!(context.graph().get("Foo::<Foo>::<<Foo>>").is_some());
|
|
1380
|
+
|
|
1381
|
+
context.delete_uri("file:///foo.rb");
|
|
1382
|
+
|
|
1383
|
+
assert!(context.graph().get("Foo").is_none());
|
|
1384
|
+
assert!(context.graph().get("Foo::<Foo>").is_none());
|
|
1385
|
+
assert!(context.graph().get("Foo::<Foo>::<<Foo>>").is_none());
|
|
1386
|
+
}
|
|
1387
|
+
}
|