mod python;
mod rules;
mod rust;
use rules::FormatterRules;
use super::{FileUnit, FunctionUnit, ImplUnit, ModuleUnit, StructUnit, TraitUnit, Visibility};
use crate::parser::LanguageType;
use crate::{BankStrategy, Result};
pub trait Formatter {
fn format(&self, strategy: &BankStrategy, language: LanguageType) -> Result<String>;
}
impl Formatter for FileUnit {
fn format(&self, strategy: &BankStrategy, language: LanguageType) -> Result<String> {
let mut output = String::new();
let rules = FormatterRules::for_language(language);
match strategy {
BankStrategy::Default => {
if let Some(source) = &self.source {
output.push_str(source);
}
}
BankStrategy::NoTests => {
if let Some(doc) = &self.doc {
output.push_str(&format!("{} {}\n", rules.doc_marker, doc));
}
for decl in &self.declares {
output.push_str(&decl.source);
output.push('\n');
}
for module in &self.modules {
if !rules.is_test_module(&module.name, &module.attributes) {
let formatted = module.format(strategy, language)?;
if !formatted.is_empty() {
output.push_str(&formatted);
output.push('\n');
}
}
}
for function in &self.functions {
if !rules.is_test_function(&function.attributes) {
let formatted = function.format(strategy, language)?;
if !formatted.is_empty() {
output.push_str(&formatted);
output.push('\n');
}
}
}
for struct_unit in &self.structs {
let formatted = struct_unit.format(strategy, language)?;
if !formatted.is_empty() {
output.push_str(&formatted);
output.push('\n');
}
}
for trait_unit in &self.traits {
let formatted = trait_unit.format(strategy, language)?;
if !formatted.is_empty() {
output.push_str(&formatted);
output.push('\n');
}
}
for impl_unit in &self.impls {
let formatted = impl_unit.format(strategy, language)?;
if !formatted.is_empty() {
output.push_str(&formatted);
output.push('\n');
}
}
}
BankStrategy::Summary => {
if let Some(doc) = &self.doc {
output.push_str(&format!("{} {}\n", rules.doc_marker, doc));
}
for decl in &self.declares {
output.push_str(&decl.source);
output.push('\n');
}
for module in &self.modules {
if module.visibility == Visibility::Public {
let module_formatted = module.format(strategy, language)?;
output.push_str(&module_formatted);
output.push('\n');
}
}
for function in &self.functions {
if function.visibility == Visibility::Public {
let function_formatted = function.format(strategy, language)?;
output.push_str(&function_formatted);
output.push('\n');
}
}
for struct_unit in &self.structs {
if struct_unit.visibility == Visibility::Public {
let struct_formatted = struct_unit.format(strategy, language)?;
output.push_str(&struct_formatted);
output.push('\n');
}
}
for trait_unit in &self.traits {
if trait_unit.visibility == Visibility::Public {
let trait_formatted = trait_unit.format(strategy, language)?;
output.push_str(&trait_formatted);
output.push('\n');
}
}
for impl_unit in &self.impls {
let impl_formatted = impl_unit.format(strategy, language)?;
output.push_str(&impl_formatted);
output.push('\n');
}
}
}
Ok(output)
}
}
impl Formatter for ModuleUnit {
fn format(&self, strategy: &BankStrategy, language: LanguageType) -> Result<String> {
let mut output = String::new();
let rules = FormatterRules::for_language(language);
if *strategy == BankStrategy::Summary && rules.is_test_module(&self.name, &self.attributes)
{
return Ok(String::new());
}
match strategy {
BankStrategy::Default => {
if let Some(source) = &self.source {
output.push_str(source);
}
}
BankStrategy::NoTests => {
if let Some(doc) = &self.doc {
for line in doc.lines() {
output.push_str(&format!("{} {}\n", rules.doc_marker, line));
}
}
for attr in &self.attributes {
output.push_str(&format!("{}\n", attr));
}
output.push_str(&format!(
"{} mod {} {{\n",
self.visibility.as_str(language),
self.name
));
for decl in &self.declares {
output.push_str(&format!(" {}\n", decl.source));
}
for function in &self.functions {
if !rules.is_test_function(&function.attributes) {
let function_formatted = function.format(strategy, language)?;
if !function_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
function_formatted.replace("\n", "\n ")
));
}
}
}
for struct_unit in &self.structs {
let struct_formatted = struct_unit.format(strategy, language)?;
if !struct_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
struct_formatted.replace("\n", "\n ")
));
}
}
for trait_unit in &self.traits {
let trait_formatted = trait_unit.format(strategy, language)?;
if !trait_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
trait_formatted.replace("\n", "\n ")
));
}
}
for impl_unit in &self.impls {
let impl_formatted = impl_unit.format(strategy, language)?;
if !impl_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
impl_formatted.replace("\n", "\n ")
));
}
}
for submodule in &self.submodules {
let sub_formatted = submodule.format(strategy, language)?;
if !sub_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
sub_formatted.replace("\n", "\n ")
));
}
}
output.push_str("}\n");
}
BankStrategy::Summary => {
if self.visibility == Visibility::Public {
let fns: Vec<&FunctionUnit> = self
.functions
.iter()
.filter(|f| f.visibility == Visibility::Public)
.collect();
let structs: Vec<&StructUnit> = self
.structs
.iter()
.filter(|s| s.visibility == Visibility::Public)
.collect();
let traits: Vec<&TraitUnit> = self
.traits
.iter()
.filter(|t| t.visibility == Visibility::Public)
.collect();
let impls: Vec<&ImplUnit> = self
.impls
.iter()
.filter(|i| i.methods.iter().any(|m| m.visibility == Visibility::Public))
.collect();
let mods: Vec<&ModuleUnit> = self
.submodules
.iter()
.filter(|m| m.visibility == Visibility::Public)
.collect();
if fns.is_empty()
&& structs.is_empty()
&& traits.is_empty()
&& impls.is_empty()
&& mods.is_empty()
{
return Ok(String::new());
}
if let Some(doc) = &self.doc {
for line in doc.lines() {
output.push_str(&format!("{} {}\n", rules.doc_marker, line));
}
}
for attr in &self.attributes {
if !rules.test_module_markers.contains(&attr.as_str()) {
output.push_str(&format!("{}\n", attr));
}
}
output.push_str(&format!("pub mod {} {{\n", self.name));
for decl in &self.declares {
output.push_str(&format!(" {}\n", decl.source));
}
for function in &fns {
if !rules.is_test_function(&function.attributes) {
let function_formatted = function.format(strategy, language)?;
if !function_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
function_formatted.replace("\n", "\n ")
));
}
}
}
for struct_unit in &structs {
let struct_formatted = struct_unit.format(strategy, language)?;
if !struct_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
struct_formatted.replace("\n", "\n ")
));
}
}
for trait_unit in &traits {
let trait_formatted = trait_unit.format(strategy, language)?;
if !trait_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
trait_formatted.replace("\n", "\n ")
));
}
}
for impl_unit in &impls {
let impl_formatted = impl_unit.format(strategy, language)?;
if !impl_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
impl_formatted.replace("\n", "\n ")
));
}
}
for submodule in &mods {
let sub_formatted = submodule.format(strategy, language)?;
if !sub_formatted.is_empty() {
output.push_str(&format!(
" {}\n\n",
sub_formatted.replace("\n", "\n ")
));
}
}
output.push_str("}\n");
}
}
}
Ok(output)
}
}
impl Formatter for FunctionUnit {
fn format(&self, strategy: &BankStrategy, language: LanguageType) -> Result<String> {
let mut output = String::new();
let rules = FormatterRules::for_language(language);
if *strategy == BankStrategy::Default {
return Ok(self.source.clone().unwrap_or_default());
}
if rules.is_test_function(&self.attributes) {
return Ok(String::new());
}
if *strategy == BankStrategy::Summary && self.visibility != Visibility::Public {
return Ok(String::new());
}
if let Some(doc) = &self.doc {
for line in doc.lines() {
output.push_str(&format!("{} {}\n", rules.doc_marker, line));
}
}
for attr in &self.attributes {
if !rules.test_markers.contains(&attr.as_str()) {
output.push_str(&format!("{}\n", attr));
}
}
match strategy {
BankStrategy::Default => { }
BankStrategy::NoTests => {
if let Some(sig) = &self.signature {
output.push_str(sig);
}
if let Some(body) = &self.body {
if self.signature.is_some()
&& !output.ends_with(' ')
&& !body.starts_with('{')
&& !body.starts_with(':')
{
output.push(' ');
}
output.push_str(body);
} else if self.signature.is_none() {
if let Some(src) = &self.source {
output.push_str(src);
}
}
}
BankStrategy::Summary => {
if let Some(signature) = &self.signature {
let formatted_sig = rules.format_signature(signature, Some(signature));
output.push_str(&formatted_sig);
} else if let Some(source) = &self.source {
let formatted_sig = rules.format_signature(source, None);
output.push_str(&formatted_sig);
}
}
}
Ok(output)
}
}
impl Formatter for StructUnit {
fn format(&self, strategy: &BankStrategy, language: LanguageType) -> Result<String> {
let mut output = String::new();
let rules = FormatterRules::for_language(language);
if *strategy == BankStrategy::Summary && self.visibility != Visibility::Public {
return Ok(String::new());
}
if let Some(doc) = &self.doc {
for line in doc.lines() {
output.push_str(&format!("{} {}\n", rules.doc_marker, line));
}
}
for attr in &self.attributes {
output.push_str(&format!("{}\n", attr));
}
match strategy {
BankStrategy::Default | BankStrategy::NoTests => {
if let Some(source) = &self.source {
output.push_str(source);
}
}
BankStrategy::Summary => {
output.push_str(&self.head);
output.push_str(rules.function_body_start_marker);
output.push('\n');
for field in &self.fields {
output.push_str(&format!(
" {}{}\n",
field.source.as_deref().unwrap_or(""),
rules.field_sep
));
}
output.push_str(rules.function_body_end_marker);
for method in &self.methods {
if method.visibility == Visibility::Public
&& !rules.is_test_function(&method.attributes)
{
let method_formatted = method.format(strategy, language)?;
if !method_formatted.is_empty() {
output.push_str(" ");
output.push_str(&method_formatted.replace("\n", "\n "));
output.push('\n');
}
}
}
}
}
Ok(output)
}
}
impl Formatter for TraitUnit {
fn format(&self, strategy: &BankStrategy, language: LanguageType) -> Result<String> {
let mut output = String::new();
let rules = FormatterRules::for_language(language);
if *strategy == BankStrategy::Summary && self.visibility != Visibility::Public {
return Ok(String::new());
}
if let Some(doc) = &self.doc {
for line in doc.lines() {
output.push_str(&format!("{} {}\n", rules.doc_marker, line));
}
}
for attr in &self.attributes {
output.push_str(&format!("{}\n", attr));
}
match strategy {
BankStrategy::Default => {
if let Some(source) = &self.source {
output.push_str(source);
}
}
BankStrategy::NoTests | BankStrategy::Summary => {
let head = format!("{} trait {}", self.visibility.as_str(language), self.name);
output.push_str(&head);
if *strategy == BankStrategy::NoTests {
output.push_str(" {\n");
for method in &self.methods {
if !rules.is_test_function(&method.attributes) {
let method_formatted = method.format(strategy, language)?;
if !method_formatted.is_empty() {
output.push_str(" ");
output.push_str(&method_formatted.replace("\n", "\n "));
output.push('\n');
}
}
}
output.push_str(rules.function_body_end_marker);
} else {
output.push_str(rules.summary_ellipsis);
}
}
}
Ok(output)
}
}
impl Formatter for ImplUnit {
fn format(&self, strategy: &BankStrategy, language: LanguageType) -> Result<String> {
let mut output = String::new();
let rules = FormatterRules::for_language(language);
let is_trait_impl = self.head.contains(" for ");
let methods_to_include: Vec<&FunctionUnit> = match strategy {
BankStrategy::Default => self.methods.iter().collect(),
BankStrategy::NoTests => self
.methods
.iter()
.filter(|m| !rules.is_test_function(&m.attributes))
.collect(),
BankStrategy::Summary => {
if is_trait_impl {
self.methods
.iter()
.filter(|m| !rules.is_test_function(&m.attributes))
.collect()
} else {
self.methods
.iter()
.filter(|m| {
m.visibility == Visibility::Public
&& !rules.is_test_function(&m.attributes)
})
.collect()
}
}
};
if methods_to_include.is_empty() && *strategy == BankStrategy::Summary && !is_trait_impl {
return Ok(String::new());
}
if let Some(doc) = &self.doc {
for line in doc.lines() {
output.push_str(&format!("{} {}\n", rules.doc_marker, line));
}
}
for attr in &self.attributes {
output.push_str(&format!("{}\n", attr));
}
match strategy {
BankStrategy::Default => {
if let Some(source) = &self.source {
output.push_str(source);
}
}
BankStrategy::NoTests | BankStrategy::Summary => {
output.push_str(&self.head);
output.push_str(" {\n");
for method in methods_to_include {
let method_formatted = method.format(strategy, language)?;
if !method_formatted.is_empty() {
output.push_str(" ");
output.push_str(&method_formatted.replace("\n", "\n "));
output.push('\n');
}
}
output.push_str(rules.function_body_end_marker);
}
}
Ok(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::Visibility;
#[test]
fn test_function_unit_format() {
let function = FunctionUnit {
name: "test_function".to_string(),
visibility: Visibility::Public,
doc: Some("Test function documentation".to_string()),
signature: Some("fn test_function()".to_string()),
body: Some("{ println!(\"test\"); }".to_string()),
source: Some("fn test_function() { println!(\"test\"); }".to_string()),
attributes: vec!["#[test]".to_string()],
};
let expected_source = function.source.clone().unwrap();
let result_default = function
.format(&BankStrategy::Default, LanguageType::Rust)
.unwrap();
assert_eq!(result_default, expected_source);
let result_no_tests = function
.format(&BankStrategy::NoTests, LanguageType::Rust)
.unwrap();
assert_eq!(result_no_tests, "");
let result_summary = function
.format(&BankStrategy::Summary, LanguageType::Rust)
.unwrap();
assert_eq!(result_summary, "");
let regular_function = FunctionUnit {
name: "regular_function".to_string(),
visibility: Visibility::Public,
doc: Some("Regular function documentation".to_string()),
signature: Some("pub fn regular_function() -> bool".to_string()),
body: Some("{ true }".to_string()),
source: Some("pub fn regular_function() -> bool { true }".to_string()),
attributes: vec![],
};
let regular_source = regular_function.source.clone().unwrap();
let regular_sig = regular_function.signature.clone().unwrap();
let rules = FormatterRules::for_language(LanguageType::Rust);
let result_default_regular = regular_function
.format(&BankStrategy::Default, LanguageType::Rust)
.unwrap();
assert_eq!(result_default_regular, regular_source);
let result_no_tests_regular = regular_function
.format(&BankStrategy::NoTests, LanguageType::Rust)
.unwrap();
assert!(result_no_tests_regular.contains("Regular function documentation"));
assert!(result_no_tests_regular.contains("pub fn regular_function() -> bool"));
assert!(result_no_tests_regular.contains("{ true }"));
let result_summary_regular = regular_function
.format(&BankStrategy::Summary, LanguageType::Rust)
.unwrap();
assert!(result_summary_regular.contains("Regular function documentation"));
assert!(
result_summary_regular
.contains(&rules.format_signature(®ular_sig, Some(®ular_sig)))
);
assert!(!result_summary_regular.contains("{ true }")); }
#[test]
fn test_module_unit_format() {
let test_module = ModuleUnit {
name: "test_module".to_string(),
visibility: Visibility::Public,
doc: Some("Test module documentation".to_string()),
source: Some(
"/// Test module documentation\n#[cfg(test)]\nmod test_module {".to_string(),
),
attributes: vec!["#[cfg(test)]".to_string()],
functions: vec![],
structs: vec![],
traits: vec![],
impls: vec![],
submodules: vec![],
declares: vec![],
};
let expected_test_source = test_module.source.clone().unwrap();
let result_default_test = test_module
.format(&BankStrategy::Default, LanguageType::Rust)
.unwrap();
assert_eq!(result_default_test, expected_test_source);
let result_no_tests_test = test_module
.format(&BankStrategy::NoTests, LanguageType::Rust)
.unwrap();
assert!(result_no_tests_test.contains("mod test_module")); assert!(result_no_tests_test.contains("#[cfg(test)]"));
let result_summary_test = test_module
.format(&BankStrategy::Summary, LanguageType::Rust)
.unwrap();
assert_eq!(result_summary_test, "");
let regular_module = ModuleUnit {
name: "regular_module".to_string(),
visibility: Visibility::Public,
doc: Some("Regular module documentation".to_string()),
source: Some("/// Regular module documentation\nmod regular_module {}".to_string()),
attributes: vec![],
functions: vec![],
structs: vec![],
traits: vec![],
impls: vec![],
submodules: vec![],
declares: vec![],
};
let result = regular_module
.format(&BankStrategy::Default, LanguageType::Rust)
.unwrap();
assert!(result.contains("Regular module documentation"));
assert!(result.contains("mod regular_module {}"));
let result = regular_module
.format(&BankStrategy::Summary, LanguageType::Rust)
.unwrap();
assert!(!result.contains("mod regular_module"));
}
#[test]
fn test_struct_unit_format() {
let struct_unit = StructUnit {
name: "TestStruct".to_string(),
head: "pub struct TestStruct".to_string(),
visibility: Visibility::Public,
doc: Some("Test struct documentation".to_string()),
attributes: vec![],
methods: vec![],
fields: Vec::new(),
source: Some("/// Test struct documentation\npub struct TestStruct {}".to_string()),
};
let result = struct_unit
.format(&BankStrategy::Default, LanguageType::Rust)
.unwrap();
assert!(result.contains("Test struct documentation"));
assert!(result.contains("pub struct TestStruct"));
let result = struct_unit
.format(&BankStrategy::Summary, LanguageType::Rust)
.unwrap();
println!("{}", result);
assert!(result.contains("pub struct TestStruct"));
}
#[test]
fn test_trait_unit_format() {
let trait_unit = TraitUnit {
name: "TestTrait".to_string(),
visibility: Visibility::Public,
doc: Some("Test trait documentation".to_string()),
source: Some("/// Test trait documentation\npub trait TestTrait {}".to_string()),
attributes: vec![],
methods: vec![],
};
let result = trait_unit
.format(&BankStrategy::Default, LanguageType::Rust)
.unwrap();
assert!(result.contains("Test trait documentation"));
assert!(result.contains("pub trait TestTrait"));
let result = trait_unit
.format(&BankStrategy::Summary, LanguageType::Rust)
.unwrap();
assert!(result.contains("pub trait TestTrait"));
}
#[test]
fn test_impl_unit_format() {
let impl_unit = ImplUnit {
head: "impl".to_string(),
doc: Some("Test impl documentation".to_string()),
source: Some("/// Test impl documentation\nimpl TestStruct {".to_string()),
attributes: vec![],
methods: vec![],
};
let result = impl_unit
.format(&BankStrategy::Default, LanguageType::Rust)
.unwrap();
println!("{}", result);
assert!(result.contains("Test impl documentation"));
assert!(result.contains("impl TestStruct {"));
let result = impl_unit
.format(&BankStrategy::Summary, LanguageType::Rust)
.unwrap();
assert!(!result.contains("impl TestStruct"));
}
#[test]
fn test_file_unit_format() {
let file_unit = FileUnit {
path: std::path::PathBuf::from("test.rs"),
doc: Some("Test file documentation".to_string()),
source: Some("/// Test file documentation".to_string()),
declares: vec![],
modules: vec![],
functions: vec![],
structs: vec![],
traits: vec![],
impls: vec![],
};
let result = file_unit
.format(&BankStrategy::Default, LanguageType::Rust)
.unwrap();
assert!(result.contains("Test file documentation"));
let result = file_unit
.format(&BankStrategy::Summary, LanguageType::Rust)
.unwrap();
assert!(result.contains("Test file documentation"));
}
}