diff --git a/cli/assets/generated/TestTemplate.t.sol b/cli/assets/generated/TestTemplate.t.sol new file mode 100644 index 0000000000000..468acba01069a --- /dev/null +++ b/cli/assets/generated/TestTemplate.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2} from "forge-std/Test.sol"; +import {{contract_name}} from "../src/{contract_name}.sol"; + +contract {contract_name}Test is Test { + {contract_name} public {instance_name}; + + function setUp() public { + {instance_name} = new {contract_name}(); + } +} \ No newline at end of file diff --git a/cli/src/cmd/forge/generate/mod.rs b/cli/src/cmd/forge/generate/mod.rs new file mode 100644 index 0000000000000..22929f421e562 --- /dev/null +++ b/cli/src/cmd/forge/generate/mod.rs @@ -0,0 +1,71 @@ +//! generate command + +use clap::{Parser, Subcommand}; +use foundry_common::fs; +use std::path::Path; +use yansi::Paint; + +/// CLI arguments for `forge generate`. +#[derive(Debug, Parser)] +pub struct GenerateArgs { + #[clap(subcommand)] + pub sub: GenerateSubcommands, +} + +#[derive(Debug, Subcommand)] +pub enum GenerateSubcommands { + /// Scaffolds test file for given contract. + Test(GenerateTestArgs), +} + +#[derive(Debug, Parser)] +pub struct GenerateTestArgs { + /// Contract name for test generation. + #[clap(long, short, value_name = "CONTRACT_NAME")] + pub contract_name: String, +} + +impl GenerateTestArgs { + pub fn run(self) -> eyre::Result<()> { + let contract_name = format_identifier(&self.contract_name, true); + let instance_name = format_identifier(&self.contract_name, false); + + // Create the test file content. + let test_content = include_str!("../../../../assets/generated/TestTemplate.t.sol"); + let test_content = test_content + .replace("{contract_name}", &contract_name) + .replace("{instance_name}", &instance_name); + + // Create the test directory if it doesn't exist. + fs::create_dir_all("test")?; + + // Define the test file path + let test_file_path = Path::new("test").join(format!("{}.t.sol", contract_name)); + + // Write the test content to the test file. + fs::write(&test_file_path, test_content)?; + + println!("{} test file: {}", Paint::green("Generated"), test_file_path.to_str().unwrap()); + Ok(()) + } +} + +/// Utility function to convert an identifier to pascal or camel case. +fn format_identifier(input: &str, is_pascal_case: bool) -> String { + let mut result = String::new(); + let mut capitalize_next = is_pascal_case; + + for word in input.split_whitespace() { + if !word.is_empty() { + let (first, rest) = word.split_at(1); + let formatted_word = if capitalize_next { + format!("{}{}", first.to_uppercase(), rest) + } else { + format!("{}{}", first.to_lowercase(), rest) + }; + capitalize_next = true; + result.push_str(&formatted_word); + } + } + result +} diff --git a/cli/src/cmd/forge/mod.rs b/cli/src/cmd/forge/mod.rs index 73e694d8bc334..5a1f508e04412 100644 --- a/cli/src/cmd/forge/mod.rs +++ b/cli/src/cmd/forge/mod.rs @@ -51,6 +51,7 @@ pub mod flatten; pub mod fmt; pub mod fourbyte; pub mod geiger; +pub mod generate; pub mod init; pub mod inspect; pub mod install; diff --git a/cli/src/forge.rs b/cli/src/forge.rs index 22f3cfec43a14..efcabad7dc368 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -2,7 +2,7 @@ use clap::{CommandFactory, Parser}; use clap_complete::generate; use foundry_cli::{ cmd::{ - forge::{cache::CacheSubcommands, watch}, + forge::{cache::CacheSubcommands, generate::GenerateSubcommands, watch}, Cmd, }, handler, @@ -97,5 +97,8 @@ fn main() -> eyre::Result<()> { } Subcommands::Doc(cmd) => cmd.run(), Subcommands::Selectors { command } => utils::block_on(command.run()), + Subcommands::Generate(cmd) => match cmd.sub { + GenerateSubcommands::Test(cmd) => cmd.run(), + }, } } diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index a7adf214f6111..391c6b783c1e4 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -9,7 +9,7 @@ use crate::cmd::forge::{ flatten, fmt::FmtArgs, fourbyte::UploadSelectorsArgs, - geiger, + geiger, generate, init::InitArgs, inspect, install::InstallArgs, @@ -163,6 +163,9 @@ pub enum Subcommands { #[clap(subcommand)] command: SelectorsSubcommands, }, + + /// Generate scaffold files. + Generate(generate::GenerateArgs), } // A set of solc compiler settings that can be set via command line arguments, which are intended