diff --git a/crates/config/src/fmt.rs b/crates/config/src/fmt.rs index 673b692fbe4fd..50ba0ec123fc2 100644 --- a/crates/config/src/fmt.rs +++ b/crates/config/src/fmt.rs @@ -44,6 +44,8 @@ pub struct FormatterConfig { pub prefer_compact: PreferCompact, /// Keep single imports on a single line even if they exceed line length. pub single_line_imports: bool, + /// Style of condition formatting in if statements + pub format_conditions: ConditionFormatStyle, } /// Style of integer types. @@ -223,6 +225,17 @@ impl PreferCompact { } } +/// Style of condition formatting in if statements +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ConditionFormatStyle { + /// Inline all conditions on a single line + #[default] + Inline, + /// Place each condition on a separate line + Multi, +} + /// Style of indent #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -254,6 +267,7 @@ impl Default for FormatterConfig { prefer_compact: PreferCompact::default(), docs_style: DocCommentStyle::default(), single_line_imports: false, + format_conditions: ConditionFormatStyle::default(), } } } diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs index ada5eadd55bf0..b8dcbfd4eea01 100644 --- a/crates/fmt/src/state/mod.rs +++ b/crates/fmt/src/state/mod.rs @@ -132,6 +132,8 @@ pub(super) struct State<'sess, 'ast> { emit_or_revert: bool, // Whether inside a variable initialization expression, or not. var_init: bool, + // Whether inside an if condition expression, or not. + in_if_condition: bool, } impl std::ops::Deref for State<'_, '_> { @@ -226,6 +228,7 @@ impl<'sess> State<'sess, '_> { return_bin_expr: false, emit_or_revert: false, var_init: false, + in_if_condition: false, block_depth: 0, call_stack: CallStack::default(), } diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs index 52fb7fe0172f1..071a94fabe292 100644 --- a/crates/fmt/src/state/sol.rs +++ b/crates/fmt/src/state/sol.rs @@ -1424,6 +1424,11 @@ impl<'ast> State<'_, 'ast> { let prev_chain = self.binary_expr; let is_chain = prev_chain.is_some_and(|prev| prev == bin_op.kind.group()); + // Check if we should use multi-line formatting for if conditions + let use_multi_line_conditions = self.in_if_condition + && matches!(self.config.format_conditions, config::ConditionFormatStyle::Multi) + && matches!(bin_op.kind.group(), BinOpGroup::Logical); + // Opening box if starting a new operator chain. if !is_chain { self.binary_expr = Some(bin_op.kind.group()); @@ -1437,7 +1442,21 @@ impl<'ast> State<'_, 'ast> { } else { self.ind }; - self.s.ibox(indent); + // Use consistent box for multi-line condition formatting to ensure all breaks happen + // together + if use_multi_line_conditions { + self.s.cbox(indent); + } else { + self.s.ibox(indent); + } + } + + // For multi-line condition formatting, break before the operator (except the first one) + if use_multi_line_conditions && is_chain { + // Break before the operator to start each condition on its own line + if !self.is_bol_or_only_ind() { + self.hardbreak(); + } } // Print LHS. @@ -1469,7 +1488,14 @@ impl<'ast> State<'_, 'ast> { self.word(bin_op.kind.to_str()); - if !self.config.pow_no_space || !matches!(bin_op.kind, ast::BinOpKind::Pow) { + // For multi-line condition formatting, break after logical operators in if conditions + if use_multi_line_conditions { + // Force a hard break after logical operators in multi-line mode + if !self.is_bol_or_only_ind() { + self.hardbreak(); + } + self.s.offset(self.ind); + } else if !self.config.pow_no_space || !matches!(bin_op.kind, ast::BinOpKind::Pow) { self.nbsp(); } } @@ -2301,14 +2327,30 @@ impl<'ast> State<'_, 'ast> { fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) { self.print_word(kw); self.print_sep_unhandled(Separator::Nbsp); + + // Set flag for if condition formatting + let was_in_if_condition = self.in_if_condition; + self.in_if_condition = true; + + // Choose ListFormat based on config + let list_format = + if matches!(self.config.format_conditions, config::ConditionFormatStyle::Multi) { + ListFormat::consistent().break_cmnts().break_single(true) + } else { + ListFormat::compact().break_cmnts().break_single(is_binary_expr(&cond.kind)) + }; + self.print_tuple( std::slice::from_ref(cond), cond.span.lo(), pos_hi, Self::print_expr, get_span!(), - ListFormat::compact().break_cmnts().break_single(is_binary_expr(&cond.kind)), + list_format, ); + + // Reset flag + self.in_if_condition = was_in_if_condition; } fn print_emit_or_revert( diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 6af175ae15a6d..a2b346351c7a2 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -140,6 +140,7 @@ sort_imports = false pow_no_space = false prefer_compact = "all" single_line_imports = false +format_conditions = "inline" [lint] severity = [] @@ -1332,7 +1333,8 @@ forgetest_init!(test_default_config, |prj, cmd| { "sort_imports": false, "pow_no_space": false, "prefer_compact": "all", - "single_line_imports": false + "single_line_imports": false, + "format_conditions": "inline" }, "lint": { "severity": [],