rubydex 0.2.6 → 0.2.8
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/THIRD_PARTY_LICENSES.html +39 -6
- data/exe/rdx +2 -0
- data/ext/rubydex/declaration.c +115 -106
- data/ext/rubydex/definition.c +100 -80
- data/ext/rubydex/diagnostic.c +5 -0
- data/ext/rubydex/document.c +23 -31
- data/ext/rubydex/extconf.rb +1 -1
- data/ext/rubydex/graph.c +257 -70
- data/ext/rubydex/handle.h +13 -0
- data/ext/rubydex/location.c +5 -0
- data/ext/rubydex/reference.c +51 -46
- data/ext/rubydex/rubydex.c +5 -0
- data/ext/rubydex/signature.c +5 -0
- data/ext/rubydex/utils.c +13 -0
- data/ext/rubydex/utils.h +4 -0
- data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
- data/lib/rubydex/errors.rb +3 -0
- data/lib/rubydex/graph.rb +9 -28
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +15 -7
- data/rust/Cargo.lock +119 -14
- data/rust/rubydex/Cargo.toml +19 -1
- data/rust/rubydex/benches/graph_memory.rs +46 -0
- data/rust/rubydex/src/config.rs +275 -0
- data/rust/rubydex/src/errors.rs +2 -0
- data/rust/rubydex/src/lib.rs +7 -0
- data/rust/rubydex/src/main.rs +148 -5
- data/rust/rubydex/src/model/declaration.rs +16 -55
- data/rust/rubydex/src/model/graph.rs +123 -17
- data/rust/rubydex/src/model/ids.rs +30 -0
- data/rust/rubydex/src/model/string_ref.rs +12 -4
- data/rust/rubydex/src/query.rs +83 -15
- data/rust/rubydex/src/resolution.rs +198 -151
- data/rust/rubydex/tests/cli.rs +66 -0
- data/rust/rubydex-mcp/src/server.rs +1 -1
- data/rust/rubydex-sys/src/declaration_api.rs +2 -2
- data/rust/rubydex-sys/src/graph_api.rs +127 -46
- metadata +4 -2
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
use std::collections::HashSet;
|
|
2
2
|
use std::collections::hash_map::Entry;
|
|
3
|
-
use std::path::PathBuf;
|
|
3
|
+
use std::path::{Path, PathBuf};
|
|
4
4
|
|
|
5
5
|
use crate::assert_mem_size;
|
|
6
|
+
use crate::config::Config;
|
|
6
7
|
use crate::diagnostic::Diagnostic;
|
|
8
|
+
use crate::errors::Errors;
|
|
7
9
|
use crate::indexing::local_graph::LocalGraph;
|
|
8
10
|
use crate::model::built_in::{OBJECT_ID, add_built_in_data};
|
|
9
11
|
use crate::model::declaration::{Ancestor, Declaration, Namespace};
|
|
@@ -11,7 +13,10 @@ use crate::model::definitions::{Definition, MethodVisibilityDefinition, Receiver
|
|
|
11
13
|
use crate::model::document::Document;
|
|
12
14
|
use crate::model::encoding::Encoding;
|
|
13
15
|
use crate::model::identity_maps::{IdentityHashMap, IdentityHashSet};
|
|
14
|
-
use crate::model::ids::{
|
|
16
|
+
use crate::model::ids::{
|
|
17
|
+
ConstantReferenceId, DeclarationId, DefinitionId, MethodReferenceId, NameId, StringId, UriId,
|
|
18
|
+
declaration_id_from_lookup_name,
|
|
19
|
+
};
|
|
15
20
|
use crate::model::name::{Name, NameRef, ParentScope, ResolvedName};
|
|
16
21
|
use crate::model::references::{ConstantReference, MethodRef};
|
|
17
22
|
use crate::model::string_ref::StringRef;
|
|
@@ -85,10 +90,10 @@ pub struct Graph {
|
|
|
85
90
|
/// Drained by `take_pending_work()` before resolution.
|
|
86
91
|
pending_work: Vec<Unit>,
|
|
87
92
|
|
|
88
|
-
///
|
|
89
|
-
|
|
93
|
+
/// Project configuration
|
|
94
|
+
config: Config,
|
|
90
95
|
}
|
|
91
|
-
assert_mem_size!(Graph,
|
|
96
|
+
assert_mem_size!(Graph, 352);
|
|
92
97
|
|
|
93
98
|
impl Graph {
|
|
94
99
|
#[must_use]
|
|
@@ -104,7 +109,7 @@ impl Graph {
|
|
|
104
109
|
position_encoding: Encoding::default(),
|
|
105
110
|
name_dependents: IdentityHashMap::default(),
|
|
106
111
|
pending_work: Vec::default(),
|
|
107
|
-
|
|
112
|
+
config: Config::new(),
|
|
108
113
|
};
|
|
109
114
|
|
|
110
115
|
add_built_in_data(&mut graph);
|
|
@@ -126,13 +131,40 @@ impl Graph {
|
|
|
126
131
|
/// Adds paths to exclude from file discovery during indexing. Excluded directories will be skipped entirely during
|
|
127
132
|
/// directory traversal.
|
|
128
133
|
pub fn exclude_paths(&mut self, paths: Vec<PathBuf>) {
|
|
129
|
-
self.
|
|
134
|
+
self.config.exclude_paths(paths);
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
/// Returns the set of paths excluded from file discovery.
|
|
133
138
|
#[must_use]
|
|
134
|
-
pub fn excluded_paths(&self) ->
|
|
135
|
-
|
|
139
|
+
pub fn excluded_paths(&self) -> HashSet<PathBuf> {
|
|
140
|
+
self.config.excluded_paths()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Returns the root directory of the workspace being indexed.
|
|
144
|
+
#[must_use]
|
|
145
|
+
pub fn workspace_path(&self) -> &Path {
|
|
146
|
+
self.config.workspace_path()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Sets the root directory of the workspace being indexed.
|
|
150
|
+
pub fn set_workspace_path(&mut self, workspace_path: PathBuf) {
|
|
151
|
+
self.config.set_workspace_path(workspace_path);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Loads a configuration file. Pass `None` to load the default `rubydex.toml` configuration file if it exists
|
|
155
|
+
///
|
|
156
|
+
/// # Errors
|
|
157
|
+
///
|
|
158
|
+
/// Returns an [`Errors::ConfigNotFound`] if an explicitly requested file does not exist or an
|
|
159
|
+
/// [`Errors::ConfigError`] if a file cannot otherwise be read or its contents are malformed.
|
|
160
|
+
pub fn load_config(&mut self, config_path: Option<&Path>) -> Result<(), Errors> {
|
|
161
|
+
match config_path {
|
|
162
|
+
Some(path) => {
|
|
163
|
+
let path = self.config.workspace_path().join(path);
|
|
164
|
+
self.config.load_file(&path)
|
|
165
|
+
}
|
|
166
|
+
None => self.config.load_default(),
|
|
167
|
+
}
|
|
136
168
|
}
|
|
137
169
|
|
|
138
170
|
/// # Panics
|
|
@@ -486,10 +518,7 @@ impl Graph {
|
|
|
486
518
|
|
|
487
519
|
#[must_use]
|
|
488
520
|
pub fn all_diagnostics(&self) -> Vec<&Diagnostic> {
|
|
489
|
-
|
|
490
|
-
let declaration_diagnostics = self.declarations.values().flat_map(Declaration::diagnostics);
|
|
491
|
-
|
|
492
|
-
document_diagnostics.chain(declaration_diagnostics).collect()
|
|
521
|
+
self.documents.values().flat_map(Document::diagnostics).collect()
|
|
493
522
|
}
|
|
494
523
|
|
|
495
524
|
/// Interns a string in the graph unless already interned. This method is only used to back the
|
|
@@ -552,7 +581,7 @@ impl Graph {
|
|
|
552
581
|
|
|
553
582
|
#[must_use]
|
|
554
583
|
pub fn get(&self, name: &str) -> Option<Vec<&Definition>> {
|
|
555
|
-
let declaration_id =
|
|
584
|
+
let declaration_id = declaration_id_from_lookup_name(name);
|
|
556
585
|
let declaration = self.declarations.get(&declaration_id)?;
|
|
557
586
|
|
|
558
587
|
Some(
|
|
@@ -1201,9 +1230,6 @@ impl Graph {
|
|
|
1201
1230
|
for def_id in detach_def_ids {
|
|
1202
1231
|
decl.remove_definition(def_id);
|
|
1203
1232
|
}
|
|
1204
|
-
if !detach_def_ids.is_empty() {
|
|
1205
|
-
decl.clear_diagnostics();
|
|
1206
|
-
}
|
|
1207
1233
|
}
|
|
1208
1234
|
|
|
1209
1235
|
let Some(decl) = self.declarations.get(&decl_id) else {
|
|
@@ -1247,6 +1273,10 @@ impl Graph {
|
|
|
1247
1273
|
let unqualified_str_id = StringId::from(&decl.unqualified_name());
|
|
1248
1274
|
let owner_id = *decl.owner_id();
|
|
1249
1275
|
let is_singleton_class = matches!(decl, Declaration::Namespace(Namespace::SingletonClass(_)));
|
|
1276
|
+
let ancestors_to_detach: Vec<Ancestor> = decl
|
|
1277
|
+
.as_namespace()
|
|
1278
|
+
.map(|ns| ns.ancestors().iter().copied().collect())
|
|
1279
|
+
.unwrap_or_default();
|
|
1250
1280
|
|
|
1251
1281
|
for def_id in def_ids {
|
|
1252
1282
|
self.push_work(Unit::Definition(def_id));
|
|
@@ -1261,6 +1291,16 @@ impl Graph {
|
|
|
1261
1291
|
ns.remove_member(&unqualified_str_id);
|
|
1262
1292
|
}
|
|
1263
1293
|
}
|
|
1294
|
+
|
|
1295
|
+
// Detach from each complete ancestor's descendant set so we don't leave a stale id in descendants
|
|
1296
|
+
for ancestor in ancestors_to_detach {
|
|
1297
|
+
if let Ancestor::Complete(ancestor_id) = ancestor
|
|
1298
|
+
&& let Some(anc_decl) = self.declarations.get_mut(&ancestor_id)
|
|
1299
|
+
&& let Some(ns) = anc_decl.as_namespace_mut()
|
|
1300
|
+
{
|
|
1301
|
+
ns.remove_descendant(&decl_id);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1264
1304
|
}
|
|
1265
1305
|
|
|
1266
1306
|
self.declarations.remove(&decl_id);
|
|
@@ -1547,6 +1587,10 @@ mod tests {
|
|
|
1547
1587
|
assert_members_eq, assert_no_diagnostics, assert_no_members,
|
|
1548
1588
|
};
|
|
1549
1589
|
|
|
1590
|
+
fn definition_ids(definitions: Vec<&Definition>) -> Vec<DefinitionId> {
|
|
1591
|
+
definitions.into_iter().map(Definition::id).collect()
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1550
1594
|
#[test]
|
|
1551
1595
|
fn deleting_a_uri() {
|
|
1552
1596
|
let mut context = GraphTest::new();
|
|
@@ -1969,6 +2013,34 @@ mod tests {
|
|
|
1969
2013
|
assert_eq!(definitions[0].offset().start(), 6);
|
|
1970
2014
|
}
|
|
1971
2015
|
|
|
2016
|
+
#[test]
|
|
2017
|
+
fn get_accepts_leading_double_colon() {
|
|
2018
|
+
let mut context = GraphTest::new();
|
|
2019
|
+
|
|
2020
|
+
context.index_uri("file:///foo.rb", "module Foo; module Bar; end; end");
|
|
2021
|
+
context.resolve();
|
|
2022
|
+
|
|
2023
|
+
// Built-in declarations (root-scoped):
|
|
2024
|
+
let unqualified = context.graph().get("Object").expect("unqualified `Object` lookup");
|
|
2025
|
+
let qualified = context.graph().get("::Object").expect("qualified `::Object` lookup");
|
|
2026
|
+
assert_eq!(definition_ids(unqualified), definition_ids(qualified));
|
|
2027
|
+
|
|
2028
|
+
// Indexed declarations, top-level and nested:
|
|
2029
|
+
let unqualified = context.graph().get("Foo").expect("unqualified `Foo` lookup");
|
|
2030
|
+
let qualified = context.graph().get("::Foo").expect("qualified `::Foo` lookup");
|
|
2031
|
+
assert_eq!(definition_ids(unqualified), definition_ids(qualified));
|
|
2032
|
+
|
|
2033
|
+
let unqualified = context.graph().get("Foo::Bar").expect("unqualified `Foo::Bar` lookup");
|
|
2034
|
+
let qualified = context
|
|
2035
|
+
.graph()
|
|
2036
|
+
.get("::Foo::Bar")
|
|
2037
|
+
.expect("qualified `::Foo::Bar` lookup");
|
|
2038
|
+
assert_eq!(definition_ids(unqualified), definition_ids(qualified));
|
|
2039
|
+
|
|
2040
|
+
// Unknown names still return None when prefixed:
|
|
2041
|
+
assert!(context.graph().get("::DoesNotExist").is_none());
|
|
2042
|
+
}
|
|
2043
|
+
|
|
1972
2044
|
#[test]
|
|
1973
2045
|
fn adding_another_definition_from_a_different_uri() {
|
|
1974
2046
|
let mut context = GraphTest::new();
|
|
@@ -2476,6 +2548,7 @@ mod tests {
|
|
|
2476
2548
|
|
|
2477
2549
|
#[cfg(test)]
|
|
2478
2550
|
mod incremental_resolution_tests {
|
|
2551
|
+
use crate::model::ids::DeclarationId;
|
|
2479
2552
|
use crate::model::name::NameRef;
|
|
2480
2553
|
use crate::test_utils::GraphTest;
|
|
2481
2554
|
use crate::{
|
|
@@ -4103,4 +4176,37 @@ mod incremental_resolution_tests {
|
|
|
4103
4176
|
assert_declaration_exists!(context, "Foo::<Foo>");
|
|
4104
4177
|
assert_declaration_exists!(context, "Bar::<Bar>");
|
|
4105
4178
|
}
|
|
4179
|
+
|
|
4180
|
+
#[test]
|
|
4181
|
+
fn constant_references_through_object_inheritance_are_invalidated() {
|
|
4182
|
+
let mut context = GraphTest::new();
|
|
4183
|
+
context.index_uri("file:///a.rb", "class Foo; end");
|
|
4184
|
+
context.index_uri(
|
|
4185
|
+
"file:///b.rb",
|
|
4186
|
+
r"
|
|
4187
|
+
module Wrap
|
|
4188
|
+
class Foo; end
|
|
4189
|
+
::Foo
|
|
4190
|
+
Foo
|
|
4191
|
+
end
|
|
4192
|
+
",
|
|
4193
|
+
);
|
|
4194
|
+
context.resolve();
|
|
4195
|
+
|
|
4196
|
+
context.delete_uri("file:///a.rb");
|
|
4197
|
+
context.resolve();
|
|
4198
|
+
|
|
4199
|
+
let kernel = context
|
|
4200
|
+
.graph()
|
|
4201
|
+
.declarations()
|
|
4202
|
+
.get(&DeclarationId::from("Kernel"))
|
|
4203
|
+
.unwrap();
|
|
4204
|
+
|
|
4205
|
+
for id in kernel.as_namespace().unwrap().descendants() {
|
|
4206
|
+
assert!(
|
|
4207
|
+
context.graph().declarations().contains_key(id),
|
|
4208
|
+
"Kernel has stale descendant id {id:?} with no backing declaration"
|
|
4209
|
+
);
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4106
4212
|
} // mod incremental_resolution_tests
|
|
@@ -9,6 +9,15 @@ pub struct DeclarationMarker;
|
|
|
9
9
|
/// `DeclarationId` represents the ID of a fully qualified name. For example, `Foo::Bar` or `Foo#my_method`
|
|
10
10
|
pub type DeclarationId = Id<DeclarationMarker>;
|
|
11
11
|
|
|
12
|
+
/// Creates a `DeclarationId` from a user-facing lookup name.
|
|
13
|
+
///
|
|
14
|
+
/// Declaration names are stored without the root-scope marker, so `"::Object"`
|
|
15
|
+
/// and `"Object"` must resolve to the same declaration ID.
|
|
16
|
+
#[must_use]
|
|
17
|
+
pub fn declaration_id_from_lookup_name(name: &str) -> DeclarationId {
|
|
18
|
+
DeclarationId::from(name.strip_prefix("::").unwrap_or(name))
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
|
|
13
22
|
pub struct DefinitionMarker;
|
|
14
23
|
|
|
@@ -84,3 +93,24 @@ assert_mem_size!(ClassVariableReferenceId, 8);
|
|
|
84
93
|
pub struct InstanceVariableMarker;
|
|
85
94
|
pub type InstanceVariableReferenceId = Id<InstanceVariableMarker>;
|
|
86
95
|
assert_mem_size!(InstanceVariableReferenceId, 8);
|
|
96
|
+
|
|
97
|
+
#[cfg(test)]
|
|
98
|
+
mod tests {
|
|
99
|
+
use super::*;
|
|
100
|
+
|
|
101
|
+
#[test]
|
|
102
|
+
fn declaration_id_from_lookup_name_accepts_root_scope_marker() {
|
|
103
|
+
assert_eq!(
|
|
104
|
+
DeclarationId::from("Object"),
|
|
105
|
+
declaration_id_from_lookup_name("::Object")
|
|
106
|
+
);
|
|
107
|
+
assert_eq!(
|
|
108
|
+
DeclarationId::from("Foo::Bar"),
|
|
109
|
+
declaration_id_from_lookup_name("::Foo::Bar")
|
|
110
|
+
);
|
|
111
|
+
assert_ne!(
|
|
112
|
+
DeclarationId::from("Object"),
|
|
113
|
+
declaration_id_from_lookup_name("::::Object")
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -9,15 +9,23 @@ use std::ops::Deref;
|
|
|
9
9
|
/// graph when its count reaches zero.
|
|
10
10
|
#[derive(Debug)]
|
|
11
11
|
pub struct StringRef {
|
|
12
|
-
value:
|
|
12
|
+
value: Box<str>,
|
|
13
13
|
ref_count: u32,
|
|
14
14
|
}
|
|
15
|
-
assert_mem_size!(StringRef,
|
|
15
|
+
assert_mem_size!(StringRef, 24);
|
|
16
16
|
|
|
17
17
|
impl StringRef {
|
|
18
18
|
#[must_use]
|
|
19
19
|
pub fn new(value: String) -> Self {
|
|
20
|
-
Self {
|
|
20
|
+
Self {
|
|
21
|
+
value: value.into_boxed_str(),
|
|
22
|
+
ref_count: 1,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[must_use]
|
|
27
|
+
pub fn as_str(&self) -> &str {
|
|
28
|
+
&self.value
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
#[must_use]
|
|
@@ -44,7 +52,7 @@ impl StringRef {
|
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
impl Deref for StringRef {
|
|
47
|
-
type Target =
|
|
55
|
+
type Target = str;
|
|
48
56
|
|
|
49
57
|
fn deref(&self) -> &Self::Target {
|
|
50
58
|
&self.value
|
data/rust/rubydex/src/query.rs
CHANGED
|
@@ -27,12 +27,25 @@ pub enum MatchMode {
|
|
|
27
27
|
Exact,
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/// Searches all declarations in parallel based on fully qualified names. Accepts multiple queries in case the caller
|
|
31
|
+
/// wants to find multiple patterns without having to re-traverse the graph and also accepts match mode.
|
|
32
|
+
///
|
|
33
|
+
/// Note: an empty query returns all declarations, so if any are included the rest of the queries will be ignored.
|
|
34
|
+
///
|
|
30
35
|
/// # Panics
|
|
31
36
|
///
|
|
32
37
|
/// Will panic if any of the threads panic
|
|
33
|
-
pub fn declaration_search(graph: &Graph,
|
|
38
|
+
pub fn declaration_search(graph: &Graph, queries: &[&str], match_mode: &MatchMode) -> Vec<DeclarationId> {
|
|
34
39
|
let num_threads = thread::available_parallelism().map_or(4, std::num::NonZero::get);
|
|
35
40
|
let declarations = graph.declarations();
|
|
41
|
+
|
|
42
|
+
// An empty query matches all declarations as per the LSP specification and is equivalent to fetching all of them
|
|
43
|
+
// directly. Since an empty query matches all, there's no point in checking the other queries or pay the price of
|
|
44
|
+
// spawning threads.
|
|
45
|
+
if queries.iter().any(|q| q.is_empty()) {
|
|
46
|
+
return declarations.keys().copied().collect();
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
let ids: Vec<DeclarationId> = declarations.keys().copied().collect();
|
|
37
50
|
let chunk_size = ids.len().div_ceil(num_threads);
|
|
38
51
|
|
|
@@ -48,16 +61,8 @@ pub fn declaration_search(graph: &Graph, query: &str, match_mode: &MatchMode) ->
|
|
|
48
61
|
chunk
|
|
49
62
|
.iter()
|
|
50
63
|
.filter(|id| {
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
match match_mode {
|
|
54
|
-
MatchMode::Fuzzy => {
|
|
55
|
-
// When the query is empty, we return everything as per the LSP specification.
|
|
56
|
-
// Otherwise, we compute the match score and return anything with a score greater than zero
|
|
57
|
-
query.is_empty() || match_score(query, name) > 0
|
|
58
|
-
}
|
|
59
|
-
MatchMode::Exact => name.contains(query),
|
|
60
|
-
}
|
|
64
|
+
let name = declarations.get(id).unwrap().name();
|
|
65
|
+
queries.iter().any(|query| matches_query(query, name, match_mode))
|
|
61
66
|
})
|
|
62
67
|
.copied()
|
|
63
68
|
.collect::<Vec<_>>()
|
|
@@ -69,6 +74,15 @@ pub fn declaration_search(graph: &Graph, query: &str, match_mode: &MatchMode) ->
|
|
|
69
74
|
})
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
/// Returns whether a single `query` matches `name` under the given [`MatchMode`].
|
|
78
|
+
#[must_use]
|
|
79
|
+
fn matches_query(query: &str, name: &str, match_mode: &MatchMode) -> bool {
|
|
80
|
+
match match_mode {
|
|
81
|
+
MatchMode::Fuzzy => match_score(query, name) > 0,
|
|
82
|
+
MatchMode::Exact => name.contains(query),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
#[must_use]
|
|
73
87
|
fn match_score(query: &str, target: &str) -> usize {
|
|
74
88
|
let mut query_chars = query.chars().peekable();
|
|
@@ -836,7 +850,7 @@ mod tests {
|
|
|
836
850
|
assert_results_eq!($context, $query, &MatchMode::default(), $expected);
|
|
837
851
|
};
|
|
838
852
|
($context:expr, $query:expr, $match_mode:expr, $expected:expr) => {
|
|
839
|
-
let actual = declaration_search(&$context.graph(), $query, $match_mode);
|
|
853
|
+
let actual = declaration_search(&$context.graph(), &[$query], $match_mode);
|
|
840
854
|
assert_eq!(
|
|
841
855
|
actual,
|
|
842
856
|
$expected
|
|
@@ -945,8 +959,8 @@ mod tests {
|
|
|
945
959
|
"
|
|
946
960
|
});
|
|
947
961
|
context.resolve();
|
|
948
|
-
let exact_results = declaration_search(context.graph(), "", &MatchMode::Exact);
|
|
949
|
-
let fuzzy_results = declaration_search(context.graph(), "", &MatchMode::Fuzzy);
|
|
962
|
+
let exact_results = declaration_search(context.graph(), &[""], &MatchMode::Exact);
|
|
963
|
+
let fuzzy_results = declaration_search(context.graph(), &[""], &MatchMode::Fuzzy);
|
|
950
964
|
|
|
951
965
|
assert_eq!(exact_results.len(), fuzzy_results.len());
|
|
952
966
|
assert_eq!(context.graph().declarations().len(), exact_results.len());
|
|
@@ -972,6 +986,51 @@ mod tests {
|
|
|
972
986
|
assert_results_eq!(context, "#Is_A?()", ["Foo#is_a_foo?()", "Bar#is_a?()"]);
|
|
973
987
|
}
|
|
974
988
|
|
|
989
|
+
#[test]
|
|
990
|
+
fn multiple_queries_return_union_of_matches() {
|
|
991
|
+
let mut context = GraphTest::new();
|
|
992
|
+
context.index_uri("file:///foo.rb", {
|
|
993
|
+
r"
|
|
994
|
+
class Foo
|
|
995
|
+
def foo_method; end
|
|
996
|
+
def bar_method; end
|
|
997
|
+
def other_method; end
|
|
998
|
+
end
|
|
999
|
+
"
|
|
1000
|
+
});
|
|
1001
|
+
context.resolve();
|
|
1002
|
+
|
|
1003
|
+
let results = declaration_search(context.graph(), &["#foo_method()", "#bar_method()"], &MatchMode::Exact);
|
|
1004
|
+
let mut names: Vec<String> = results
|
|
1005
|
+
.iter()
|
|
1006
|
+
.map(|id| context.graph().declarations().get(id).unwrap().name().to_string())
|
|
1007
|
+
.collect();
|
|
1008
|
+
names.sort();
|
|
1009
|
+
|
|
1010
|
+
assert_eq!(names, ["Foo#bar_method()", "Foo#foo_method()"]);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
#[test]
|
|
1014
|
+
fn overlapping_queries_do_not_duplicate_results() {
|
|
1015
|
+
let mut context = GraphTest::new();
|
|
1016
|
+
context.index_uri("file:///foo.rb", {
|
|
1017
|
+
r"
|
|
1018
|
+
class Foo
|
|
1019
|
+
def is_a_foo?; end
|
|
1020
|
+
end
|
|
1021
|
+
"
|
|
1022
|
+
});
|
|
1023
|
+
context.resolve();
|
|
1024
|
+
|
|
1025
|
+
let results = declaration_search(context.graph(), &["is_a", "foo?"], &MatchMode::Exact);
|
|
1026
|
+
let matches = results
|
|
1027
|
+
.iter()
|
|
1028
|
+
.filter(|id| context.graph().declarations().get(id).unwrap().name() == "Foo#is_a_foo?()")
|
|
1029
|
+
.count();
|
|
1030
|
+
|
|
1031
|
+
assert_eq!(matches, 1);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
975
1034
|
fn test_root() -> PathBuf {
|
|
976
1035
|
let root = if cfg!(windows) { "C:\\" } else { "/" };
|
|
977
1036
|
PathBuf::from_str(root).unwrap()
|
|
@@ -2767,7 +2826,16 @@ mod tests {
|
|
|
2767
2826
|
self_decl_id: None,
|
|
2768
2827
|
nesting_name_id,
|
|
2769
2828
|
},
|
|
2770
|
-
[
|
|
2829
|
+
[
|
|
2830
|
+
"Bar",
|
|
2831
|
+
"Bar#@@bar_cvar",
|
|
2832
|
+
"BasicObject",
|
|
2833
|
+
"Class",
|
|
2834
|
+
"Foo",
|
|
2835
|
+
"Kernel",
|
|
2836
|
+
"Module",
|
|
2837
|
+
"Object"
|
|
2838
|
+
]
|
|
2771
2839
|
);
|
|
2772
2840
|
}
|
|
2773
2841
|
|