use crate::qdd::{CreateSpec, QualityProfile};
use anyhow::Result;
pub struct AstBuilder {
pub(crate) profile: QualityProfile,
}
impl AstBuilder {
#[must_use]
pub fn new(profile: QualityProfile) -> Self {
Self { profile }
}
pub fn build_function(&self, spec: &CreateSpec) -> Result<String> {
let mut code = String::new();
code.push_str(&format!("/// {}\n", spec.purpose));
code.push_str("///\n");
for param in &spec.inputs {
code.push_str(&format!(
"/// * `{}` - {}\n",
param.name,
param
.description
.as_deref()
.unwrap_or("Parameter description")
));
}
code.push_str("///\n");
code.push_str(&format!(
"/// # Returns\n/// {}\n",
spec.outputs
.description
.as_deref()
.unwrap_or("Function return value")
));
if self.profile.thresholds.require_doctests {
code.push_str("///\n/// # Example\n/// ```\n");
code.push_str(&format!("/// let result = {}(", spec.name));
for (i, param) in spec.inputs.iter().enumerate() {
if i > 0 {
code.push_str(", ");
}
code.push_str(&self.generate_example_value(¶m.param_type));
}
code.push_str(");\n/// assert!(result.is_ok());\n/// ```\n");
}
code.push_str(&format!("pub fn {}(", spec.name));
for (i, param) in spec.inputs.iter().enumerate() {
if i > 0 {
code.push_str(", ");
}
code.push_str(&format!("{}: {}", param.name, param.param_type));
}
code.push_str(&format!(") -> Result<{}> {{\n", spec.outputs.param_type));
code.push_str(" // Implementation placeholder\n");
code.push_str(" todo!(\"Implementation needed\")\n");
code.push_str("}\n");
Ok(code)
}
pub(crate) fn generate_example_value(&self, param_type: &str) -> String {
match param_type {
"u32" | "i32" => "42".to_string(),
"f32" | "f64" => "3.14".to_string(),
"String" => "\"test\".to_string()".to_string(),
"&str" => "\"test\"".to_string(),
"bool" => "true".to_string(),
_ => "Default::default()".to_string(),
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use crate::qdd::{CodeType, CreateSpec, Parameter};
#[test]
fn test_ast_builder_function_generation() {
let profile = QualityProfile::standard();
let builder = AstBuilder::new(profile);
let spec = CreateSpec {
code_type: CodeType::Function,
name: "test_func".to_string(),
purpose: "Test function".to_string(),
inputs: vec![Parameter {
name: "input".to_string(),
param_type: "String".to_string(),
description: None,
}],
outputs: Parameter {
name: "output".to_string(),
param_type: "String".to_string(),
description: None,
},
};
let code = builder.build_function(&spec).unwrap();
assert!(code.contains("/// Test function"));
assert!(code.contains("pub fn test_func(input: String) -> Result<String>"));
assert!(code.contains("todo!"));
}
#[test]
fn test_ast_builder_with_doctests() {
let mut profile = QualityProfile::standard();
profile.thresholds.require_doctests = true;
let builder = AstBuilder::new(profile);
let spec = create_multi_param_spec("my_func");
let code = builder.build_function(&spec).unwrap();
assert!(code.contains("# Example"));
assert!(code.contains("/// ```"));
assert!(code.contains("let result = my_func("));
assert!(code.contains("assert!(result.is_ok());"));
}
#[test]
fn test_ast_builder_without_doctests() {
let mut profile = QualityProfile::standard();
profile.thresholds.require_doctests = false;
let builder = AstBuilder::new(profile);
let spec = create_minimal_spec("simple");
let code = builder.build_function(&spec).unwrap();
assert!(!code.contains("# Example"));
}
#[test]
fn test_ast_builder_parameter_documentation() {
let profile = QualityProfile::standard();
let builder = AstBuilder::new(profile);
let spec = CreateSpec {
code_type: CodeType::Function,
name: "documented_fn".to_string(),
purpose: "A well-documented function".to_string(),
inputs: vec![
Parameter {
name: "input".to_string(),
param_type: "String".to_string(),
description: Some("The input string".to_string()),
},
Parameter {
name: "flag".to_string(),
param_type: "bool".to_string(),
description: None, },
],
outputs: Parameter {
name: "output".to_string(),
param_type: "i32".to_string(),
description: Some("The computed result".to_string()),
},
};
let code = builder.build_function(&spec).unwrap();
assert!(code.contains("/// A well-documented function"));
assert!(code.contains("/// * `input` - The input string"));
assert!(code.contains("/// * `flag` - Parameter description")); assert!(code.contains("/// # Returns"));
assert!(code.contains("/// The computed result"));
}
#[test]
fn test_generate_example_value_all_types() {
let profile = QualityProfile::standard();
let builder = AstBuilder::new(profile);
assert_eq!(builder.generate_example_value("u32"), "42");
assert_eq!(builder.generate_example_value("i32"), "42");
assert_eq!(builder.generate_example_value("f32"), "3.14");
assert_eq!(builder.generate_example_value("f64"), "3.14");
assert_eq!(
builder.generate_example_value("String"),
"\"test\".to_string()"
);
assert_eq!(builder.generate_example_value("&str"), "\"test\"");
assert_eq!(builder.generate_example_value("bool"), "true");
assert_eq!(
builder.generate_example_value("CustomType"),
"Default::default()"
);
assert_eq!(
builder.generate_example_value("Vec<u8>"),
"Default::default()"
);
}
fn create_minimal_spec(name: &str) -> CreateSpec {
CreateSpec {
code_type: CodeType::Function,
name: name.to_string(),
purpose: "Minimal test function".to_string(),
inputs: vec![],
outputs: Parameter {
name: "result".to_string(),
param_type: "()".to_string(),
description: None,
},
}
}
fn create_multi_param_spec(name: &str) -> CreateSpec {
CreateSpec {
code_type: CodeType::Function,
name: name.to_string(),
purpose: "Multi-parameter function".to_string(),
inputs: vec![
Parameter {
name: "x".to_string(),
param_type: "u32".to_string(),
description: Some("First parameter".to_string()),
},
Parameter {
name: "y".to_string(),
param_type: "i32".to_string(),
description: Some("Second parameter".to_string()),
},
Parameter {
name: "z".to_string(),
param_type: "f64".to_string(),
description: None,
},
],
outputs: Parameter {
name: "result".to_string(),
param_type: "String".to_string(),
description: Some("Computed result".to_string()),
},
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use super::*;
use crate::qdd::{CodeType, CreateSpec, Parameter};
use proptest::prelude::*;
proptest! {
#[test]
fn generated_function_contains_function_name(
name in "[a-z][a-z0-9_]{0,20}"
) {
let profile = QualityProfile::standard();
let builder = AstBuilder::new(profile);
let spec = CreateSpec {
code_type: CodeType::Function,
name: name.clone(),
purpose: "Test purpose".to_string(),
inputs: vec![],
outputs: Parameter {
name: "result".to_string(),
param_type: "()".to_string(),
description: None,
},
};
let code = builder.build_function(&spec).unwrap();
prop_assert!(
code.contains(&format!("pub fn {}", name)),
"Generated code should contain function name: {}",
name
);
}
}
}