From 169e6a8625e0c49d75a7229f939a227ffba9e147 Mon Sep 17 00:00:00 2001 From: Arthur Welf Date: Thu, 16 Jan 2025 20:14:10 +0100 Subject: [PATCH] Add --no-function-bodies option --- src/main.rs | 110 +++++++------------ src/processor.rs | 79 +++++++++----- src/test_utils.rs | 4 +- src/transformer.rs | 260 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 328 insertions(+), 125 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0ebdb9b..4d66f2b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,11 @@ struct Cli { #[arg(long)] no_comments: bool, - /// Print processing statistics + /// Remove function bodies except for string/serialization methods + #[arg(long)] + no_function_bodies: bool, + + /// Don't print processing statistics #[arg(long)] no_stats: bool, @@ -63,14 +67,18 @@ fn main() -> Result<()> { } fn create_processor(cli: &Cli) -> impl Processor { - FileProcessor::with_options(cli.no_comments, cli.dry_run, cli.single_file) + FileProcessor::with_options( + cli.no_comments, + cli.no_function_bodies, + cli.dry_run, + cli.single_file, + ) } #[cfg(test)] mod tests { use super::*; use std::fs; - use std::io::{self, Write}; use tempfile::TempDir; #[test] @@ -126,6 +134,7 @@ mod tests { input_path: PathBuf::from("test"), output_dir_name: None, no_comments: true, + no_function_bodies: false, no_stats: false, dry_run: true, single_file: true, @@ -164,41 +173,6 @@ mod tests { Ok(()) } - #[test] - fn test_main_with_stats_output() -> Result<()> { - let temp_dir = TempDir::new()?; - let test_file = temp_dir.path().join("test.rs"); - fs::write(&test_file, "fn main() { println!(\"test\"); }")?; - - // Capture stdout - let stdout = io::stdout(); - let mut handle = stdout.lock(); - - let args = vec!["program", test_file.to_str().unwrap(), "--dry-run"]; - let cli = Cli::try_parse_from(args)?; - assert!(!cli.no_stats, "Stats should be enabled by default"); - - let processor = create_processor(&cli); - let stats = processor.process_path(&cli.input_path, cli.output_dir_name.as_deref())?; - - writeln!(handle, "\nProcessing Statistics:")?; - writeln!(handle, "Files processed: {}", stats.files_processed)?; - writeln!(handle, "Total input size: {} bytes", stats.input_size)?; - writeln!(handle, "Total output size: {} bytes", stats.output_size)?; - writeln!( - handle, - "Size reduction: {:.1}%", - stats.reduction_percentage() - )?; - - assert!(stats.files_processed > 0); - assert!(stats.input_size > 0); - assert!(stats.output_size > 0); - assert!(stats.reduction_percentage() > 0.0); - - Ok(()) - } - #[test] fn test_main_with_output_dir() -> Result<()> { let temp_dir = TempDir::new()?; @@ -262,44 +236,35 @@ mod tests { } #[test] - fn test_main_with_stats_display() -> Result<()> { + fn test_main_with_stats_output() -> Result<()> { let temp_dir = TempDir::new()?; let test_file = temp_dir.path().join("test.rs"); - fs::write(&test_file, "fn main() { println!(\"test\"); }")?; - - // Capture stdout - let mut output = Vec::new(); - { - let cli = Cli { - input_path: test_file.clone(), - output_dir_name: None, - no_comments: false, - no_stats: false, - dry_run: true, - single_file: false, - }; - - let processor = create_processor(&cli); - let stats = processor.process_path(&cli.input_path, cli.output_dir_name.as_deref())?; - - writeln!(output, "\nProcessing Statistics:")?; - writeln!(output, "Files processed: {}", stats.files_processed)?; - writeln!(output, "Total input size: {} bytes", stats.input_size)?; - writeln!(output, "Total output size: {} bytes", stats.output_size)?; - writeln!( - output, - "Size reduction: {:.1}%", - stats.reduction_percentage() - )?; - } + fs::write( + &test_file, + r#" + fn main() { + println!("Starting program"); + let mut sum = 0; + for i in 0..100 { + sum += i; + println!("Current sum: {}", sum); + } + println!("Final sum: {}", sum); + } + "#, + )?; - let output_str = String::from_utf8(output)?; - assert!(output_str.contains("Processing Statistics:")); - assert!(output_str.contains("Files processed:")); - assert!(output_str.contains("Total input size:")); - assert!(output_str.contains("Total output size:")); - assert!(output_str.contains("Size reduction:")); + let args = vec![ + "program", + test_file.to_str().unwrap(), + "--no-comments", + "--no-function-bodies", + ]; + let cli = Cli::try_parse_from(args)?; + let stats = + create_processor(&cli).process_path(&cli.input_path, cli.output_dir_name.as_deref())?; + assert!(stats.reduction_percentage() > 0.0); Ok(()) } @@ -316,6 +281,7 @@ mod tests { input_path: test_file, output_dir_name: Some("test-output".to_string()), no_comments: true, + no_function_bodies: false, no_stats: true, dry_run: true, single_file: false, diff --git a/src/processor.rs b/src/processor.rs index ff157f9..6dace90 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -31,6 +31,7 @@ pub trait Processor { fn dry_run(&self) -> bool; fn single_file(&self) -> bool; fn no_comments(&self) -> bool; + fn no_function_body(&self) -> bool; fn process_file(&self, input: &Path, output: &Path) -> Result<(usize, usize)>; fn process_directory_to_single_file( @@ -72,7 +73,7 @@ pub trait Processor { } let mut analyzer = RustAnalyzer::new(&content)?; - let mut transformer = CodeTransformer::new(self.no_comments()); + let mut transformer = CodeTransformer::new(self.no_comments(), self.no_function_body()); transformer.visit_file_mut(&mut analyzer.ast); let processed_content = prettyplease::unparse(&analyzer.ast); @@ -236,14 +237,21 @@ pub trait Processor { pub struct FileProcessor { no_comments: bool, + no_function_bodies: bool, dry_run: bool, single_file: bool, } impl FileProcessor { - pub fn with_options(no_comments: bool, dry_run: bool, single_file: bool) -> Self { + pub fn with_options( + no_comments: bool, + no_function_bodies: bool, + dry_run: bool, + single_file: bool, + ) -> Self { Self { no_comments, + no_function_bodies, dry_run, single_file, } @@ -263,6 +271,10 @@ impl Processor for FileProcessor { self.no_comments } + fn no_function_body(&self) -> bool { + self.no_function_bodies + } + fn process_file(&self, input: &Path, output: &Path) -> Result<(usize, usize)> { // Verify input file exists before trying to read it if !input.try_exists()? { @@ -284,7 +296,7 @@ impl Processor for FileProcessor { } let mut analyzer = RustAnalyzer::new(&content)?; - let mut transformer = CodeTransformer::new(self.no_comments); + let mut transformer = CodeTransformer::new(self.no_comments(), self.no_function_body()); transformer.visit_file_mut(&mut analyzer.ast); @@ -319,7 +331,7 @@ mod tests { let test_file = temp_dir.path().join("test.rs"); fs::write(&test_file, "fn main() {}")?; - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let stats = processor.process_path(&test_file, Some("output"))?; assert_eq!(stats.files_processed, 1); @@ -343,7 +355,7 @@ mod tests { "pub fn add(a: i32, b: i32) -> i32 { a + b }", )?; - let processor = FileProcessor::with_options(false, false, true); + let processor = FileProcessor::with_options(false, false, false, true); let output_dir = temp_dir.path().join("output"); let stats = processor.process_directory_to_single_file(input_dir, &output_dir)?; @@ -370,7 +382,7 @@ mod tests { "pub fn add(a: i32, b: i32) -> i32 { a + b }", )?; - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let output_dir = temp_dir.path().join("output"); let stats = processor.process_directory(input_dir, &output_dir)?; @@ -447,7 +459,7 @@ mod tests { fn test_invalid_input_path() -> Result<()> { let temp_dir = TempDir::new()?; let invalid_path = temp_dir.path().join("nonexistent"); - let processor = FileProcessor::with_options(false, true, false); + let processor = FileProcessor::with_options(false, true, false, false); let result = processor.process_path(&invalid_path, None); assert!(result.is_err()); @@ -458,7 +470,7 @@ mod tests { #[test] fn test_processor_options() { - let processor = FileProcessor::with_options(true, true, true); + let processor = FileProcessor::with_options(true, true, true, true); assert!(processor.no_comments()); assert!(processor.dry_run()); assert!(processor.single_file()); @@ -467,7 +479,7 @@ mod tests { #[test] fn test_process_directory_empty() -> Result<()> { let temp_dir = TempDir::new()?; - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let stats = processor.process_directory(temp_dir.path(), temp_dir.path())?; assert_eq!(stats.files_processed, 0); Ok(()) @@ -499,7 +511,7 @@ mod tests { } fn get_result_number(&self) -> Result {} }"#; - assert_eq!(process_code(input, false)?.trim(), expected.trim()); + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); Ok(()) } @@ -519,7 +531,7 @@ impl MyStruct { Cow::Borrowed("test") } }"#; - assert_eq!(process_code(input, false)?.trim(), expected.trim()); + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); Ok(()) } @@ -537,7 +549,7 @@ impl MyStruct { impl MyStruct { fn derived_method(&self) -> String {} }"#; - assert_eq!(process_code(input, false)?.trim(), expected.trim()); + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); Ok(()) } @@ -545,7 +557,15 @@ impl MyStruct { fn test_main_full_workflow() -> Result<()> { let temp_dir = TempDir::new()?; let test_file = temp_dir.path().join("test.rs"); - fs::write(&test_file, "fn main() { println!(\"test\"); }")?; + fs::write( + &test_file, + r#"fn main() { + println!("test"); + println!("more code to increase size"); + let x = 42; + println!("The answer is {}", x); + }"#, + )?; let args = vec![ "program", @@ -598,7 +618,7 @@ impl MyStruct { "#, )?; - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let output_dir = temp_dir.path().join("output"); let stats = processor.process_directory(&src_dir, &output_dir)?; @@ -638,7 +658,7 @@ impl MyStruct { "#, )?; - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let output_dir = temp_dir.path().join("output"); let stats = processor.process_directory(&src_dir, &output_dir)?; @@ -672,7 +692,7 @@ impl MyStruct { )?; // Test with comments preserved - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let output_dir = temp_dir.path().join("output-with-comments"); processor.process_directory(&src_dir, &output_dir)?; @@ -681,7 +701,7 @@ impl MyStruct { assert!(content.contains("/// Function documentation")); // Test with comments removed - let processor = FileProcessor::with_options(true, false, false); + let processor = FileProcessor::with_options(true, false, false, false); let output_dir = temp_dir.path().join("output-no-comments"); processor.process_directory(&src_dir, &output_dir)?; @@ -701,14 +721,21 @@ impl MyStruct { // Create multiple source files fs::write( src_dir.join("main.rs"), - r#"fn main() { println!("main"); }"#, + r#"fn main() { + println!("Hello"); + println!("This is a much longer function"); + let x = 42; + let y = x * 2; + println!("The answer is: {}", x); + println!("Double the answer is: {}", y); + }"#, )?; fs::write( src_dir.join("lib.rs"), r#"pub fn lib_function() { println!("lib"); }"#, )?; - let processor = FileProcessor::with_options(false, false, true); + let processor = FileProcessor::with_options(false, false, false, false); let output_dir = temp_dir.path().join("output"); let stats = processor.process_directory_to_single_file(&src_dir, &output_dir)?; @@ -728,7 +755,7 @@ impl MyStruct { let temp_dir = TempDir::new()?; let nonexistent_parent = temp_dir.path().join("nonexistent").join("test.rs"); - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let result = processor.process_path(&nonexistent_parent, None); assert!(result.is_err()); @@ -744,7 +771,7 @@ impl MyStruct { fs::write(temp_dir.path().join("test.txt"), "not rust")?; fs::write(temp_dir.path().join("test.rs.txt"), "not rust module")?; - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let stats = processor.process_directory(temp_dir.path(), temp_dir.path())?; // Should skip non-rust and .rs.txt files @@ -758,7 +785,7 @@ impl MyStruct { let rust_file = temp_dir.path().join("test.rs"); fs::write(&rust_file, "invalid rust code @#$%")?; - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let result = processor.process_directory(temp_dir.path(), temp_dir.path()); assert!(result.is_err()); @@ -781,7 +808,7 @@ impl MyStruct { let output_path = temp_dir.path().join("output"); fs::write(&output_path, "blocking file")?; - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let result = processor.process_directory(&src_dir, &output_path); assert!(result.is_err()); @@ -796,7 +823,7 @@ impl MyStruct { fn test_process_directory_to_single_file_empty() -> Result<()> { let temp_dir = TempDir::new()?; - let processor = FileProcessor::with_options(false, false, true); + let processor = FileProcessor::with_options(false, false, false, false); let stats = processor.process_directory_to_single_file(temp_dir.path(), temp_dir.path())?; assert_eq!(stats.files_processed, 0); @@ -810,7 +837,7 @@ impl MyStruct { let temp_dir = TempDir::new()?; fs::write(temp_dir.path().join("test.rs"), "invalid rust @#$%")?; - let processor = FileProcessor::with_options(false, false, true); + let processor = FileProcessor::with_options(false, false, true, false); let result = processor.process_directory_to_single_file(temp_dir.path(), temp_dir.path()); assert!(result.is_err()); @@ -832,7 +859,7 @@ impl MyStruct { let output_file = temp_dir.path().join("output"); fs::create_dir(&output_file)?; - let processor = FileProcessor::with_options(false, false, false); + let processor = FileProcessor::with_options(false, false, false, false); let result = processor.process_file(&input_file, &output_file); assert!(result.is_err()); diff --git a/src/test_utils.rs b/src/test_utils.rs index 000a84a..21910cf 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -5,11 +5,11 @@ use anyhow::Result; #[cfg(test)] /// Helper function to process a string of Rust code -pub fn process_code(code: &str, no_comments: bool) -> Result { +pub fn process_code(code: &str, no_comments: bool, no_function_bodies: bool) -> Result { use syn::visit_mut::VisitMut; let analyzer = RustAnalyzer::new(code)?; - let mut transformer = CodeTransformer::new(no_comments); + let mut transformer = CodeTransformer::new(no_comments, no_function_bodies); let mut ast = analyzer.ast; transformer.visit_file_mut(&mut ast); diff --git a/src/transformer.rs b/src/transformer.rs index 3157a80..8ef8d0b 100644 --- a/src/transformer.rs +++ b/src/transformer.rs @@ -30,6 +30,8 @@ impl RustAnalyzer { if path_str.contains("String") || path_str.contains("str") || path_str.contains("Cow") + || path_str.contains("ToString") + || path_str.contains("Display") { return true; } @@ -62,12 +64,16 @@ impl RustAnalyzer { pub struct CodeTransformer { no_comments: bool, + no_function_bodies: bool, } impl CodeTransformer { /// Creates a new CodeTransformer instance - pub fn new(no_comments: bool) -> Self { - Self { no_comments } + pub fn new(no_comments: bool, no_function_bodies: bool) -> Self { + Self { + no_comments, + no_function_bodies, + } } /// Gets attributes from any Item type @@ -323,8 +329,10 @@ impl VisitMut for CodeTransformer { // Process function-level comments Self::process_attributes(&mut item_fn.attrs, self.no_comments); - // Replace with empty block - item_fn.block = parse_quote!({}); + // Only replace block if no_function_bodies is true and return type isn't string-like + if self.no_function_bodies && !Self::analyze_return_type(&item_fn.sig.output) { + item_fn.block = parse_quote!({}); + } } Item::Trait(item_trait) => { // Process trait-level comments @@ -338,6 +346,7 @@ impl VisitMut for CodeTransformer { // Then handle the default implementation if method.default.is_some() + && self.no_function_bodies && !Self::analyze_return_type(&method.sig.output) { method.default = Some(parse_quote!({})); @@ -361,8 +370,10 @@ impl VisitMut for CodeTransformer { if let ImplItem::Fn(method) = impl_item { Self::process_attributes(&mut method.attrs, self.no_comments); - if is_derived - || (!is_serialize && !Self::analyze_return_type(&method.sig.output)) + if self.no_function_bodies + && (is_derived + || (!is_serialize + && !Self::analyze_return_type(&method.sig.output))) { method.block = parse_quote!({}); } @@ -402,11 +413,11 @@ mod tests { } "#; - let result = process_code(input, false)?; - + // Test with no_function_bodies = true + let result = process_code(input, false, true)?; let expected = r#"fn add(a: i32, b: i32) -> i32 {}"#; - assert_eq!(result.trim(), expected.trim()); + Ok(()) } @@ -417,14 +428,19 @@ mod tests { fn to_string(&self) -> String { "test".to_string() } + + fn get_number(&self) -> i32 { + 42 + } } "#; let expected = r#"impl MyStruct { fn to_string(&self) -> String { "test".to_string() } + fn get_number(&self) -> i32 {} }"#; - assert_eq!(process_code(input, false)?.trim(), expected.trim()); + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); Ok(()) } @@ -440,6 +456,10 @@ mod tests { fn serialize(&self) -> String { serde_json::to_string(self).unwrap() } + + fn get_number(&self) -> i32 { + 42 + } } "#; let expected = r#"#[derive(Serialize)] @@ -450,8 +470,9 @@ impl MyStruct { fn serialize(&self) -> String { serde_json::to_string(self).unwrap() } + fn get_number(&self) -> i32 {} }"#; - assert_eq!(process_code(input, false)?.trim(), expected.trim()); + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); Ok(()) } @@ -481,7 +502,7 @@ impl MyStruct { } fn get_number(&self) -> Option {} }"#; - assert_eq!(process_code(input, false)?.trim(), expected.trim()); + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); Ok(()) } @@ -509,7 +530,7 @@ impl MyStruct { state.end() } }"#; - assert_eq!(process_code(input, false)?.trim(), expected.trim()); + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); Ok(()) } @@ -582,7 +603,7 @@ impl MyStruct { // Test with comments enabled for (input, expected) in test_cases { assert_eq!( - process_code(input, false)?.trim(), + process_code(input, false, true)?.trim(), expected.trim(), "Failed with comments enabled" ); @@ -605,7 +626,7 @@ impl MyStruct { }"#; assert_eq!( - process_code(no_comments_input, true)?.trim(), + process_code(no_comments_input, true, true)?.trim(), no_comments_expected.trim(), "Failed with comments disabled" ); @@ -641,11 +662,11 @@ struct MyStruct { }"#; assert_eq!( - process_code(input, false)?.trim(), + process_code(input, false, true)?.trim(), expected_with_comments.trim() ); assert_eq!( - process_code(input, true)?.trim(), + process_code(input, true, true)?.trim(), expected_no_comments.trim() ); Ok(()) @@ -679,11 +700,11 @@ struct MyStruct { }"#; assert_eq!( - process_code(input, false)?.trim(), + process_code(input, false, true)?.trim(), expected_with_comments.trim() ); assert_eq!( - process_code(input, true)?.trim(), + process_code(input, true, true)?.trim(), expected_no_comments.trim() ); Ok(()) @@ -716,11 +737,11 @@ struct MyStruct { }"#; assert_eq!( - process_code(input, false)?.trim(), + process_code(input, false, true)?.trim(), expected_with_comments.trim() ); assert_eq!( - process_code(input, true)?.trim(), + process_code(input, true, true)?.trim(), expected_no_comments.trim() ); Ok(()) @@ -757,16 +778,104 @@ struct MyStruct { }"#; assert_eq!( - process_code(input, false)?.trim(), + process_code(input, false, true)?.trim(), expected_with_comments.trim() ); assert_eq!( - process_code(input, true)?.trim(), + process_code(input, true, true)?.trim(), expected_no_comments.trim() ); Ok(()) } + #[test] + fn test_no_function_bodies_behavior() -> Result<()> { + let test_cases = vec![ + ( + // Case 1: Regular functions should have empty bodies + r#" + fn regular_function(x: i32) -> i32 { + x + 42 + } + "#, + r#"fn regular_function(x: i32) -> i32 {}"#, + ), + ( + // Case 2: String-returning functions should keep their bodies + r#" + fn string_function() -> String { + "hello".to_string() + } + "#, + r#"fn string_function() -> String { + "hello".to_string() +}"#, + ), + ( + // Case 3: Result functions should keep their bodies + r#" + fn result_string() -> Result { + Ok("success".to_string()) + } + "#, + r#"fn result_string() -> Result { + Ok("success".to_string()) +}"#, + ), + ( + // Case 4: Option<&str> functions should keep their bodies + r#" + fn optional_str() -> Option<&'static str> { + Some("maybe") + } + "#, + r#"fn optional_str() -> Option<&'static str> { + Some("maybe") +}"#, + ), + ( + // Case 5: Non-string Result functions should have empty bodies + r#" + fn result_number() -> Result { + Ok(42) + } + "#, + r#"fn result_number() -> Result {}"#, + ), + ( + // Case 6: Impl blocks with mixed methods + r#" + impl MyStruct { + fn get_number(&self) -> i32 { + 42 + } + + fn get_name(&self) -> String { + "test".to_string() + } + } + "#, + r#"impl MyStruct { + fn get_number(&self) -> i32 {} + fn get_name(&self) -> String { + "test".to_string() + } +}"#, + ), + ]; + + for (input, expected) in test_cases { + assert_eq!( + process_code(input, false, true)?.trim(), + expected.trim(), + "Failed for input:\n{}", + input + ); + } + + Ok(()) + } + #[test] fn test_mixed_comments_in_module() -> Result<()> { let input = r#" @@ -819,13 +928,114 @@ pub mod outer_module { }"#; assert_eq!( - process_code(input, false)?.trim(), + process_code(input, false, true)?.trim(), expected_with_comments.trim() ); assert_eq!( - process_code(input, true)?.trim(), + process_code(input, true, true)?.trim(), expected_no_comments.trim() ); Ok(()) } + + #[test] + fn test_no_function_bodies_impl() -> Result<()> { + let input = r#" + impl MyStruct { + fn regular_method(&self) -> i32 { + 42 + } + + fn string_method(&self) -> String { + "hello".to_string() + } + + fn option_string(&self) -> Option { + Some("hello".to_string()) + } + + fn result_string(&self) -> Result { + Ok("hello".to_string()) + } + } + "#; + + let expected = r#"impl MyStruct { + fn regular_method(&self) -> i32 {} + fn string_method(&self) -> String { + "hello".to_string() + } + fn option_string(&self) -> Option { + Some("hello".to_string()) + } + fn result_string(&self) -> Result { + Ok("hello".to_string()) + } +}"#; + + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); + Ok(()) + } + + #[test] + fn test_no_function_bodies_trait() -> Result<()> { + let input = r#" + trait MyTrait { + fn required_method(&self) -> i32; + + fn default_method(&self) -> i32 { + 42 + } + + fn string_method(&self) -> String { + "hello".to_string() + } + + fn option_string(&self) -> Option { + Some("hello".to_string()) + } + } + "#; + + let expected = r#"trait MyTrait { + /// This is a required method + fn required_method(&self) -> i32; + /// There is a default implementation + fn default_method(&self) -> i32 {} + /// There is a default implementation + fn string_method(&self) -> String { + "hello".to_string() + } + /// There is a default implementation + fn option_string(&self) -> Option { + Some("hello".to_string()) + } +}"#; + + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); + Ok(()) + } + + #[test] + fn test_no_function_bodies_derive() -> Result<()> { + let input = r#" + #[derive(Debug)] + struct MyStruct; + + impl Debug for MyStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "MyStruct") + } + } + "#; + + let expected = r#"#[derive(Debug)] +struct MyStruct; +impl Debug for MyStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {} +}"#; + + assert_eq!(process_code(input, false, true)?.trim(), expected.trim()); + Ok(()) + } }