From 22502bd992365b5d9375c3ffcb618139cd8e03f9 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 20 Jul 2026 00:29:09 +0200 Subject: [PATCH] Add StandaloneLinePlainConstructorParamFixer for constructors without promoted properties --- ...andaloneLinePlainConstructorParamFixer.php | 163 ++++++++++++++++++ .../Exception/TokenNotFoundException.php | 5 +- .../Fixture/plain_params.php.inc | 29 ++++ .../Fixture/skip_empty_params.php.inc | 10 ++ .../Fixture/skip_other_method.php.inc | 10 ++ .../Fixture/skip_promoted_property.php.inc | 10 ++ ...loneLinePlainConstructorParamFixerTest.php | 28 +++ .../config/configured_rule.php | 10 ++ src/Config/Level/SpacesLevel.php | 2 + 9 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 packages/coding-standard/src/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer.php create mode 100644 packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/plain_params.php.inc create mode 100644 packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/skip_empty_params.php.inc create mode 100644 packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/skip_other_method.php.inc create mode 100644 packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/skip_promoted_property.php.inc create mode 100644 packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/StandaloneLinePlainConstructorParamFixerTest.php create mode 100644 packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/config/configured_rule.php diff --git a/packages/coding-standard/src/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer.php b/packages/coding-standard/src/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer.php new file mode 100644 index 0000000000..b03a5a2637 --- /dev/null +++ b/packages/coding-standard/src/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer.php @@ -0,0 +1,163 @@ + $tokens + */ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_FUNCTION); + } + + /** + * @param Tokens $tokens + */ + public function fix(SplFileInfo $fileInfo, Tokens $tokens): void + { + // from the bottom up, as adding tokens shifts every position after them + for ($position = count($tokens) - 1; $position >= 0; --$position) { + /** @var Token $token */ + $token = $tokens[$position]; + + if (! $token->isGivenKind(T_FUNCTION)) { + continue; + } + + if (! $this->methodNameResolver->isMethodName($tokens, $position, '__construct')) { + continue; + } + + $paramBracketPosition = $this->resolveOpenBracketPosition($tokens, $position); + if ($paramBracketPosition === null) { + continue; + } + + if (! $this->hasParams($tokens, $paramBracketPosition)) { + continue; + } + + if ($this->hasPromotedProperty($tokens, $paramBracketPosition)) { + continue; + } + + $this->paramNewliner->processFunction($tokens, $position); + } + } + + /** + * @param Tokens $tokens + */ + private function resolveOpenBracketPosition(Tokens $tokens, int $position): ?int + { + $namePosition = $tokens->getNextMeaningfulToken($position); + if ($namePosition === null) { + return null; + } + + return $tokens->getNextMeaningfulToken($namePosition); + } + + /** + * An empty parameter list would be broken into an empty line between the brackets. + * + * @param Tokens $tokens + */ + private function hasParams(Tokens $tokens, int $openBracketPosition): bool + { + $firstParamPosition = $tokens->getNextMeaningfulToken($openBracketPosition); + if ($firstParamPosition === null) { + return false; + } + + /** @var Token $firstParamToken */ + $firstParamToken = $tokens[$firstParamPosition]; + + return $firstParamToken->getContent() !== ')'; + } + + /** + * @param Tokens $tokens + */ + private function hasPromotedProperty(Tokens $tokens, int $openBracketPosition): bool + { + $closeBracketPosition = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBracketPosition); + + for ($index = $openBracketPosition + 1; $index < $closeBracketPosition; ++$index) { + /** @var Token $token */ + $token = $tokens[$index]; + + if ($token->isGivenKind(self::PROMOTION_KINDS)) { + return true; + } + } + + return false; + } +} diff --git a/packages/coding-standard/src/TokenRunner/Exception/TokenNotFoundException.php b/packages/coding-standard/src/TokenRunner/Exception/TokenNotFoundException.php index 40b5d036d0..76016885b6 100644 --- a/packages/coding-standard/src/TokenRunner/Exception/TokenNotFoundException.php +++ b/packages/coding-standard/src/TokenRunner/Exception/TokenNotFoundException.php @@ -8,8 +8,9 @@ final class TokenNotFoundException extends Exception { - public function __construct(int $position) - { + public function __construct( + int $position + ) { $message = sprintf('Token on position %d was not found', $position); parent::__construct($message); diff --git a/packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/plain_params.php.inc b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/plain_params.php.inc new file mode 100644 index 0000000000..ea1f6e5ee7 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/plain_params.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/skip_empty_params.php.inc b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/skip_empty_params.php.inc new file mode 100644 index 0000000000..007a9b0ffc --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/Fixture/skip_empty_params.php.inc @@ -0,0 +1,10 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFiles(__DIR__ . '/Fixture'); + } + + public function provideConfig(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/config/configured_rule.php b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/config/configured_rule.php new file mode 100644 index 0000000000..000605c419 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLinePlainConstructorParamFixer/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(StandaloneLinePlainConstructorParamFixer::class); +}; diff --git a/src/Config/Level/SpacesLevel.php b/src/Config/Level/SpacesLevel.php index 6fa48e8b62..c87066e140 100644 --- a/src/Config/Level/SpacesLevel.php +++ b/src/Config/Level/SpacesLevel.php @@ -31,6 +31,7 @@ use PhpCsFixer\Fixer\Whitespace\TypesSpacesFixer; use Symplify\CodingStandard\Fixer\Spacing\MethodChainingNewlineFixer; use Symplify\CodingStandard\Fixer\Spacing\SpaceAfterCommaHereNowDocFixer; +use Symplify\CodingStandard\Fixer\Spacing\StandaloneLinePlainConstructorParamFixer; use Symplify\CodingStandard\Fixer\Spacing\StandaloneLinePromotedPropertyFixer; use Symplify\CodingStandard\Fixer\Spacing\StandaloneLineRequiredParamFixer; use Symplify\CodingStandard\Fixer\Strict\BlankLineAfterStrictTypesFixer; @@ -75,6 +76,7 @@ final class SpacesLevel // most invasive structural changes MethodChainingIndentationFixer::class, StandaloneLinePromotedPropertyFixer::class, + StandaloneLinePlainConstructorParamFixer::class, StandaloneLineRequiredParamFixer::class, MethodArgumentSpaceFixer::class,