diff --git a/scarb/src/bin/scarb/args.rs b/scarb/src/bin/scarb/args.rs index 19def7a65..d034d7f36 100644 --- a/scarb/src/bin/scarb/args.rs +++ b/scarb/src/bin/scarb/args.rs @@ -4,6 +4,7 @@ use std::collections::BTreeMap; use std::ffi::OsString; +use std::path::PathBuf; use anyhow::Result; use camino::Utf8PathBuf; @@ -341,6 +342,8 @@ pub struct FmtArgs { /// Specify package(s) to format. #[command(flatten)] pub packages_filter: PackagesFilter, + #[clap(value_name = "TARGET")] + pub target: Option, } /// Arguments accepted by the `add` command. diff --git a/scarb/src/bin/scarb/commands/fmt.rs b/scarb/src/bin/scarb/commands/fmt.rs index c06b9cb41..82b09a14c 100644 --- a/scarb/src/bin/scarb/commands/fmt.rs +++ b/scarb/src/bin/scarb/commands/fmt.rs @@ -29,6 +29,7 @@ pub fn run(args: FmtArgs, config: &Config) -> Result<()> { packages, action, color: !args.no_color, + target_file: args.target, }, &ws, )? { diff --git a/scarb/src/ops/fmt.rs b/scarb/src/ops/fmt.rs index f3123f541..9260c60da 100644 --- a/scarb/src/ops/fmt.rs +++ b/scarb/src/ops/fmt.rs @@ -34,6 +34,7 @@ pub struct FmtOptions { pub action: FmtAction, pub packages: Vec, pub color: bool, + pub target_file: Option, } #[tracing::instrument(skip_all, level = "debug")] @@ -42,6 +43,10 @@ pub fn format(opts: FmtOptions, ws: &Workspace<'_>) -> Result { let all_correct = AtomicBool::new(true); + if let Some(target_file) = &opts.target_file { + return format_single_file(target_file, &opts, ws, &all_correct); + } + for package_id in opts.packages.iter() { let package = ws.fetch_package(package_id)?; @@ -66,6 +71,28 @@ pub fn format(opts: FmtOptions, ws: &Workspace<'_>) -> Result { Ok(result) } +fn format_single_file( + target_file: &Path, + opts: &FmtOptions, + ws: &Workspace<'_>, + all_correct: &AtomicBool, +) -> Result { + let config = FormatterConfig::default(); + let fmt = CairoFormatter::new(config); + + let success = match &opts.action { + FmtAction::Fix => format_file_in_place(&fmt, opts, ws, target_file), + FmtAction::Check => check_file_formatting(&fmt, opts, ws, target_file), + FmtAction::Emit(target) => emit_formatted_file(&fmt, target, ws, target_file), + }; + + if !success { + all_correct.store(false, Ordering::Release); + } + + Ok(all_correct.load(Ordering::Acquire)) +} + struct PathFormatter<'t> { all_correct: &'t AtomicBool, opts: &'t FmtOptions, diff --git a/scarb/tests/fmt.rs b/scarb/tests/fmt.rs index 40352593a..5bbf2e69c 100644 --- a/scarb/tests/fmt.rs +++ b/scarb/tests/fmt.rs @@ -178,33 +178,33 @@ fn format_with_import_sorting() { .write_str(indoc! {"\ use openzeppelin::introspection::interface; use openzeppelin::introspection::first; - + #[starknet::contract] mod SRC5 { use openzeppelin::introspection::interface; use openzeppelin::introspection::{interface, AB}; - + #[storage] struct Storage { supported_interfaces: LegacyMap } - + use openzeppelin::introspection::first; - + mod A {} mod G; mod F; - + #[abi(embed_v0)] impl SRC5Impl of interface::ISRC5 { fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { true } } - + use A; use starknet::ArrayTrait; - + mod Inner { use C; use B; @@ -227,7 +227,7 @@ fn format_with_import_sorting() { +use openzeppelin::introspection::first; use openzeppelin::introspection::interface; -use openzeppelin::introspection::first; - + #[starknet::contract] mod SRC5 { + mod F; @@ -239,25 +239,25 @@ fn format_with_import_sorting() { use openzeppelin::introspection::interface; use openzeppelin::introspection::{interface, AB}; + use starknet::ArrayTrait; - + #[storage] struct Storage { @@ -11,11 +18,7 @@ supported_interfaces: LegacyMap } - + - use openzeppelin::introspection::first; - mod A {} - mod G; - mod F; - + #[abi(embed_v0)] impl SRC5Impl of interface::ISRC5 { @@ -24,11 +27,8 @@ } } - + - use A; - use starknet::ArrayTrait; - @@ -267,7 +267,7 @@ fn format_with_import_sorting() { - use B; } } - + "}); } @@ -398,3 +398,28 @@ fn workspace_emit_with_root() { let content = t.child("second/src/lib.cairo").read_to_string(); assert_eq!(content, SIMPLE_ORIGINAL); } + +#[test] +fn format_specific_file() { + let t = build_temp_dir(SIMPLE_ORIGINAL); + + // Create two files: one to be formatted and one to be left alone + t.child("src/lib.cairo").write_str(SIMPLE_ORIGINAL).unwrap(); + t.child("src/other.cairo").write_str(SIMPLE_ORIGINAL).unwrap(); + + // Format only the lib.cairo file + Scarb::quick_snapbox() + .arg("fmt") + .arg("src/lib.cairo") + .current_dir(&t) + .assert() + .success(); + + // Check that lib.cairo was formatted + let lib_content = t.child("src/lib.cairo").read_to_string(); + assert_eq!(lib_content, SIMPLE_FORMATTED); + + // Check that other.cairo was not formatted + let other_content = t.child("src/other.cairo").read_to_string(); + assert_eq!(other_content, SIMPLE_ORIGINAL); +}