rubydex 0.2.7 → 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/ext/rubydex/graph.c +28 -14
- data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +4 -4
- data/rust/rubydex/src/main.rs +148 -5
- data/rust/rubydex/src/model/declaration.rs +14 -14
- data/rust/rubydex/src/model/graph.rs +85 -2
- 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 +196 -139
- 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 +54 -43
- metadata +2 -2
data/rust/rubydex/tests/cli.rs
CHANGED
|
@@ -19,6 +19,9 @@ fn prints_help() {
|
|
|
19
19
|
.success()
|
|
20
20
|
.stdout(predicate::str::contains("A Static Analysis Toolkit for Ruby"))
|
|
21
21
|
.stdout(predicate::str::contains("Usage:"))
|
|
22
|
+
.stdout(predicate::str::contains(
|
|
23
|
+
"If the first path is a directory, it is used as the workspace root for rubydex.toml",
|
|
24
|
+
))
|
|
22
25
|
.stdout(predicate::str::contains("--stats"))
|
|
23
26
|
.stdout(predicate::str::contains("--dot"))
|
|
24
27
|
.stdout(predicate::str::contains("--stop-after"));
|
|
@@ -52,6 +55,69 @@ fn paths_argument_variants() {
|
|
|
52
55
|
});
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
#[test]
|
|
59
|
+
fn single_directory_argument_is_workspace_root_for_config() {
|
|
60
|
+
with_context(|context| {
|
|
61
|
+
context.write("included.rb", "class Included\nend\n");
|
|
62
|
+
context.write("excluded/skipped.rb", "class Skipped\nend\n");
|
|
63
|
+
context.write("rubydex.toml", "exclude = [\"excluded\"]\n");
|
|
64
|
+
|
|
65
|
+
rdx(&[context.absolute_path().to_str().unwrap()])
|
|
66
|
+
.success()
|
|
67
|
+
.stderr(predicate::str::is_empty())
|
|
68
|
+
.stdout(predicate::str::contains("Indexed 2 files"));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[test]
|
|
73
|
+
fn first_directory_argument_is_workspace_root_for_config() {
|
|
74
|
+
with_context(|context| {
|
|
75
|
+
context.write("included/kept.rb", "class Included\nend\n");
|
|
76
|
+
context.write("excluded/skipped.rb", "class Skipped\nend\n");
|
|
77
|
+
context.write("rubydex.toml", "exclude = [\"excluded\"]\n");
|
|
78
|
+
|
|
79
|
+
rdx(&[
|
|
80
|
+
context.absolute_path().to_str().unwrap(),
|
|
81
|
+
context.absolute_path_to("included").to_str().unwrap(),
|
|
82
|
+
context.absolute_path_to("excluded").to_str().unwrap(),
|
|
83
|
+
])
|
|
84
|
+
.success()
|
|
85
|
+
.stderr(predicate::str::is_empty())
|
|
86
|
+
.stdout(predicate::str::contains("Indexed 2 files"));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[test]
|
|
91
|
+
fn single_file_argument_is_not_used_as_workspace_root() {
|
|
92
|
+
with_context(|context| {
|
|
93
|
+
context.write("included.rb", "class Included\nend\n");
|
|
94
|
+
context.write("excluded/skipped.rb", "class Skipped\nend\n");
|
|
95
|
+
context.write("rubydex.toml", "exclude = [\"excluded\"]\n");
|
|
96
|
+
|
|
97
|
+
rdx(&[context.absolute_path_to("excluded/skipped.rb").to_str().unwrap()])
|
|
98
|
+
.success()
|
|
99
|
+
.stderr(predicate::str::is_empty())
|
|
100
|
+
.stdout(predicate::str::contains("Indexed 2 files"));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[test]
|
|
105
|
+
fn first_file_argument_is_not_used_as_workspace_root() {
|
|
106
|
+
with_context(|context| {
|
|
107
|
+
context.write("included.rb", "class Included\nend\n");
|
|
108
|
+
context.write("excluded/skipped.rb", "class Skipped\nend\n");
|
|
109
|
+
context.write("rubydex.toml", "exclude = [\"excluded\"]\n");
|
|
110
|
+
|
|
111
|
+
rdx(&[
|
|
112
|
+
context.absolute_path_to("included.rb").to_str().unwrap(),
|
|
113
|
+
context.absolute_path_to("excluded").to_str().unwrap(),
|
|
114
|
+
])
|
|
115
|
+
.success()
|
|
116
|
+
.stderr(predicate::str::is_empty())
|
|
117
|
+
.stdout(predicate::str::contains("Indexed 3 files"));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
55
121
|
#[test]
|
|
56
122
|
fn prints_index_metrics() {
|
|
57
123
|
with_context(|context| {
|
|
@@ -241,7 +241,7 @@ impl RubydexServer {
|
|
|
241
241
|
.to_string();
|
|
242
242
|
}
|
|
243
243
|
};
|
|
244
|
-
let ids = rubydex::query::declaration_search(graph, ¶ms.query, &match_mode);
|
|
244
|
+
let ids = rubydex::query::declaration_search(graph, &[params.query.as_str()], &match_mode);
|
|
245
245
|
|
|
246
246
|
let limit = params.limit.filter(|&l| l > 0).unwrap_or(50).min(100); // default 50, max 100
|
|
247
247
|
let offset = params.offset.unwrap_or(0);
|
|
@@ -9,7 +9,7 @@ use crate::definition_api::{DefinitionsIter, rdx_definitions_iter_new_from_ids};
|
|
|
9
9
|
use crate::graph_api::{GraphPointer, with_graph};
|
|
10
10
|
use crate::reference_api::{CConstantReference, CMethodReference, ConstantReferencesIter, MethodReferencesIter};
|
|
11
11
|
use crate::utils;
|
|
12
|
-
use rubydex::model::ids::{DeclarationId, StringId};
|
|
12
|
+
use rubydex::model::ids::{DeclarationId, StringId, declaration_id_from_lookup_name};
|
|
13
13
|
|
|
14
14
|
#[repr(C)]
|
|
15
15
|
#[derive(Debug, Clone, Copy)]
|
|
@@ -80,7 +80,7 @@ pub(crate) unsafe fn decl_id_from_char_ptr(ptr: *const c_char) -> Option<Declara
|
|
|
80
80
|
if s.is_empty() {
|
|
81
81
|
return None;
|
|
82
82
|
}
|
|
83
|
-
Some(
|
|
83
|
+
Some(declaration_id_from_lookup_name(&s))
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/// An iterator over declaration IDs
|
|
@@ -11,7 +11,7 @@ use rubydex::errors::Errors;
|
|
|
11
11
|
use rubydex::indexing::LanguageId;
|
|
12
12
|
use rubydex::model::encoding::Encoding;
|
|
13
13
|
use rubydex::model::graph::Graph;
|
|
14
|
-
use rubydex::model::ids::{DeclarationId, NameId, UriId};
|
|
14
|
+
use rubydex::model::ids::{DeclarationId, NameId, UriId, declaration_id_from_lookup_name};
|
|
15
15
|
use rubydex::model::keywords;
|
|
16
16
|
use rubydex::model::name::NameRef;
|
|
17
17
|
use rubydex::model::visibility::Visibility;
|
|
@@ -58,64 +58,75 @@ where
|
|
|
58
58
|
result
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/// Searches the graph using exact substring matching
|
|
61
|
+
/// Searches the graph using exact substring matching, returning every declaration whose name matches any of the
|
|
62
|
+
/// queries.
|
|
62
63
|
///
|
|
63
64
|
/// # Safety
|
|
64
65
|
///
|
|
65
|
-
/// Expects
|
|
66
|
+
/// Expects `pointer` to be a valid graph and `c_queries` to point to an array of `count` valid, NUL-terminated C
|
|
67
|
+
/// strings. Returns a null pointer when `count` is zero, `c_queries` is null, or any query is not valid UTF-8.
|
|
66
68
|
#[unsafe(no_mangle)]
|
|
67
69
|
pub unsafe extern "C" fn rdx_graph_declarations_search(
|
|
68
70
|
pointer: GraphPointer,
|
|
69
|
-
|
|
71
|
+
c_queries: *const *const c_char,
|
|
72
|
+
count: usize,
|
|
70
73
|
) -> *mut DeclarationsIter {
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
};
|
|
74
|
+
if count == 0 || c_queries.is_null() {
|
|
75
|
+
return ptr::null_mut();
|
|
76
|
+
}
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let decl = graph.declarations().get(&id)?;
|
|
81
|
-
Some(CDeclaration::from_declaration(id, decl))
|
|
82
|
-
})
|
|
83
|
-
.collect::<Vec<CDeclaration>>()
|
|
84
|
-
.into_boxed_slice()
|
|
85
|
-
});
|
|
78
|
+
let Ok(queries) = (unsafe { utils::convert_double_pointer_to_vec(c_queries, count) }) else {
|
|
79
|
+
return ptr::null_mut();
|
|
80
|
+
};
|
|
81
|
+
let query_refs: Vec<&str> = queries.iter().map(String::as_str).collect();
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
let entries = with_graph(pointer, |graph| {
|
|
84
|
+
query::declaration_search(graph, &query_refs, &query::MatchMode::Exact)
|
|
85
|
+
.into_iter()
|
|
86
|
+
.filter_map(|id| {
|
|
87
|
+
let decl = graph.declarations().get(&id)?;
|
|
88
|
+
Some(CDeclaration::from_declaration(id, decl))
|
|
89
|
+
})
|
|
90
|
+
.collect::<Vec<CDeclaration>>()
|
|
91
|
+
.into_boxed_slice()
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
DeclarationsIter::new(entries)
|
|
89
95
|
}
|
|
90
96
|
|
|
91
|
-
/// Searches the graph using fuzzy matching
|
|
97
|
+
/// Searches the graph using fuzzy matching, returning every declaration whose name matches any of the queries.
|
|
92
98
|
///
|
|
93
99
|
/// # Safety
|
|
94
100
|
///
|
|
95
|
-
/// Expects
|
|
101
|
+
/// Expects `pointer` to be a valid graph and `c_queries` to point to an array of `count` valid, NUL-terminated C
|
|
102
|
+
/// strings. Returns a null pointer when `count` is zero, `c_queries` is null, or any query is not valid UTF-8.
|
|
96
103
|
#[unsafe(no_mangle)]
|
|
97
104
|
pub unsafe extern "C" fn rdx_graph_declarations_fuzzy_search(
|
|
98
105
|
pointer: GraphPointer,
|
|
99
|
-
|
|
106
|
+
c_queries: *const *const c_char,
|
|
107
|
+
count: usize,
|
|
100
108
|
) -> *mut DeclarationsIter {
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
};
|
|
109
|
+
if count == 0 || c_queries.is_null() {
|
|
110
|
+
return ptr::null_mut();
|
|
111
|
+
}
|
|
105
112
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
let decl = graph.declarations().get(&id)?;
|
|
111
|
-
Some(CDeclaration::from_declaration(id, decl))
|
|
112
|
-
})
|
|
113
|
-
.collect::<Vec<CDeclaration>>()
|
|
114
|
-
.into_boxed_slice()
|
|
115
|
-
});
|
|
113
|
+
let Ok(queries) = (unsafe { utils::convert_double_pointer_to_vec(c_queries, count) }) else {
|
|
114
|
+
return ptr::null_mut();
|
|
115
|
+
};
|
|
116
|
+
let query_refs: Vec<&str> = queries.iter().map(String::as_str).collect();
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
let entries = with_graph(pointer, |graph| {
|
|
119
|
+
query::declaration_search(graph, &query_refs, &query::MatchMode::Fuzzy)
|
|
120
|
+
.into_iter()
|
|
121
|
+
.filter_map(|id| {
|
|
122
|
+
let decl = graph.declarations().get(&id)?;
|
|
123
|
+
Some(CDeclaration::from_declaration(id, decl))
|
|
124
|
+
})
|
|
125
|
+
.collect::<Vec<CDeclaration>>()
|
|
126
|
+
.into_boxed_slice()
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
DeclarationsIter::new(entries)
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
/// # Panics
|
|
@@ -498,7 +509,7 @@ pub unsafe extern "C" fn rdx_graph_get_declaration(pointer: GraphPointer, name:
|
|
|
498
509
|
};
|
|
499
510
|
|
|
500
511
|
with_graph(pointer, |graph| {
|
|
501
|
-
let decl_id =
|
|
512
|
+
let decl_id = declaration_id_from_lookup_name(&name_str);
|
|
502
513
|
|
|
503
514
|
if let Some(decl) = graph.declarations().get(&decl_id) {
|
|
504
515
|
Box::into_raw(Box::new(CDeclaration::from_declaration(decl_id, decl))).cast_const()
|
|
@@ -917,7 +928,7 @@ pub unsafe extern "C" fn rdx_graph_complete_namespace_access(
|
|
|
917
928
|
graph,
|
|
918
929
|
CompletionReceiver::NamespaceAccess {
|
|
919
930
|
self_decl_id,
|
|
920
|
-
namespace_decl_id:
|
|
931
|
+
namespace_decl_id: declaration_id_from_lookup_name(&name_str),
|
|
921
932
|
},
|
|
922
933
|
Vec::new(),
|
|
923
934
|
)
|
|
@@ -951,7 +962,7 @@ pub unsafe extern "C" fn rdx_graph_complete_method_call(
|
|
|
951
962
|
graph,
|
|
952
963
|
CompletionReceiver::MethodCall {
|
|
953
964
|
self_decl_id,
|
|
954
|
-
receiver_decl_id:
|
|
965
|
+
receiver_decl_id: declaration_id_from_lookup_name(&name_str),
|
|
955
966
|
},
|
|
956
967
|
Vec::new(),
|
|
957
968
|
)
|
|
@@ -995,7 +1006,7 @@ pub unsafe extern "C" fn rdx_graph_complete_method_argument(
|
|
|
995
1006
|
CompletionReceiver::MethodArgument {
|
|
996
1007
|
self_decl_id,
|
|
997
1008
|
nesting_name_id,
|
|
998
|
-
method_decl_id:
|
|
1009
|
+
method_decl_id: declaration_id_from_lookup_name(&name_str),
|
|
999
1010
|
},
|
|
1000
1011
|
names_to_untrack,
|
|
1001
1012
|
)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubydex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shopify
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A high-performance static analysis suite for Ruby, built in Rust with
|
|
14
14
|
Ruby APIs
|