diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 9efd28e0b9a2c..e123988861691 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -139,7 +139,7 @@ impl BenchmarkProject { // Clone the repository let repo_url = format!("https://github.com/{}/{}.git", config.org, config.repo); - clone_remote(&repo_url, root); + clone_remote(&repo_url, root, true); // Checkout specific revision if provided if !config.rev.is_empty() && config.rev != "main" && config.rev != "master" { diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs index 8ae05b1784509..684f14d275f10 100644 --- a/crates/fmt/src/state/mod.rs +++ b/crates/fmt/src/state/mod.rs @@ -120,7 +120,6 @@ impl CallStack { } pub(super) struct State<'sess, 'ast> { - debug: bool, pub(super) s: pp::Printer, ind: isize, @@ -204,7 +203,6 @@ impl<'sess> State<'sess, '_> { comments: Comments, ) -> Self { Self { - debug: false, s: pp::Printer::new( config.line_length, if matches!(config.style, IndentStyle::Tab) { @@ -369,20 +367,27 @@ impl State<'_, '_> { fn estimate_size(&self, span: Span) -> usize { if let Ok(snip) = self.sm.span_to_snippet(span) { - let (mut size, mut prev_needs_space) = (0, false); + let (mut size, mut first, mut prev_needs_space) = (0, true, false); + for line in snip.lines() { + let line = line.trim(); + if prev_needs_space { - // Previous line ended with a bracket and config with bracket spacing. - // Previous line ended with ',' a hardbreak or a space are required. - // Previous line ended with ';' a hardbreak is required. size += 1; + } else if !first + && let Some(c) = line.chars().next() + && matches!(c, '&' | '|' | '=' | '>' | '<' | '+' | '-' | '*' | '/' | '%' | '^') + { + // if the line starts with an operator, a space or a line break are required. + size += 1 } + first = false; // trim spaces before and after mixed comments let mut search = line; loop { if let Some((lhs, comment)) = search.split_once(r#"/*"#) { - size += lhs.trim().len() + 2; + size += lhs.trim_end().len() + 2; search = comment; } else if let Some((comment, rhs)) = search.split_once(r#"*/"#) { size += comment.len() + 2; @@ -393,10 +398,15 @@ impl State<'_, '_> { } } - prev_needs_space = (self.config.bracket_spacing - && (line.ends_with('(') || line.ends_with('{'))) - || line.ends_with(',') - || line.ends_with(';'); + // Next line requires a line break if this one: + // - ends with a bracket and fmt config forces bracket spacing. + // - ends with ',' a line break or a space are required. + // - ends with ';' a line break is required. + prev_needs_space = match line.chars().next_back() { + Some('(') | Some('{') => self.config.bracket_spacing, + Some(',') | Some(';') => true, + _ => false, + }; } return size; } diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs index 22abc24637200..7b88ed2cb4075 100644 --- a/crates/fmt/src/state/sol.rs +++ b/crates/fmt/src/state/sol.rs @@ -1246,9 +1246,8 @@ impl<'ast> State<'_, 'ast> { self.call_stack.add_precall(lhs_size); let is_simple_rhs = matches!(rhs.kind, ast::ExprKind::Lit(..) | ast::ExprKind::Ident(..)); - let is_chain = is_call_chain(&rhs.kind, false); - if (is_chain && overflows && fits_alone) || is_simple_rhs { + if (overflows && fits_alone) || is_simple_rhs { self.s.ibox(self.ind) } else { self.s.ibox(0) @@ -1267,7 +1266,7 @@ impl<'ast> State<'_, 'ast> { self.print_expr(rhs); self.end(); } - _ if (is_chain && overflows && fits_alone) || (is_simple_rhs && overflows) => { + _ if overflows && (fits_alone || is_simple_rhs) => { self.print_sep(Separator::Space); self.print_expr(rhs); } diff --git a/crates/fmt/testdata/OperatorExpressions/120.fmt.sol b/crates/fmt/testdata/OperatorExpressions/120.fmt.sol index 781523ca72e01..3b108a7f87398 100644 --- a/crates/fmt/testdata/OperatorExpressions/120.fmt.sol +++ b/crates/fmt/testdata/OperatorExpressions/120.fmt.sol @@ -49,8 +49,8 @@ function test_nested() { "string mismatch" ); - state.zeroForOne = IERC20(Currency.unwrap(state.poolKey1.currency0)) - == IERC20(Currency.unwrap(state.poolKey0.curerncy1)); + state.zeroForOne = + IERC20(Currency.unwrap(state.poolKey1.currency0)) == IERC20(Currency.unwrap(state.poolKey0.curerncy1)); coreAddresses.evc == address(0) && coreAddresses.protocolConfig == address(0) && coreAddresses.sequenceRegistry == address(0) && coreAddresses.balanceTracker == address(0) diff --git a/crates/fmt/testdata/ReprosCalls/110.fmt.sol b/crates/fmt/testdata/ReprosCalls/110.fmt.sol index c46a0b3e85281..444799afecd9d 100644 --- a/crates/fmt/testdata/ReprosCalls/110.fmt.sol +++ b/crates/fmt/testdata/ReprosCalls/110.fmt.sol @@ -115,5 +115,11 @@ contract Orchestrator { amount(), 0 /* nonce */ ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = + self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = + self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; } } diff --git a/crates/fmt/testdata/ReprosCalls/120.fmt.sol b/crates/fmt/testdata/ReprosCalls/120.fmt.sol index 7dabb2c84ae01..85fcbd1d4b923 100644 --- a/crates/fmt/testdata/ReprosCalls/120.fmt.sol +++ b/crates/fmt/testdata/ReprosCalls/120.fmt.sol @@ -105,5 +105,9 @@ contract Orchestrator { amount(), 0 /* nonce */ ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; } } diff --git a/crates/fmt/testdata/ReprosCalls/80.fmt.sol b/crates/fmt/testdata/ReprosCalls/80.fmt.sol index aa927d4a770cd..502092b227f7e 100644 --- a/crates/fmt/testdata/ReprosCalls/80.fmt.sol +++ b/crates/fmt/testdata/ReprosCalls/80.fmt.sol @@ -170,5 +170,11 @@ contract Orchestrator { amount(), 0 /* nonce */ ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 + - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 + - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; } } diff --git a/crates/fmt/testdata/ReprosCalls/original.sol b/crates/fmt/testdata/ReprosCalls/original.sol index 64456c939bbb8..c352a52e03793 100644 --- a/crates/fmt/testdata/ReprosCalls/original.sol +++ b/crates/fmt/testdata/ReprosCalls/original.sol @@ -111,5 +111,11 @@ contract Orchestrator { ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( address(fromToken()), amount(), 0 /* nonce */ ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = + self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 + - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; } } diff --git a/crates/forge/tests/cli/fmt_integration.rs b/crates/forge/tests/cli/fmt_integration.rs new file mode 100644 index 0000000000000..d79fbf37169ca --- /dev/null +++ b/crates/forge/tests/cli/fmt_integration.rs @@ -0,0 +1,29 @@ +use foundry_test_utils::util::ExtTester; + +/// Test `forge fmt` immutability. +/// TODO: make sure original fmt is not changed after projects format and rev available. +/// TODO: enable win test after fixed. +macro_rules! fmt_test { + ($name:ident, $org:expr, $repo:expr, $commit:expr) => { + #[test] + fn $name() { + let (_, mut cmd) = ExtTester::new($org, $repo, $commit).setup_forge_prj(false); + cmd.arg("fmt").assert_success(); + cmd.arg("--check").assert_success(); + } + }; +} + +fmt_test!(fmt_ithaca_account, "ithacaxyz", "account", "213c04ee1808784c18609607d85feba7730538fd"); + +fmt_test!(fmt_univ4_core, "Uniswap", "v4-core", "59d3ecf53afa9264a16bba0e38f4c5d2231f80bc"); + +fmt_test!( + fmt_evk_periphery, + "euler-xyz", + "evk-periphery", + "e41f2b9b7ed677ca03ff7bd7221a4e2fdd55504f" +); + +#[cfg(not(windows))] +fmt_test!(fmt_0x_settler, "0xProject", "0x-settler", "a388c8251ab6c4bedce1641b31027d7b1136daef"); diff --git a/crates/forge/tests/cli/install.rs b/crates/forge/tests/cli/install.rs index 9101670ab2dfa..d92834203aa82 100644 --- a/crates/forge/tests/cli/install.rs +++ b/crates/forge/tests/cli/install.rs @@ -450,7 +450,7 @@ Compiler run successful! async fn uni_v4_core_sync_foundry_lock() { let (prj, mut cmd) = ExtTester::new("Uniswap", "v4-core", "e50237c43811bd9b526eff40f26772152a42daba") - .setup_forge_prj(); + .setup_forge_prj(true); assert!(!prj.root().join(FOUNDRY_LOCK).exists()); @@ -504,7 +504,7 @@ async fn oz_contracts_sync_foundry_lock() { "openzeppelin-contracts", "840c974028316f3c8172c1b8e5ed67ad95e255ca", ) - .setup_forge_prj(); + .setup_forge_prj(true); assert!(!prj.root().join(FOUNDRY_LOCK).exists()); @@ -561,7 +561,7 @@ async fn correctly_sync_dep_with_multiple_version() { "sync-lockfile-multi-version-dep", "1ca47e73a168e54f8f7761862dbd0c603856c5c8", ) - .setup_forge_prj(); + .setup_forge_prj(true); assert!(!prj.root().join(FOUNDRY_LOCK).exists()); diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index c1b55defc9d3b..5af06cdc06ccb 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -34,4 +34,5 @@ mod version; mod ext_integration; mod fmt; +mod fmt_integration; mod test_optimizer; diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index b2da96b03a7c3..e0beba825d729 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -147,7 +147,7 @@ impl ExtTester { self } - pub fn setup_forge_prj(&self) -> (TestProject, TestCommand) { + pub fn setup_forge_prj(&self, recursive: bool) -> (TestProject, TestCommand) { let (prj, mut test_cmd) = setup_forge(self.name, self.style.clone()); // Export vyper and forge in test command - workaround for snekmate venom tests. @@ -170,7 +170,7 @@ impl ExtTester { // Clone the external repository. let repo_url = format!("https://github.com/{}/{}.git", self.org, self.name); let root = prj.root().to_str().unwrap(); - clone_remote(&repo_url, root); + clone_remote(&repo_url, root, recursive); // Checkout the revision. if self.rev.is_empty() { @@ -224,7 +224,7 @@ impl ExtTester { return; } - let (prj, mut test_cmd) = self.setup_forge_prj(); + let (prj, mut test_cmd) = self.setup_forge_prj(true); // Run installation command. self.run_install_commands(prj.root().to_str().unwrap()); @@ -423,9 +423,14 @@ pub fn get_vyper() -> Vyper { } /// Clones a remote repository into the specified directory. Panics if the command fails. -pub fn clone_remote(repo_url: &str, target_dir: &str) { +pub fn clone_remote(repo_url: &str, target_dir: &str, recursive: bool) { let mut cmd = Command::new("git"); - cmd.args(["clone", "--recursive", "--shallow-submodules"]); + cmd.args(["clone"]); + if recursive { + cmd.args(["--recursive", "--shallow-submodules"]); + } else { + cmd.args(["--depth=1", "--no-checkout", "--filter=blob:none", "--no-recurse-submodules"]); + } cmd.args([repo_url, target_dir]); test_debug!("{cmd:?}"); let status = cmd.status().unwrap();