use super::types::{FileContext, FunctionSignature};
const OPEN_BRACE: char = '{';
const CLOSE_BRACE: char = '}';
#[allow(dead_code)]
pub fn analyze_file_context(content: &str) -> FileContext {
let is_test_file = content.contains("#[test]")
|| content.contains("#[cfg(test)]")
|| content.contains("mod tests");
let is_bin_file = content.contains("fn main()");
let is_example_file = content.contains("//! Example") || content.contains("// Example");
let mut function_signatures = Vec::new();
let lines: Vec<&str> = content.lines().collect();
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim_start();
if (trimmed.starts_with("fn ")
|| trimmed.starts_with("pub fn ")
|| trimmed.starts_with("async fn ")
|| trimmed.starts_with("pub async fn "))
&& let Some(sig) = parse_function_signature_multiline(&lines, i)
{
function_signatures.push(sig);
}
}
FileContext {
is_test_file,
is_bin_file,
is_example_file,
function_signatures,
}
}
#[allow(dead_code)]
pub fn parse_function_signature_multiline(
lines: &[&str],
start_idx: usize,
) -> Option<FunctionSignature> {
let (full_signature, brace_line) = collect_signature_lines(lines, start_idx)?;
let name = extract_function_name(&full_signature)?;
let (returns_result, returns_option) = check_return_types(&full_signature);
let line_end = find_function_end(lines, brace_line, start_idx);
Some(FunctionSignature {
name,
line_start: start_idx + 1, line_end: line_end + 1, returns_result,
returns_option,
})
}
#[allow(dead_code)]
fn collect_signature_lines(lines: &[&str], start_idx: usize) -> Option<(String, usize)> {
let mut brace_line = start_idx;
let mut signature_lines = vec![lines[start_idx].to_string()];
while brace_line < lines.len() && !lines[brace_line].contains(OPEN_BRACE) {
if brace_line > start_idx {
signature_lines.push(lines[brace_line].to_string());
}
brace_line += 1;
}
let full_signature = signature_lines.join(" ");
Some((full_signature, brace_line))
}
#[allow(dead_code)]
fn extract_function_name(full_signature: &str) -> Option<String> {
let name_start = full_signature.find("fn ")?;
let name_part = &full_signature[name_start + 3..];
let end = name_part.find(['(', '<'])?;
Some(name_part[..end].trim().to_string())
}
#[allow(dead_code)]
fn check_return_types(full_signature: &str) -> (bool, bool) {
let returns_result = full_signature.contains("-> Result")
|| full_signature.contains("-> anyhow::Result")
|| full_signature.contains("-> std::result::Result")
|| full_signature.contains("-> io::Result");
let returns_option = full_signature.contains("-> Option");
(returns_result, returns_option)
}
fn count_brace(ch: char) -> (usize, usize) {
if ch == OPEN_BRACE {
(1, 0)
} else if ch == CLOSE_BRACE {
(0, 1)
} else {
(0, 0)
}
}
fn find_function_end(lines: &[&str], brace_line: usize, start_idx: usize) -> usize {
let indent = lines[start_idx].len() - lines[start_idx].trim_start().len();
let mut line_end = brace_line + 1;
let mut brace_count = 1;
while line_end < lines.len() && brace_count > 0 {
let line = lines[line_end];
let line_indent = line.len() - line.trim_start().len();
for ch in line.chars() {
let (opens, closes) = count_brace(ch);
brace_count += opens;
brace_count -= closes;
if brace_count == 0 && line_indent == indent {
break;
}
}
if brace_count == 0 {
break;
}
line_end += 1;
}
line_end
}
#[allow(dead_code)]
pub fn check_can_use_question_mark(context: &FileContext) -> bool {
if context.is_test_file {
return false;
}
if context.is_bin_file {
for sig in &context.function_signatures {
if sig.name == "main" && sig.returns_result {
return true;
}
}
return false;
}
if context.is_example_file {
return false;
}
!context.function_signatures.is_empty()
&& context
.function_signatures
.iter()
.any(|sig| sig.returns_result || sig.returns_option)
}