Skip to content

Commit 3237e20

Browse files
committed
feat(formatter): add first-method-chain-on-new-line option
closes #775 Signed-off-by: azjezz <[email protected]>
1 parent 258d21c commit 3237e20

File tree

5 files changed

+36
-2
lines changed

5 files changed

+36
-2
lines changed

crates/formatter/src/internal/format/member_access.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,11 @@ impl<'arena> MemberAccessChain<'arena> {
265265
matches!(self.accesses.first(), Some(MemberAccess::StaticMethodCall(_)))
266266
}
267267

268+
#[inline]
269+
fn is_first_link_object_method_call(&self) -> bool {
270+
matches!(self.accesses.first(), Some(MemberAccess::MethodCall(_) | MemberAccess::NullSafeMethodCall(_)))
271+
}
272+
268273
#[inline]
269274
fn is_already_broken(&self, f: &FormatterState) -> bool {
270275
let mut accesses_len = self.accesses.len();
@@ -624,6 +629,10 @@ fn should_inline_first_access<'arena>(
624629
f: &FormatterState<'_, 'arena>,
625630
member_access_chain: &MemberAccessChain<'arena>,
626631
) -> bool {
632+
if f.settings.first_method_chain_on_new_line && member_access_chain.is_first_link_object_method_call() {
633+
return false;
634+
}
635+
627636
if member_access_chain.find_fluent_access_chain_start().is_some_and(|start| start == 0) {
628637
return false;
629638
}

crates/formatter/src/settings.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,27 @@ pub struct FormatSettings {
325325
#[serde(default)]
326326
pub method_chain_breaking_style: MethodChainBreakingStyle,
327327

328+
/// When method chaining breaks across lines, place the first method on a new line.
329+
///
330+
/// This follows PER-CS 4.7: "When [method chaining is] put on separate lines, [...] the first method MUST be on the next line."
331+
///
332+
/// When enabled:
333+
/// ```php
334+
/// $this
335+
/// ->getCache()
336+
/// ->forget();
337+
/// ```
338+
///
339+
/// When disabled:
340+
/// ```php
341+
/// $this->getCache()
342+
/// ->forget();
343+
/// ```
344+
///
345+
/// Default: `true`
346+
#[serde(default = "default_true")]
347+
pub first_method_chain_on_new_line: bool,
348+
328349
/// Whether to preserve line breaks in method chains, even if they could fit on a single line.
329350
///
330351
/// Default: false
@@ -930,6 +951,7 @@ impl Default for FormatSettings {
930951
null_type_hint: NullTypeHint::default(),
931952
break_promoted_properties_list: true,
932953
method_chain_breaking_style: MethodChainBreakingStyle::NextLine,
954+
first_method_chain_on_new_line: true,
933955
line_before_binary_operator: true,
934956
sort_uses: true,
935957
sort_class_methods: false,

crates/formatter/tests/cases/chain_comments/after.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ private function generateMenuItemUrl(MenuItemDto $menuItemDto): string
1919
// set any other parameters defined by the menu item
2020
->setAll($routeParameters);
2121

22-
$mainTemplate = $this->cleanupTemplate($template)
22+
$mainTemplate = $this
23+
->cleanupTemplate($template)
2324
// Cleanup head, we'll insert it after having parsed the DOM
2425
->replaceRegex('/<head>((.|\n)*?)<\/head>/', '<head></head>');
2526
}

crates/formatter/tests/cases/issue_122/after.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
->belongsTo('user_permissions.user_id', 'users.id')
66
->belongsTo('user_permissions.permission_id', 'permissions.id');
77

8-
$item = $this->getCachePool()
8+
$item = $this
9+
->getCachePool()
910
->getItem($key)
1011
->set($value);
1112

docs/tools/formatter/configuration-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ excludes = ["src/**/AutoGenerated/**/*.php"] # Additionally excluded from forma
5555
| `inline-empty-classlike-braces` | `boolean` | `false` | Place empty class-like bodies on the same line. |
5656
| `inline-empty-anonymous-class-braces` | `boolean` | `true` | Place empty anonymous class bodies on the same line. |
5757
| `method-chain-breaking-style`| `enum("same-line", "next-line")` | `"next-line"` | How to break method chains. |
58+
| `first-method-chain-on-new-line` | `boolean` | `true` | When method chaining breaks across lines, place the first method call on a new line. Only applies to object method calls (`->`, `?->`), not static methods (`::`) or property access ([PER-CS 4.7](https://www.php-fig.org/per/coding-style/#47-method-and-function-calls) compliant). |
5859
| `preserve-breaking-member-access-chain` | `boolean` | `false` | Preserve existing line breaks in method chains. |
5960
| `preserve-breaking-argument-list` | `boolean` | `false` | Preserve existing line breaks in argument lists. |
6061
| `preserve-breaking-array-like` | `boolean` | `true` | Preserve existing line breaks in array-like structures. |

0 commit comments

Comments
 (0)