use crate::ast::{FunctionId, FunctionNode};
use crate::language::function_body::FunctionBody;
use crate::language::parser::{LanguageParser, ParsedModule};
use crate::language::span::SourceSpan;
use anyhow::{Context, Result};
use syn::spanned::Spanned;
use syn::{File, ImplItem, ImplItemFn, Item, ItemFn, Signature};
pub struct RustParser;
impl LanguageParser for RustParser {
fn parse(&self, source: &str, filename: &str) -> Result<Box<dyn ParsedModule>> {
let file = syn::parse_file(source)
.with_context(|| format!("Failed to parse Rust file: {}", filename))?;
Ok(Box::new(RustModule::new(file, source.to_string())))
}
}
struct RustModule {
file: File,
source: String,
}
impl RustModule {
fn new(file: File, source: String) -> Self {
Self { file, source }
}
fn extract_functions(&self, file_index: usize) -> Vec<FunctionNode> {
let mut functions = Vec::new();
let mut local_index = 0;
for item in &self.file.items {
self.visit_item(item, None, file_index, &mut local_index, &mut functions);
}
functions.sort_by_key(|f| f.span.start);
functions
}
fn visit_item(
&self,
item: &Item,
type_name: Option<&str>,
file_index: usize,
local_index: &mut usize,
functions: &mut Vec<FunctionNode>,
) {
match item {
Item::Fn(item_fn) => {
self.extract_item_fn(item_fn, type_name, file_index, local_index, functions);
}
Item::Impl(item_impl) => {
let type_name = if let syn::Type::Path(type_path) = &*item_impl.self_ty {
type_path
.path
.segments
.last()
.map(|seg| seg.ident.to_string())
} else {
None
};
for impl_item in &item_impl.items {
if let ImplItem::Fn(method) = impl_item {
self.extract_impl_fn(
method,
type_name.as_deref(),
file_index,
local_index,
functions,
);
}
}
}
_ => {
}
}
}
fn extract_item_fn(
&self,
item_fn: &ItemFn,
name_prefix: Option<&str>,
file_index: usize,
local_index: &mut usize,
functions: &mut Vec<FunctionNode>,
) {
self.extract_function_common(
&item_fn.sig,
item_fn,
name_prefix,
file_index,
local_index,
functions,
);
}
fn extract_impl_fn(
&self,
impl_fn: &ImplItemFn,
name_prefix: Option<&str>,
file_index: usize,
local_index: &mut usize,
functions: &mut Vec<FunctionNode>,
) {
self.extract_function_common(
&impl_fn.sig,
impl_fn,
name_prefix,
file_index,
local_index,
functions,
);
}
fn extract_function_common<S: Spanned>(
&self,
sig: &Signature,
item: &S,
name_prefix: Option<&str>,
file_index: usize,
local_index: &mut usize,
functions: &mut Vec<FunctionNode>,
) {
let name = if let Some(prefix) = name_prefix {
format!("{}::{}", prefix, sig.ident)
} else {
sig.ident.to_string()
};
let full_span = item.span();
let span_start = full_span.start();
let span_end = full_span.end();
let start_byte = self.line_column_to_byte(span_start.line, span_start.column);
let end_byte = self.line_column_to_byte(span_end.line, span_end.column);
let body_source = if start_byte < self.source.len() && end_byte <= self.source.len() {
self.source[start_byte..end_byte].to_string()
} else {
format!("fn {}() {{}}", sig.ident)
};
let span = SourceSpan::new(
start_byte,
end_byte,
span_start.line as u32,
span_end.line as u32,
span_start.column as u32,
);
functions.push(FunctionNode {
id: FunctionId {
file_index,
local_index: *local_index,
},
name: Some(name),
span,
body: FunctionBody::Rust {
source: body_source,
},
suppression_reason: None,
});
*local_index += 1;
}
fn line_column_to_byte(&self, line: usize, column: usize) -> usize {
let mut byte_offset = 0;
let mut current_line = 1;
for (i, ch) in self.source.char_indices() {
if current_line == line {
let line_start = byte_offset;
let line_text = self.source[line_start..].lines().next().unwrap_or("");
for (col_count, (char_idx, _)) in line_text.char_indices().enumerate() {
if col_count == column {
return line_start + char_idx;
}
}
return line_start + line_text.len();
}
if ch == '\n' {
current_line += 1;
byte_offset = i + 1;
}
}
self.source.len()
}
}
impl ParsedModule for RustModule {
fn discover_functions(&self, file_index: usize, _source: &str) -> Vec<FunctionNode> {
self.extract_functions(file_index)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rust_parser_simple_function() {
let source = r#"
fn simple() {
let x = 1;
}
"#;
let parser = RustParser;
let module = parser.parse(source, "test.rs").unwrap();
let functions = module.discover_functions(0, source);
assert_eq!(functions.len(), 1);
assert_eq!(functions[0].name, Some("simple".to_string()));
assert_eq!(functions[0].id.file_index, 0);
assert_eq!(functions[0].id.local_index, 0);
}
#[test]
fn test_rust_parser_multiple_functions() {
let source = r#"
fn first() {
println!("first");
}
fn second() {
println!("second");
}
"#;
let parser = RustParser;
let module = parser.parse(source, "test.rs").unwrap();
let functions = module.discover_functions(0, source);
assert_eq!(functions.len(), 2);
assert_eq!(functions[0].name, Some("first".to_string()));
assert_eq!(functions[1].name, Some("second".to_string()));
}
#[test]
fn test_rust_parser_method() {
let source = r#"
struct Calculator {
value: i32,
}
impl Calculator {
fn new() -> Self {
Calculator { value: 0 }
}
fn add(&mut self, x: i32) {
self.value += x;
}
}
"#;
let parser = RustParser;
let module = parser.parse(source, "test.rs").unwrap();
let functions = module.discover_functions(0, source);
assert_eq!(functions.len(), 2);
assert_eq!(functions[0].name, Some("Calculator::new".to_string()));
assert_eq!(functions[1].name, Some("Calculator::add".to_string()));
}
#[test]
fn test_rust_parser_async_function() {
let source = r#"
async fn fetch_data() -> String {
"data".to_string()
}
"#;
let parser = RustParser;
let module = parser.parse(source, "test.rs").unwrap();
let functions = module.discover_functions(0, source);
assert_eq!(functions.len(), 1);
assert_eq!(functions[0].name, Some("fetch_data".to_string()));
}
#[test]
fn test_rust_parser_parse_error() {
let source = r#"
fn invalid( {
// Missing closing paren
}
"#;
let parser = RustParser;
let result = parser.parse(source, "test.rs");
assert!(result.is_err());
}
#[test]
fn test_rust_parser_deterministic_ordering() {
let source = r#"
fn third() {}
fn first() {}
fn second() {}
"#;
let parser = RustParser;
let module = parser.parse(source, "test.rs").unwrap();
let functions = module.discover_functions(0, source);
assert_eq!(functions.len(), 3);
assert_eq!(functions[0].name, Some("third".to_string()));
assert_eq!(functions[1].name, Some("first".to_string()));
assert_eq!(functions[2].name, Some("second".to_string()));
}
#[test]
fn test_rust_parser_empty_file() {
let source = "";
let parser = RustParser;
let module = parser.parse(source, "test.rs").unwrap();
let functions = module.discover_functions(0, source);
assert_eq!(functions.len(), 0);
}
}