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,166 @@
|
|
|
1
|
+
use super::normalize_indentation;
|
|
2
|
+
use crate::indexing::local_graph::LocalGraph;
|
|
3
|
+
use crate::indexing::ruby_indexer::RubyIndexer;
|
|
4
|
+
use crate::model::definitions::Definition;
|
|
5
|
+
use crate::model::ids::UriId;
|
|
6
|
+
use crate::offset::Offset;
|
|
7
|
+
use crate::position::Position;
|
|
8
|
+
|
|
9
|
+
#[cfg(any(test, feature = "test_utils"))]
|
|
10
|
+
pub struct LocalGraphTest {
|
|
11
|
+
uri: String,
|
|
12
|
+
graph: LocalGraph,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[cfg(any(test, feature = "test_utils"))]
|
|
16
|
+
impl LocalGraphTest {
|
|
17
|
+
#[must_use]
|
|
18
|
+
pub fn new(uri: &str, source: &str) -> Self {
|
|
19
|
+
let uri = uri.to_string();
|
|
20
|
+
let source = normalize_indentation(source);
|
|
21
|
+
|
|
22
|
+
let mut indexer = RubyIndexer::new(uri.clone(), &source);
|
|
23
|
+
indexer.index();
|
|
24
|
+
let graph = indexer.local_graph();
|
|
25
|
+
|
|
26
|
+
Self { uri, graph }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[must_use]
|
|
30
|
+
pub fn uri(&self) -> &str {
|
|
31
|
+
&self.uri
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#[must_use]
|
|
35
|
+
pub fn graph(&self) -> &LocalGraph {
|
|
36
|
+
&self.graph
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// # Panics
|
|
40
|
+
///
|
|
41
|
+
/// Panics if a definition cannot be found at the given location.
|
|
42
|
+
#[must_use]
|
|
43
|
+
pub fn all_definitions_at<'a>(&'a self, location: &str) -> Vec<&'a Definition> {
|
|
44
|
+
let (uri, offset) = self.parse_location(&format!("{}:{}", self.uri(), location));
|
|
45
|
+
let uri_id = UriId::from(&uri);
|
|
46
|
+
|
|
47
|
+
let definitions = self
|
|
48
|
+
.graph()
|
|
49
|
+
.definitions()
|
|
50
|
+
.values()
|
|
51
|
+
.filter(|def| def.uri_id() == &uri_id && def.offset() == &offset)
|
|
52
|
+
.collect::<Vec<_>>();
|
|
53
|
+
|
|
54
|
+
assert!(
|
|
55
|
+
!definitions.is_empty(),
|
|
56
|
+
"could not find a definition matching {location}, did you mean one of the following: {:?}",
|
|
57
|
+
{
|
|
58
|
+
let mut offsets = self
|
|
59
|
+
.graph()
|
|
60
|
+
.definitions()
|
|
61
|
+
.values()
|
|
62
|
+
.map(crate::model::definitions::Definition::offset)
|
|
63
|
+
.collect::<Vec<_>>();
|
|
64
|
+
|
|
65
|
+
offsets.sort_by_key(|a| a.start());
|
|
66
|
+
|
|
67
|
+
offsets
|
|
68
|
+
.iter()
|
|
69
|
+
.map(|offset| offset.to_display_range(self.graph.document()))
|
|
70
|
+
.collect::<Vec<_>>()
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
definitions
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// # Panics
|
|
78
|
+
///
|
|
79
|
+
/// Panics if no definition or multiple definitions are found at the given location.
|
|
80
|
+
#[must_use]
|
|
81
|
+
pub fn definition_at<'a>(&'a self, location: &str) -> &'a Definition {
|
|
82
|
+
let definitions = self.all_definitions_at(location);
|
|
83
|
+
assert!(
|
|
84
|
+
definitions.len() < 2,
|
|
85
|
+
"found more than one definition matching {location}"
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
definitions[0]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Parses a location string like `<file:///foo.rb:3:0-3:5>` into `(uri, start_offset, end_offset)`
|
|
92
|
+
///
|
|
93
|
+
/// Format: uri:start_line:start_column-end_line:end_column
|
|
94
|
+
/// Line and column numbers are 0-indexed
|
|
95
|
+
///
|
|
96
|
+
/// # Panics
|
|
97
|
+
///
|
|
98
|
+
/// Panics if the location format is invalid, the URI has no source, or the positions are invalid.
|
|
99
|
+
#[must_use]
|
|
100
|
+
pub fn parse_location(&self, location: &str) -> (String, Offset) {
|
|
101
|
+
let (uri, start_position, end_position) = Self::parse_location_positions(location);
|
|
102
|
+
let line_index = self.graph.document().line_index();
|
|
103
|
+
|
|
104
|
+
let start_offset = line_index.offset(start_position).unwrap_or(0.into());
|
|
105
|
+
let end_offset = line_index.offset(end_position).unwrap_or(0.into());
|
|
106
|
+
|
|
107
|
+
(uri, Offset::new(start_offset.into(), end_offset.into()))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn parse_location_positions(location: &str) -> (String, Position, Position) {
|
|
111
|
+
let trimmed = location.trim().trim_start_matches('<').trim_end_matches('>');
|
|
112
|
+
|
|
113
|
+
let (start_part, end_part) = trimmed.rsplit_once('-').unwrap_or_else(|| {
|
|
114
|
+
panic!("Invalid location format: {location} (expected uri:start_line:start_column-end_line:end_column)")
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
let (start_prefix, start_column_str) = start_part
|
|
118
|
+
.rsplit_once(':')
|
|
119
|
+
.unwrap_or_else(|| panic!("Invalid location format: missing start column in {location}"));
|
|
120
|
+
let (uri, start_line_str) = start_prefix
|
|
121
|
+
.rsplit_once(':')
|
|
122
|
+
.unwrap_or_else(|| panic!("Invalid location format: missing start line in {location}"));
|
|
123
|
+
|
|
124
|
+
let (end_line_str, end_column_str) = end_part
|
|
125
|
+
.split_once(':')
|
|
126
|
+
.unwrap_or_else(|| panic!("Invalid location format: missing end line or column in {location}"));
|
|
127
|
+
|
|
128
|
+
let start_line = Self::parse_number(start_line_str, "start line", location);
|
|
129
|
+
let start_column = Self::parse_number(start_column_str, "start column", location);
|
|
130
|
+
let end_line = Self::parse_number(end_line_str, "end line", location);
|
|
131
|
+
let end_column = Self::parse_number(end_column_str, "end column", location);
|
|
132
|
+
|
|
133
|
+
(
|
|
134
|
+
uri.to_string(),
|
|
135
|
+
Position {
|
|
136
|
+
line: start_line - 1,
|
|
137
|
+
col: start_column - 1,
|
|
138
|
+
},
|
|
139
|
+
Position {
|
|
140
|
+
line: end_line - 1,
|
|
141
|
+
col: end_column - 1,
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fn parse_number(value: &str, field: &str, location: &str) -> u32 {
|
|
147
|
+
value
|
|
148
|
+
.parse()
|
|
149
|
+
.unwrap_or_else(|_| panic!("Invalid {field} '{value}' in location {location}"))
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[cfg(test)]
|
|
154
|
+
mod tests {
|
|
155
|
+
use super::*;
|
|
156
|
+
|
|
157
|
+
#[test]
|
|
158
|
+
fn parse_locations() {
|
|
159
|
+
let context = LocalGraphTest::new("file://foo.rb", "class Foo; end");
|
|
160
|
+
|
|
161
|
+
let (uri, offset) = context.parse_location("file://foo.rb:1:1-1:14");
|
|
162
|
+
|
|
163
|
+
assert_eq!(uri, "file://foo.rb");
|
|
164
|
+
assert_eq!(offset, Offset::new(0, 13));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
mod context;
|
|
2
|
+
mod graph_test;
|
|
3
|
+
mod local_graph_test;
|
|
4
|
+
|
|
5
|
+
pub use context::Context;
|
|
6
|
+
pub use context::with_context;
|
|
7
|
+
pub use graph_test::GraphTest;
|
|
8
|
+
pub use local_graph_test::LocalGraphTest;
|
|
9
|
+
|
|
10
|
+
#[must_use]
|
|
11
|
+
pub fn normalize_indentation(input: &str) -> String {
|
|
12
|
+
let input = if let Some(rest) = input.strip_prefix('\n') {
|
|
13
|
+
match rest.chars().next() {
|
|
14
|
+
Some(' ' | '\t') => rest,
|
|
15
|
+
_ => input,
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
input
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let lines: Vec<&str> = input.lines().collect();
|
|
22
|
+
if lines.is_empty() {
|
|
23
|
+
return String::new();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let first_non_empty_line = match lines.iter().find(|line| !line.trim().is_empty()) {
|
|
27
|
+
Some(line) => *line,
|
|
28
|
+
None => return input.to_string(),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
let base_indent = first_non_empty_line.len() - first_non_empty_line.trim_start().len();
|
|
32
|
+
|
|
33
|
+
let mut normalized = lines
|
|
34
|
+
.iter()
|
|
35
|
+
.map(|line| {
|
|
36
|
+
if line.trim().is_empty() {
|
|
37
|
+
""
|
|
38
|
+
} else if line.len() >= base_indent && line.chars().take(base_indent).all(char::is_whitespace) {
|
|
39
|
+
&line[base_indent..]
|
|
40
|
+
} else {
|
|
41
|
+
line
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
.collect::<Vec<_>>()
|
|
45
|
+
.join("\n");
|
|
46
|
+
|
|
47
|
+
if input.ends_with('\n') {
|
|
48
|
+
normalized.push('\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
normalized
|
|
52
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
//! DOT format generator for Graphviz visualization of the graph structure.
|
|
2
|
+
|
|
3
|
+
use std::fmt::Write;
|
|
4
|
+
|
|
5
|
+
use crate::model::graph::Graph;
|
|
6
|
+
|
|
7
|
+
const NAME_NODE_SHAPE: &str = "hexagon";
|
|
8
|
+
const DEFINITION_NODE_SHAPE: &str = "ellipse";
|
|
9
|
+
const URI_NODE_SHAPE: &str = "box";
|
|
10
|
+
|
|
11
|
+
/// Escapes a string for use in DOT format labels and identifiers.
|
|
12
|
+
fn escape_dot_string(s: &str) -> String {
|
|
13
|
+
if !s.contains('"') {
|
|
14
|
+
return s.to_string();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let mut result = String::with_capacity(s.len());
|
|
18
|
+
for c in s.chars() {
|
|
19
|
+
match c {
|
|
20
|
+
'"' => result.push_str("\\\""),
|
|
21
|
+
_ => result.push(c),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
result
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[must_use]
|
|
28
|
+
pub fn generate(graph: &Graph) -> String {
|
|
29
|
+
let mut output = String::new();
|
|
30
|
+
output.push_str("digraph {\n");
|
|
31
|
+
output.push_str(" rankdir=TB;\n\n");
|
|
32
|
+
|
|
33
|
+
write_declaration_nodes(&mut output, graph);
|
|
34
|
+
write_definition_nodes(&mut output, graph);
|
|
35
|
+
write_document_nodes(&mut output, graph);
|
|
36
|
+
|
|
37
|
+
output.push_str("}\n");
|
|
38
|
+
output
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn write_declaration_nodes(output: &mut String, graph: &Graph) {
|
|
42
|
+
let mut declarations: Vec<_> = graph.declarations().values().collect();
|
|
43
|
+
declarations.sort_by(|a, b| a.name().cmp(b.name()));
|
|
44
|
+
|
|
45
|
+
for declaration in declarations {
|
|
46
|
+
let name = declaration.name();
|
|
47
|
+
let escaped_name = escape_dot_string(name);
|
|
48
|
+
let node_id = format!("Name:{name}");
|
|
49
|
+
let _ = writeln!(
|
|
50
|
+
output,
|
|
51
|
+
" \"{node_id}\" [label=\"{escaped_name}\",shape={NAME_NODE_SHAPE}];"
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
for def_id in declaration.definitions() {
|
|
55
|
+
let _ = writeln!(output, " \"{node_id}\" -> \"def_{def_id}\" [dir=both];");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
output.push('\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fn write_definition_nodes(output: &mut String, graph: &Graph) {
|
|
63
|
+
let mut definitions: Vec<_> = graph
|
|
64
|
+
.definitions()
|
|
65
|
+
.iter()
|
|
66
|
+
.filter_map(|(def_id, definition)| {
|
|
67
|
+
graph
|
|
68
|
+
.declarations()
|
|
69
|
+
.get(graph.definitions_to_declarations().get(def_id).unwrap())
|
|
70
|
+
.map(|declaration| {
|
|
71
|
+
let def_type = definition.kind();
|
|
72
|
+
let escaped_name = escape_dot_string(declaration.name());
|
|
73
|
+
let label = format!("{def_type}({escaped_name})");
|
|
74
|
+
let line = format!(" \"def_{def_id}\" [label=\"{label}\",shape={DEFINITION_NODE_SHAPE}];\n");
|
|
75
|
+
(label, line)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
.collect();
|
|
79
|
+
|
|
80
|
+
definitions.sort_by(|a, b| a.0.cmp(&b.0));
|
|
81
|
+
|
|
82
|
+
for (_, line) in definitions {
|
|
83
|
+
output.push_str(&line);
|
|
84
|
+
}
|
|
85
|
+
output.push('\n');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn write_document_nodes(output: &mut String, graph: &Graph) {
|
|
89
|
+
let mut documents: Vec<_> = graph.documents().values().collect();
|
|
90
|
+
documents.sort_by(|a, b| a.uri().cmp(b.uri()));
|
|
91
|
+
|
|
92
|
+
for document in documents {
|
|
93
|
+
let uri = document.uri();
|
|
94
|
+
let label = uri.rsplit('/').next().unwrap_or(uri);
|
|
95
|
+
let escaped_uri = escape_dot_string(uri);
|
|
96
|
+
let escaped_label = escape_dot_string(label);
|
|
97
|
+
let _ = writeln!(
|
|
98
|
+
output,
|
|
99
|
+
" \"{escaped_uri}\" [label=\"{escaped_label}\",shape={URI_NODE_SHAPE}];"
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
for def_id in document.definitions() {
|
|
103
|
+
let _ = writeln!(output, " \"def_{def_id}\" -> \"{escaped_uri}\";");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
output.push('\n');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#[cfg(test)]
|
|
110
|
+
mod tests {
|
|
111
|
+
use super::*;
|
|
112
|
+
use crate::test_utils::GraphTest;
|
|
113
|
+
|
|
114
|
+
fn create_test_graph() -> GraphTest {
|
|
115
|
+
let mut graph_test = GraphTest::new();
|
|
116
|
+
graph_test.index_uri(
|
|
117
|
+
"file:///test.rb",
|
|
118
|
+
"
|
|
119
|
+
class TestClass
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
module TestModule
|
|
123
|
+
end
|
|
124
|
+
",
|
|
125
|
+
);
|
|
126
|
+
graph_test.resolve();
|
|
127
|
+
graph_test
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#[test]
|
|
131
|
+
fn test_dot_generation() {
|
|
132
|
+
let context = create_test_graph();
|
|
133
|
+
let dot_output = generate(context.graph());
|
|
134
|
+
|
|
135
|
+
let class_def_id = context
|
|
136
|
+
.graph()
|
|
137
|
+
.definitions()
|
|
138
|
+
.iter()
|
|
139
|
+
.find(|(_, def)| matches!(def, crate::model::definitions::Definition::Class(_)))
|
|
140
|
+
.map(|(id, _)| id.to_string())
|
|
141
|
+
.unwrap();
|
|
142
|
+
|
|
143
|
+
let module_def_id = context
|
|
144
|
+
.graph()
|
|
145
|
+
.definitions()
|
|
146
|
+
.iter()
|
|
147
|
+
.find(|(_, def)| matches!(def, crate::model::definitions::Definition::Module(_)))
|
|
148
|
+
.map(|(id, _)| id.to_string())
|
|
149
|
+
.unwrap();
|
|
150
|
+
|
|
151
|
+
let expected = format!(
|
|
152
|
+
r#"digraph {{
|
|
153
|
+
rankdir=TB;
|
|
154
|
+
|
|
155
|
+
"Name:Class" [label="Class",shape=hexagon];
|
|
156
|
+
"Name:Module" [label="Module",shape=hexagon];
|
|
157
|
+
"Name:Object" [label="Object",shape=hexagon];
|
|
158
|
+
"Name:TestClass" [label="TestClass",shape=hexagon];
|
|
159
|
+
"Name:TestClass" -> "def_{class_def_id}" [dir=both];
|
|
160
|
+
"Name:TestModule" [label="TestModule",shape=hexagon];
|
|
161
|
+
"Name:TestModule" -> "def_{module_def_id}" [dir=both];
|
|
162
|
+
|
|
163
|
+
"def_{class_def_id}" [label="Class(TestClass)",shape=ellipse];
|
|
164
|
+
"def_{module_def_id}" [label="Module(TestModule)",shape=ellipse];
|
|
165
|
+
|
|
166
|
+
"file:///test.rb" [label="test.rb",shape=box];
|
|
167
|
+
"def_{class_def_id}" -> "file:///test.rb";
|
|
168
|
+
"def_{module_def_id}" -> "file:///test.rb";
|
|
169
|
+
|
|
170
|
+
}}
|
|
171
|
+
"#
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
assert_eq!(dot_output, expected);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
use assert_cmd::{assert::Assert, prelude::*};
|
|
2
|
+
use predicates::prelude::*;
|
|
3
|
+
use regex::Regex;
|
|
4
|
+
use rubydex::test_utils::{normalize_indentation, with_context};
|
|
5
|
+
use std::process::Command;
|
|
6
|
+
|
|
7
|
+
fn rdx_cmd(args: &[&str]) -> Command {
|
|
8
|
+
let mut cmd = Command::cargo_bin("rubydex_cli").unwrap();
|
|
9
|
+
cmd.args(args);
|
|
10
|
+
cmd
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fn rdx(args: &[&str]) -> Assert {
|
|
14
|
+
rdx_cmd(args).assert()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[test]
|
|
18
|
+
fn prints_help() {
|
|
19
|
+
rdx(&["--help"])
|
|
20
|
+
.success()
|
|
21
|
+
.stdout(predicate::str::contains("A Static Analysis Toolkit for Ruby"))
|
|
22
|
+
.stdout(predicate::str::contains("Usage:"))
|
|
23
|
+
.stdout(predicate::str::contains("--stats"))
|
|
24
|
+
.stdout(predicate::str::contains("--visualize"))
|
|
25
|
+
.stdout(predicate::str::contains("--stop-after"));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[test]
|
|
29
|
+
fn paths_argument_variants() {
|
|
30
|
+
rdx(&[])
|
|
31
|
+
.success()
|
|
32
|
+
.stderr(predicate::str::is_empty())
|
|
33
|
+
.stdout(predicate::str::contains("Indexed 0 files"));
|
|
34
|
+
|
|
35
|
+
rdx(&["."])
|
|
36
|
+
.success()
|
|
37
|
+
.stderr(predicate::str::is_empty())
|
|
38
|
+
.stdout(predicate::str::contains("Indexed 0 files"));
|
|
39
|
+
|
|
40
|
+
with_context(|context| {
|
|
41
|
+
context.write("dir1/file1.rb", "class Class1\nend\n");
|
|
42
|
+
context.write("dir1/file2.rb", "class Class2\nend\n");
|
|
43
|
+
context.write("dir2/file1.rb", "class Class3\nend\n");
|
|
44
|
+
context.write("dir2/file2.rb", "class Class4\nend\n"); // not indexed
|
|
45
|
+
|
|
46
|
+
rdx(&[
|
|
47
|
+
context.absolute_path_to("dir1").to_str().unwrap(),
|
|
48
|
+
context.absolute_path_to("dir2/file1.rb").to_str().unwrap(),
|
|
49
|
+
])
|
|
50
|
+
.success()
|
|
51
|
+
.stderr(predicate::str::is_empty())
|
|
52
|
+
.stdout(predicate::str::contains("Indexed 3 files"));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[test]
|
|
57
|
+
fn prints_index_metrics() {
|
|
58
|
+
with_context(|context| {
|
|
59
|
+
context.write("file1.rb", "class FirstClass\nend\n");
|
|
60
|
+
context.write("file2.rb", "module SecondModule\nend\n");
|
|
61
|
+
|
|
62
|
+
rdx(&[context.absolute_path().to_str().unwrap()])
|
|
63
|
+
.success()
|
|
64
|
+
.stderr(predicate::str::is_empty())
|
|
65
|
+
.stdout(predicate::str::contains("Indexed 2 files"))
|
|
66
|
+
.stdout(predicate::str::contains("Found 5 names"))
|
|
67
|
+
.stdout(predicate::str::contains("Found 2 definitions"));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn normalize_visualization_output(output: &str) -> String {
|
|
72
|
+
let def_re = Regex::new(r"def_-?[a-f0-9]+").unwrap();
|
|
73
|
+
let uri_re = Regex::new(r#"file://[^"]+/([^/"]+\.rb)"#).unwrap();
|
|
74
|
+
|
|
75
|
+
let normalized = def_re.replace_all(output, "def_<ID>");
|
|
76
|
+
uri_re.replace_all(&normalized, "file://<PATH>/$1").to_string()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#[test]
|
|
80
|
+
fn visualize_simple_class() {
|
|
81
|
+
with_context(|context| {
|
|
82
|
+
context.write("simple.rb", "class SimpleClass\nend\n");
|
|
83
|
+
|
|
84
|
+
let output = rdx_cmd(&[context.absolute_path().to_str().unwrap(), "--visualize"])
|
|
85
|
+
.output()
|
|
86
|
+
.unwrap();
|
|
87
|
+
|
|
88
|
+
assert!(output.status.success());
|
|
89
|
+
|
|
90
|
+
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
91
|
+
let normalized = normalize_visualization_output(&stdout);
|
|
92
|
+
|
|
93
|
+
let expected = normalize_indentation({
|
|
94
|
+
r#"
|
|
95
|
+
digraph {
|
|
96
|
+
rankdir=TB;
|
|
97
|
+
|
|
98
|
+
"Name:Class" [label="Class",shape=hexagon];
|
|
99
|
+
"Name:Module" [label="Module",shape=hexagon];
|
|
100
|
+
"Name:Object" [label="Object",shape=hexagon];
|
|
101
|
+
"Name:SimpleClass" [label="SimpleClass",shape=hexagon];
|
|
102
|
+
"Name:SimpleClass" -> "def_<ID>" [dir=both];
|
|
103
|
+
|
|
104
|
+
"def_<ID>" [label="Class(SimpleClass)",shape=ellipse];
|
|
105
|
+
|
|
106
|
+
"file://<PATH>/simple.rb" [label="simple.rb",shape=box];
|
|
107
|
+
"def_<ID>" -> "file://<PATH>/simple.rb";
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
"#
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
assert_eq!(normalized, expected);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#[test]
|
|
119
|
+
fn stop_after() {
|
|
120
|
+
with_context(|context| {
|
|
121
|
+
context.write("file1.rb", "class Class1\nend\n");
|
|
122
|
+
context.write("file2.rb", "class Class2\nend\n");
|
|
123
|
+
|
|
124
|
+
rdx(&[
|
|
125
|
+
context.absolute_path().to_str().unwrap(),
|
|
126
|
+
"--stop-after",
|
|
127
|
+
"listing",
|
|
128
|
+
"--stats",
|
|
129
|
+
])
|
|
130
|
+
.success()
|
|
131
|
+
.stdout(predicate::str::contains("Listing"))
|
|
132
|
+
.stdout(predicate::str::contains("Indexing").not())
|
|
133
|
+
.stdout(predicate::str::contains("Resolution").not())
|
|
134
|
+
.stdout(predicate::str::contains("Querying").not());
|
|
135
|
+
|
|
136
|
+
rdx(&[
|
|
137
|
+
context.absolute_path().to_str().unwrap(),
|
|
138
|
+
"--stop-after",
|
|
139
|
+
"indexing",
|
|
140
|
+
"--stats",
|
|
141
|
+
])
|
|
142
|
+
.success()
|
|
143
|
+
.stdout(predicate::str::contains("Listing"))
|
|
144
|
+
.stdout(predicate::str::contains("Indexing"))
|
|
145
|
+
.stdout(predicate::str::contains("Resolution").not())
|
|
146
|
+
.stdout(predicate::str::contains("Querying").not());
|
|
147
|
+
|
|
148
|
+
rdx(&[
|
|
149
|
+
context.absolute_path().to_str().unwrap(),
|
|
150
|
+
"--stop-after",
|
|
151
|
+
"resolution",
|
|
152
|
+
"--stats",
|
|
153
|
+
])
|
|
154
|
+
.success()
|
|
155
|
+
.stdout(predicate::str::contains("Listing"))
|
|
156
|
+
.stdout(predicate::str::contains("Indexing"))
|
|
157
|
+
.stdout(predicate::str::contains("Resolution"))
|
|
158
|
+
.stdout(predicate::str::contains("Querying").not());
|
|
159
|
+
|
|
160
|
+
rdx(&[context.absolute_path().to_str().unwrap(), "--stats"])
|
|
161
|
+
.success()
|
|
162
|
+
.stdout(predicate::str::contains("Listing"))
|
|
163
|
+
.stdout(predicate::str::contains("Indexing"))
|
|
164
|
+
.stdout(predicate::str::contains("Resolution"))
|
|
165
|
+
.stdout(predicate::str::contains("Querying"));
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "rubydex-sys"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
crate-type = ["cdylib", "staticlib"]
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
rubydex = { path = "../rubydex" }
|
|
12
|
+
libc = "0.2.174"
|
|
13
|
+
url = "2.5.4"
|
|
14
|
+
line-index = "0.1.2"
|
|
15
|
+
|
|
16
|
+
[build-dependencies]
|
|
17
|
+
cbindgen = "0.29"
|
|
18
|
+
|
|
19
|
+
[lints]
|
|
20
|
+
workspace = true
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
extern crate cbindgen;
|
|
2
|
+
|
|
3
|
+
fn main() {
|
|
4
|
+
cbindgen::generate(".")
|
|
5
|
+
.expect("Unable to generate bindings")
|
|
6
|
+
.write_to_file("rustbindings.h");
|
|
7
|
+
|
|
8
|
+
// Set the install name for macOS dylibs so they can be found via @rpath at runtime.
|
|
9
|
+
// This works for both native and cross-compilation scenarios.
|
|
10
|
+
let target = std::env::var("TARGET").unwrap_or_default();
|
|
11
|
+
if target.contains("apple") {
|
|
12
|
+
println!("cargo::rustc-cdylib-link-arg=-Wl,-install_name,@rpath/librubydex_sys.dylib");
|
|
13
|
+
}
|
|
14
|
+
}
|