1use tree_sitter::{Node, Parser};
5
6use super::types::*;
7
8pub fn parse(source: &str, file: &str, result: &mut ScanResult) {
9 let mut parser = Parser::new();
10 if parser
11 .set_language(&tree_sitter_rust::LANGUAGE.into())
12 .is_err()
13 {
14 return;
15 }
16 let tree = match parser.parse(source, None) {
17 Some(t) => t,
18 None => return,
19 };
20 extract_rust(&tree.root_node(), source, file, result);
21}
22
23fn extract_rust(root: &Node, source: &str, file: &str, result: &mut ScanResult) {
24 let mut cursor = root.walk();
25 for child in root.named_children(&mut cursor) {
26 match child.kind() {
27 "struct_item" => extract_struct(&child, source, file, result),
28 "enum_item" => extract_enum(&child, source, file, result),
29 "trait_item" => extract_trait(&child, source, file, result),
30 "impl_item" => extract_impl(&child, source, file, result),
31 "function_item" => extract_function(&child, source, file, result),
32 _ => {}
33 }
34 }
35}
36
37fn extract_struct(node: &Node, source: &str, file: &str, result: &mut ScanResult) {
38 let name = match get_name(node, source) {
39 Some(n) => n,
40 None => return,
41 };
42 let vis = get_visibility(node, source);
43 let fields = extract_fields(node, source);
44
45 result.types.insert(
46 name.clone(),
47 TypeInfo {
48 name,
49 source: source_loc(file, node),
50 kind: TypeKind::Struct,
51 fields,
52 visibility: vis,
53 ..Default::default()
54 },
55 );
56}
57
58fn extract_fields(node: &Node, source: &str) -> Vec<Field> {
59 let mut fields = Vec::new();
60 if let Some(body) = node.child_by_field_name("body") {
61 let mut cursor = body.walk();
62 for child in body.named_children(&mut cursor) {
63 if child.kind() == "field_declaration" {
64 if let Some(name_node) = child.child_by_field_name("name") {
65 let name = node_text(&name_node, source).to_string();
66 let type_name = child
67 .child_by_field_name("type")
68 .map(|t| node_text(&t, source).to_string())
69 .unwrap_or_default();
70 let optional = type_name.starts_with("Option<");
71 fields.push(Field {
72 name,
73 type_name,
74 optional,
75 });
76 }
77 }
78 }
79 }
80 fields
81}
82
83fn extract_enum(node: &Node, source: &str, file: &str, result: &mut ScanResult) {
84 let name = match get_name(node, source) {
85 Some(n) => n,
86 None => return,
87 };
88 let vis = get_visibility(node, source);
89
90 let mut variants = Vec::new();
91 if let Some(body) = node.child_by_field_name("body") {
92 let mut cursor = body.walk();
93 for child in body.named_children(&mut cursor) {
94 if child.kind() == "enum_variant" {
95 if let Some(name_node) = child.child_by_field_name("name") {
96 variants.push(node_text(&name_node, source).to_string());
97 }
98 }
99 }
100 }
101
102 result.types.insert(
103 name.clone(),
104 TypeInfo {
105 name,
106 source: source_loc(file, node),
107 kind: TypeKind::Enum,
108 variants,
109 visibility: vis,
110 ..Default::default()
111 },
112 );
113}
114
115fn extract_trait(node: &Node, source: &str, file: &str, result: &mut ScanResult) {
116 let name = match get_name(node, source) {
117 Some(n) => n,
118 None => return,
119 };
120 let vis = get_visibility(node, source);
121
122 let mut methods = Vec::new();
123 if let Some(body) = node.child_by_field_name("body") {
124 let mut cursor = body.walk();
125 for child in body.named_children(&mut cursor) {
126 if child.kind() == "function_signature_item" || child.kind() == "function_item" {
127 if let Some(name_node) = child.child_by_field_name("name") {
128 methods.push(node_text(&name_node, source).to_string());
129 }
130 }
131 }
132 }
133
134 result.types.insert(
135 name.clone(),
136 TypeInfo {
137 name,
138 source: source_loc(file, node),
139 kind: TypeKind::Trait,
140 methods,
141 visibility: vis,
142 ..Default::default()
143 },
144 );
145}
146
147fn extract_impl(node: &Node, source: &str, file: &str, result: &mut ScanResult) {
148 let type_node = match node.child_by_field_name("type") {
149 Some(n) => n,
150 None => return,
151 };
152 let type_name = node_text(&type_node, source).to_string();
153
154 let trait_name = node
155 .child_by_field_name("trait")
156 .map(|t| node_text(&t, source).to_string());
157
158 let mut methods = Vec::new();
159 if let Some(body) = node.child_by_field_name("body") {
160 let mut cursor = body.walk();
161 for child in body.named_children(&mut cursor) {
162 if child.kind() == "function_item" {
163 let vis = get_visibility(&child, source);
164 if let Some(name_node) = child.child_by_field_name("name") {
165 let method_name = node_text(&name_node, source).to_string();
166 methods.push(method_name.clone());
167
168 if matches!(vis, Visibility::Public) {
169 let sig = build_fn_signature(&child, source);
170 let is_async = has_async(&child, source);
171 let is_test = has_test_attr(&child, source);
172 let qualified = format!("{}::{}", type_name, method_name);
173 result.functions.insert(
174 qualified,
175 FunctionInfo {
176 name: method_name,
177 source: source_loc(file, &child),
178 signature: sig,
179 visibility: vis,
180 is_async,
181 is_test,
182 },
183 );
184 }
185 }
186 }
187 }
188 }
189
190 if let Some(typedef) = result.types.get_mut(&type_name) {
191 for m in &methods {
192 if !typedef.methods.contains(m) {
193 typedef.methods.push(m.clone());
194 }
195 }
196 if let Some(trait_name) = &trait_name {
197 if !typedef.implements.contains(trait_name) {
198 typedef.implements.push(trait_name.clone());
199 }
200 }
201 }
202}
203
204fn extract_function(node: &Node, source: &str, file: &str, result: &mut ScanResult) {
205 let name = match node.child_by_field_name("name") {
206 Some(n) => node_text(&n, source).to_string(),
207 None => return,
208 };
209 let vis = get_visibility(node, source);
210 let sig = build_fn_signature(node, source);
211 let is_async = has_async(node, source);
212 let is_test = has_test_attr(node, source);
213
214 result.functions.insert(
215 name.clone(),
216 FunctionInfo {
217 name,
218 source: source_loc(file, node),
219 signature: sig,
220 visibility: vis,
221 is_async,
222 is_test,
223 },
224 );
225}
226
227fn get_visibility(node: &Node, source: &str) -> Visibility {
230 let mut cursor = node.walk();
231 for child in node.named_children(&mut cursor) {
232 if child.kind() == "visibility_modifier" {
233 let text = node_text(&child, source);
234 return if text.contains("pub(crate)") || text.contains("pub(super)") {
235 Visibility::Internal
236 } else {
237 Visibility::Public
238 };
239 }
240 }
241 Visibility::Private
242}
243
244fn node_text<'a>(node: &Node, source: &'a str) -> &'a str {
245 &source[node.byte_range()]
246}
247
248fn get_name(node: &Node, source: &str) -> Option<String> {
249 for field_name in &["name", "type"] {
250 if let Some(name_node) = node.child_by_field_name(field_name) {
251 return Some(node_text(&name_node, source).to_string());
252 }
253 }
254 None
255}
256
257fn source_loc(file: &str, node: &Node) -> String {
258 format!("{}:{}", file, node.start_position().row + 1)
259}
260
261fn build_fn_signature(node: &Node, source: &str) -> String {
262 let name = node
263 .child_by_field_name("name")
264 .map(|n| node_text(&n, source))
265 .unwrap_or("?");
266 let params = node
267 .child_by_field_name("parameters")
268 .map(|n| node_text(&n, source))
269 .unwrap_or("()");
270 let ret = node
271 .child_by_field_name("return_type")
272 .map(|n| format!(" -> {}", node_text(&n, source)))
273 .unwrap_or_default();
274 let async_prefix = if has_async(node, source) {
275 "async "
276 } else {
277 ""
278 };
279 format!("{async_prefix}fn {name}{params}{ret}")
280}
281
282fn has_async(node: &Node, source: &str) -> bool {
283 let text = node_text(node, source);
284 text.starts_with("async ") || text.starts_with("pub async ") || text.contains(" async fn ")
285}
286
287fn has_test_attr(node: &Node, source: &str) -> bool {
288 if let Some(parent) = node.parent() {
289 let idx = node.start_byte();
290 let mut cursor = parent.walk();
291 for child in parent.named_children(&mut cursor) {
292 if child.start_byte() >= idx {
293 break;
294 }
295 if child.kind() == "attribute_item" || child.kind() == "inner_attribute_item" {
296 let text = node_text(&child, source);
297 if text.contains("test") {
298 return true;
299 }
300 }
301 }
302 }
303 false
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 fn parse_rust_str(source: &str) -> ScanResult {
311 let mut result = ScanResult::default();
312 parse(source, "src/lib.rs", &mut result);
313 result
314 }
315
316 #[test]
317 fn pub_struct_with_fields() {
318 let r = parse_rust_str(
319 r#"
320pub struct User {
321 pub name: String,
322 pub age: u32,
323 pub email: Option<String>,
324}
325"#,
326 );
327 let t = &r.types["User"];
328 assert_eq!(t.kind, TypeKind::Struct);
329 assert_eq!(t.visibility, Visibility::Public);
330 assert_eq!(t.fields.len(), 3);
331 assert_eq!(t.fields[0].name, "name");
332 assert!(!t.fields[0].optional);
333 assert!(t.fields[2].optional);
334 }
335
336 #[test]
337 fn private_struct() {
338 let r = parse_rust_str("struct Internal { x: i32 }");
339 assert_eq!(r.types["Internal"].visibility, Visibility::Private);
340 }
341
342 #[test]
343 fn enum_variants() {
344 let r = parse_rust_str("pub enum Color { Red, Green, Blue }");
345 let t = &r.types["Color"];
346 assert_eq!(t.kind, TypeKind::Enum);
347 assert_eq!(t.variants, vec!["Red", "Green", "Blue"]);
348 }
349
350 #[test]
351 fn trait_methods() {
352 let r = parse_rust_str(
353 r#"
354pub trait Drawable {
355 fn draw(&self);
356 fn resize(&mut self, w: u32, h: u32);
357}
358"#,
359 );
360 let t = &r.types["Drawable"];
361 assert_eq!(t.kind, TypeKind::Trait);
362 assert!(t.methods.contains(&"draw".to_string()));
363 assert!(t.methods.contains(&"resize".to_string()));
364 }
365
366 #[test]
367 fn function_with_signature() {
368 let r = parse_rust_str("pub fn process(input: &str) -> Result<String> { todo!() }");
369 let f = &r.functions["process"];
370 assert_eq!(f.visibility, Visibility::Public);
371 assert!(f.signature.contains("-> Result<String>"));
372 }
373
374 #[test]
375 fn async_function() {
376 let r = parse_rust_str("pub async fn fetch(url: &str) -> Vec<u8> { todo!() }");
377 let f = &r.functions["fetch"];
378 assert!(f.is_async);
379 assert!(f.signature.starts_with("async fn"));
380 }
381
382 #[test]
383 fn impl_adds_methods_and_traits() {
384 let r = parse_rust_str(
385 r#"
386pub struct Foo { val: i32 }
387impl Foo {
388 pub fn new(val: i32) -> Self { Self { val } }
389 fn internal(&self) {}
390}
391impl Display for Foo {
392 fn fmt(&self, f: &mut Formatter) -> Result { todo!() }
393}
394"#,
395 );
396 let t = &r.types["Foo"];
397 assert!(t.methods.contains(&"new".to_string()));
398 assert!(t.methods.contains(&"internal".to_string()));
399 assert!(t.implements.contains(&"Display".to_string()));
400 assert!(r.functions.contains_key("Foo::new"));
401 }
402
403 #[test]
404 fn source_location() {
405 let r = parse_rust_str("\npub struct Pos;");
406 assert_eq!(r.types["Pos"].source, "src/lib.rs:2");
407 }
408}