Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions crates/formatter/src/internal/format/binaryish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,12 @@ fn print_binaryish_expression_parts<'arena>(
None
};

let force_break = f.must_break_condition && line_before_operator;

let right_document = vec![
in f.arena;
if operator_has_leading_comments || (line_before_operator && !should_inline_this_level) {
Document::Line(if has_space_around { Line::default() } else { Line::soft() })
if force_break || operator_has_leading_comments || (line_before_operator && !should_inline_this_level) {
Document::Line(if force_break { Line::hard() } else if has_space_around { Line::default() } else { Line::soft() })
} else {
Document::String(if has_space_around { " " } else { "" })
},
Expand Down
15 changes: 12 additions & 3 deletions crates/formatter/src/internal/format/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,14 @@ pub(super) fn print_condition<'arena>(
right_parenthesis: Span,
) -> Document<'arena> {
let was_in_condition = f.in_condition;
let was_must_break_condition = f.must_break_condition;
f.in_condition = true;

let must_break = f.settings.preserve_breaking_condition_statement
&& has_new_line_in_range(f.source_text, left_parenthesis.end.offset, condition.span().start.offset);

f.must_break_condition = must_break;

let has_breaking_concat = match condition {
Expression::Call(call) => {
call.get_argument_list().arguments.iter().any(|arg| contains_breaking_concatenation(arg.value()))
Expand All @@ -646,6 +652,7 @@ pub(super) fn print_condition<'arena>(
let condition = if is_expandable_expression(condition, true)
&& !has_breaking_concat
&& !f.has_comment(condition.span(), CommentFlags::LEADING | CommentFlags::TRAILING)
&& !must_break
{
Document::Group(Group::new(vec![
in f.arena;
Expand All @@ -664,17 +671,19 @@ pub(super) fn print_condition<'arena>(
format_token(f, left_parenthesis, "("),
Document::IndentIfBreak(IndentIfBreak::new(group_id, vec![
in f.arena;
Document::Line(Line::soft()),
Document::Line(if must_break { Line::hard() } else { Line::soft() }),
condition.format(f),
])),
Document::Line(Line::soft()),
Document::Line(if must_break { Line::hard() } else { Line::soft() }),
format_token(f, right_parenthesis, ")"),
])
.with_id(group_id),
.with_id(group_id)
.with_break(must_break),
)
};

f.in_condition = was_in_condition;
f.must_break_condition = was_must_break_condition;

condition
}
2 changes: 2 additions & 0 deletions crates/formatter/src/internal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub struct FormatterState<'ctx, 'arena> {
parameter_state: ParameterState,
in_script_terminating_statement: bool,
in_condition: bool,
must_break_condition: bool,
is_wrapped_in_parens: bool,
is_in_inlined_binary_chain: bool,
halted_compilation: bool,
Expand Down Expand Up @@ -161,6 +162,7 @@ impl<'ctx, 'arena> FormatterState<'ctx, 'arena> {
argument_state: ArgumentState::default(),
parameter_state: ParameterState::default(),
in_condition: false,
must_break_condition: false,
is_wrapped_in_parens: false,
is_in_inlined_binary_chain: false,
halted_compilation: false,
Expand Down
6 changes: 6 additions & 0 deletions crates/formatter/src/presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const DEFAULT_PRESET: FormatSettings = FormatSettings {
preserve_breaking_parameter_list: false,
preserve_breaking_attribute_list: false,
preserve_breaking_conditional_expression: false,
preserve_breaking_condition_statement: false,
break_promoted_properties_list: true,
line_before_binary_operator: true,
always_break_named_arguments_list: false,
Expand Down Expand Up @@ -224,6 +225,7 @@ const PSR12_PRESET: FormatSettings = FormatSettings {
preserve_breaking_parameter_list: true,
preserve_breaking_attribute_list: false,
preserve_breaking_conditional_expression: false,
preserve_breaking_condition_statement: false,
break_promoted_properties_list: true,
line_before_binary_operator: true,
always_break_named_arguments_list: false,
Expand Down Expand Up @@ -307,6 +309,7 @@ const PINT_PRESET: FormatSettings = FormatSettings {
preserve_breaking_parameter_list: true,
preserve_breaking_attribute_list: true,
preserve_breaking_conditional_expression: true,
preserve_breaking_condition_statement: true,
break_promoted_properties_list: true,
line_before_binary_operator: true,
always_break_named_arguments_list: false,
Expand Down Expand Up @@ -390,6 +393,7 @@ const TEMPEST_PRESET: FormatSettings = FormatSettings {
preserve_breaking_parameter_list: true,
preserve_breaking_attribute_list: true,
preserve_breaking_conditional_expression: true,
preserve_breaking_condition_statement: true,
break_promoted_properties_list: true,
line_before_binary_operator: true,
always_break_named_arguments_list: false,
Expand Down Expand Up @@ -473,6 +477,7 @@ const HACK_PRESET: FormatSettings = FormatSettings {
preserve_breaking_parameter_list: false,
preserve_breaking_attribute_list: false,
preserve_breaking_conditional_expression: false,
preserve_breaking_condition_statement: false,
break_promoted_properties_list: true,
line_before_binary_operator: true,
always_break_named_arguments_list: true,
Expand Down Expand Up @@ -556,6 +561,7 @@ const DRUPAL_PRESET: FormatSettings = FormatSettings {
preserve_breaking_parameter_list: true,
preserve_breaking_attribute_list: true,
preserve_breaking_conditional_expression: true,
preserve_breaking_condition_statement: true,
break_promoted_properties_list: true,
line_before_binary_operator: true,
always_break_named_arguments_list: false,
Expand Down
8 changes: 8 additions & 0 deletions crates/formatter/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,14 @@ generate_formatter_settings! {
/// Default: false
preserve_breaking_conditional_expression: bool => "default_false",

/// Whether to preserve line breaks in condition statements (if, elseif, while, do-while, switch, match).
///
/// When enabled, if the original source has conditions broken across multiple lines,
/// the formatter maintains that layout using PER Coding Style 3.0 rules.
///
/// Default: false
preserve_breaking_condition_statement: bool => "default_false",

/// Whether to break a parameter list with one or more promoted properties into multiple lines.
///
/// When enabled, parameter lists with promoted properties are always multi-line:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

// Multi-line if with && operator -- should preserve
if (
$foo
&& $bar
) {
echo 'if';
}

// Multi-line elseif with || operator -- should preserve
if ($a) {
echo 'a';
} elseif (
$foo
|| $bar
) {
echo 'elseif';
}

// Multi-line while -- should preserve
while (
$foo
&& $bar
) {
echo 'while';
}

// Multi-line do-while -- should preserve
do {
echo 'do';
} while (
$foo
&& $bar
);

// Multi-line switch -- should preserve
switch (
$foo
) {
case 1:
break;
}

// Multi-line match -- should preserve
$result = match (
$foo
) {
1 => 'one',
default => 'other',
};

// Single-line if -- must NOT be forced to break (DTCT-03)
if ($foo && $bar) {
echo 'single-line if';
}

// Single-line while -- must NOT be forced to break (DTCT-03)
while ($x) {
echo 'single-line while';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

// Multi-line if with && operator -- should preserve
if (
$foo && $bar
) {
echo 'if';
}

// Multi-line elseif with || operator -- should preserve
if ($a) {
echo 'a';
} elseif (
$foo || $bar
) {
echo 'elseif';
}

// Multi-line while -- should preserve
while (
$foo && $bar
) {
echo 'while';
}

// Multi-line do-while -- should preserve
do {
echo 'do';
} while (
$foo && $bar
);

// Multi-line switch -- should preserve
switch (
$foo
) {
case 1:
break;
}

// Multi-line match -- should preserve
$result = match (
$foo
) {
1 => 'one',
default => 'other',
};

// Single-line if -- must NOT be forced to break (DTCT-03)
if ($foo && $bar) {
echo 'single-line if';
}

// Single-line while -- must NOT be forced to break (DTCT-03)
while ($x) {
echo 'single-line while';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FormatSettings {
preserve_breaking_condition_statement: true,
..Default::default()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

// Multi-line if -- should collapse when disabled
if ($foo && $bar) {
echo 'if';
}

// Multi-line while -- should collapse when disabled
while ($x) {
echo 'while';
}

// Multi-line do-while -- should collapse when disabled
do {
echo 'do';
} while ($x);

// Multi-line switch -- should collapse when disabled
switch ($val) {
case 1:
break;
}

// Multi-line match -- should collapse when disabled
$result = match ($val) {
1 => 'one',
default => 'other',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

// Multi-line if -- should collapse when disabled
if (
$foo && $bar
) {
echo 'if';
}

// Multi-line while -- should collapse when disabled
while (
$x
) {
echo 'while';
}

// Multi-line do-while -- should collapse when disabled
do {
echo 'do';
} while (
$x
);

// Multi-line switch -- should collapse when disabled
switch (
$val
) {
case 1:
break;
}

// Multi-line match -- should collapse when disabled
$result = match (
$val
) {
1 => 'one',
default => 'other',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FormatSettings {
preserve_breaking_condition_statement: false,
..Default::default()
}
2 changes: 2 additions & 0 deletions crates/formatter/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ test_case!(preserve_breaking_attribute_list);
test_case!(preserve_breaking_attribute_list_disabled);
test_case!(preserve_breaking_conditional_expression);
test_case!(preserve_breaking_conditional_expression_disabled);
test_case!(preserve_breaking_condition_statement);
test_case!(preserve_breaking_condition_statement_disabled);
test_case!(preserve_breaking_parameter_list_promoted_properties);
test_case!(hooks_always_break);
test_case!(inline_abstract_property_hooks);
Expand Down
1 change: 1 addition & 0 deletions docs/tools/formatter/configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ public function bar(
| `preserve-breaking-parameter-list` | `boolean` | `false` | Preserve existing line breaks in parameter lists. |
| `preserve-breaking-attribute-list` | `boolean` | `false` | Preserve existing line breaks in attribute lists. |
| `preserve-breaking-conditional-expression` | `boolean` | `false` | Preserve existing line breaks in ternary expressions. |
| `preserve-breaking-condition-statement` | `boolean` | `false` | Preserve existing line breaks in control structure conditions (`if`, `elseif`, `while`, `do-while`, `switch`, `match`). When enabled, each boolean operator is placed on its own line. |
| `break-promoted-properties-list` | `boolean` | `true` | Always break parameter lists with promoted properties. |
| `line-before-binary-operator` | `boolean` | `true` | Place the binary operator on the next line when breaking. |
| `always-break-named-arguments-list` | `boolean` | `false` | Always break named argument lists into multiple lines. |
Expand Down