diff --git a/src/Fixer/FunctionNotation/AlignMultilineParametersFixer.php b/src/Fixer/FunctionNotation/AlignMultilineParametersFixer.php index b357968..7fe2b23 100644 --- a/src/Fixer/FunctionNotation/AlignMultilineParametersFixer.php +++ b/src/Fixer/FunctionNotation/AlignMultilineParametersFixer.php @@ -14,14 +14,35 @@ use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer; +use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; use SplFileInfo; final class AlignMultilineParametersFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { - private const C_VARIABLES = 'variables'; - private const C_DEFAULTS = 'defaults'; + /** + * @internal + */ + public const C_VARIABLES = 'variables'; + /** + * @internal + */ + public const C_DEFAULTS = 'defaults'; + + private array $parameterModifiers; + + public function __construct() { + parent::__construct(); + $this->parameterModifiers = [ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + ]; + if (defined('T_READONLY')) { + $this->parameterModifiers[] = T_READONLY; + } + } public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -105,7 +126,7 @@ function test( $typeAnalysis = $argument->getTypeAnalysis(); if ($typeAnalysis) { $hasAtLeastOneTypedArgument = true; - $typeLength = strlen($typeAnalysis->getName()); + $typeLength = $this->getFullTypeLength($tokens, $typeAnalysis->getStartIndex()); if ($typeLength > $longestType) { $longestType = $typeLength; } @@ -124,24 +145,24 @@ function test( if ($this->configuration[self::C_VARIABLES] === true) { $typeLen = 0; if ($argument->getTypeAnalysis() !== null) { - $typeLen = strlen($argument->getTypeAnalysis()->getName()); + $typeLen = $this->getFullTypeLength($tokens, $argument->getTypeAnalysis()->getStartIndex()); } $appendix = str_repeat(' ', $longestType - $typeLen + (int)$hasAtLeastOneTypedArgument); if ($argument->hasTypeAnalysis()) { - $whitespace = $appendix; + $whitespaceToken = $appendix; } else { - $whitespace = $this->whitespacesConfig->getLineEnding() . $argsIndent . $appendix; + $whitespaceToken = $this->whitespacesConfig->getLineEnding() . $argsIndent . $appendix; } } else { if ($argument->hasTypeAnalysis()) { - $whitespace = ' '; + $whitespaceToken = ' '; } else { - $whitespace = $this->whitespacesConfig->getLineEnding() . $argsIndent; + $whitespaceToken = $this->whitespacesConfig->getLineEnding() . $argsIndent; } } - $tokens->ensureWhitespaceAtIndex($whitespaceIndex, 0, $whitespace); + $tokens->ensureWhitespaceAtIndex($whitespaceIndex, 0, $whitespaceToken); } if ($this->configuration[self::C_DEFAULTS] !== null) { @@ -162,4 +183,28 @@ function test( } } + private function getFullTypeLength(Tokens $tokens, int $typeIndex): int { + /** @var \PhpCsFixer\Tokenizer\Token $typeToken */ + $typeToken = $tokens[$typeIndex]; + $typeLength = strlen($typeToken->getContent()); + + /** @var \PhpCsFixer\Tokenizer\Token $possiblyReadonlyToken */ + $possiblyReadonlyToken = $tokens[$typeIndex - 2]; + if ($possiblyReadonlyToken->isGivenKind($this->parameterModifiers)) { + /** @var \PhpCsFixer\Tokenizer\Token $whitespaceToken */ + $whitespaceToken = $tokens[$typeIndex - 1]; + $typeLength += strlen($possiblyReadonlyToken->getContent() . $whitespaceToken->getContent()); + } + + /** @var \PhpCsFixer\Tokenizer\Token $possiblyPromotionToken */ + $possiblyPromotionToken = $tokens[$typeIndex - 4]; + if ($possiblyPromotionToken->isGivenKind($this->parameterModifiers)) { + /** @var \PhpCsFixer\Tokenizer\Token $whitespaceToken */ + $whitespaceToken = $tokens[$typeIndex - 3]; + $typeLength += strlen($possiblyPromotionToken->getContent() . $whitespaceToken->getContent()); + } + + return $typeLength; + } + } diff --git a/tests/Fixer/FunctionNotation/AlignMultilineParametersFixerTest.php b/tests/Fixer/FunctionNotation/AlignMultilineParametersFixerTest.php index 85a65ba..85f45e2 100644 --- a/tests/Fixer/FunctionNotation/AlignMultilineParametersFixerTest.php +++ b/tests/Fixer/FunctionNotation/AlignMultilineParametersFixerTest.php @@ -17,8 +17,8 @@ final class AlignMultilineParametersFixerTest extends AbstractFixerTestCase { */ public function testBothTrue(string $expected, ?string $input = null): void { $this->fixer->configure([ - 'variables' => true, - 'defaults' => true, + AlignMultilineParametersFixer::C_VARIABLES => true, + AlignMultilineParametersFixer::C_DEFAULTS => true, ]); $this->doTest($expected, $input); } @@ -179,8 +179,8 @@ final class AlignMultilineParametersFixerTest extends AbstractFixerTestCase { */ public function testBothFalse(string $expected, ?string $input = null): void { $this->fixer->configure([ - 'variables' => false, - 'defaults' => false, + AlignMultilineParametersFixer::C_VARIABLES => false, + AlignMultilineParametersFixer::C_DEFAULTS => false, ]); $this->doTest($expected, $input); } @@ -200,8 +200,8 @@ final class AlignMultilineParametersFixerTest extends AbstractFixerTestCase { */ public function testBothNull(string $expected, ?string $input = null): void { $this->fixer->configure([ - 'variables' => null, - 'defaults' => null, + AlignMultilineParametersFixer::C_VARIABLES => null, + AlignMultilineParametersFixer::C_DEFAULTS => null, ]); $this->doTest($expected, $input); } @@ -212,6 +212,138 @@ final class AlignMultilineParametersFixerTest extends AbstractFixerTestCase { } } + /** + * @dataProvider provide80TrueCases + * @requires PHP 8.0 + */ + public function test80BothTrue(string $expected, ?string $input = null): void { + $this->fixer->configure([ + AlignMultilineParametersFixer::C_VARIABLES => true, + AlignMultilineParametersFixer::C_DEFAULTS => true, + ]); + $this->doTest($expected, $input); + } + + public function provide80TrueCases(): iterable { + yield 'constructor promotion, defaults' => [ + 'fixer->configure([ + AlignMultilineParametersFixer::C_VARIABLES => false, + AlignMultilineParametersFixer::C_DEFAULTS => false, + ]); + $this->doTest($expected, $input); + } + + public function provideFalse80Cases(): iterable { + foreach ($this->provide80TrueCases() as $key => $case) { + if (isset($case[1])) { + yield $key => [$case[1], $case[0]]; + } else { + yield $key => $case; + } + } + } + + /** + * @dataProvider provide81TrueCases + * @requires PHP 8.1 + */ + public function test81BothTrue(string $expected, ?string $input = null): void { + $this->fixer->configure([ + AlignMultilineParametersFixer::C_VARIABLES => true, + AlignMultilineParametersFixer::C_DEFAULTS => true, + ]); + $this->doTest($expected, $input); + } + + public function provide81TrueCases(): iterable { + yield 'constructor promotion, readonly, defaults' => [ + ' [ + 'fixer->configure([ + AlignMultilineParametersFixer::C_VARIABLES => false, + AlignMultilineParametersFixer::C_DEFAULTS => false, + ]); + $this->doTest($expected, $input); + } + + public function provideFalse81Cases(): iterable { + foreach ($this->provide81TrueCases() as $key => $case) { + if (isset($case[1])) { + yield $key => [$case[1], $case[0]]; + } else { + yield $key => $case; + } + } + } + protected function createFixer(): AbstractFixer { return new AlignMultilineParametersFixer(); }