diff --git a/CHANGELOG.md b/CHANGELOG.md index 76a3bc2a..4b7adc43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG for PHP CS Fixer: custom fixers +## v3.25.0 +- ReadonlyPromotedPropertiesFixer - support asymmetric visibility + ## v3.24.0 - Add PhpUnitRequiresConstraintFixer diff --git a/README.md b/README.md index ee79dbfd..a3dc44cb 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Latest stable version](https://img.shields.io/packagist/v/kubawerlos/php-cs-fixer-custom-fixers.svg?label=current%20version)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers) [![PHP version](https://img.shields.io/packagist/php-v/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://php.net) [![License](https://img.shields.io/github/license/kubawerlos/php-cs-fixer-custom-fixers.svg)](LICENSE) -![Tests](https://img.shields.io/badge/tests-3610-brightgreen.svg) +![Tests](https://img.shields.io/badge/tests-3613-brightgreen.svg) [![Downloads](https://img.shields.io/packagist/dt/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers) [![CI status](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml/badge.svg)](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml) diff --git a/src/Fixer/ReadonlyPromotedPropertiesFixer.php b/src/Fixer/ReadonlyPromotedPropertiesFixer.php index 198e162f..4bdfafce 100644 --- a/src/Fixer/ReadonlyPromotedPropertiesFixer.php +++ b/src/Fixer/ReadonlyPromotedPropertiesFixer.php @@ -22,6 +22,23 @@ final class ReadonlyPromotedPropertiesFixer extends AbstractFixer { + /** @var list */ + private array $promotedPropertyVisibilityKinds; + + public function __construct() + { + $this->promotedPropertyVisibilityKinds = [ + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, + CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, + ]; + if (\defined('T_PUBLIC_SET')) { + $this->promotedPropertyVisibilityKinds[] = \T_PUBLIC_SET; + $this->promotedPropertyVisibilityKinds[] = \T_PROTECTED_SET; + $this->promotedPropertyVisibilityKinds[] = \T_PRIVATE_SET; + } + } + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( @@ -53,11 +70,7 @@ public function getPriority(): int public function isCandidate(Tokens $tokens): bool { - return \defined('T_READONLY') && $tokens->isAnyTokenKindsFound([ - CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, - CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, - CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, - ]); + return \defined('T_READONLY') && $tokens->isAnyTokenKindsFound($this->promotedPropertyVisibilityKinds); } public function isRisky(): bool @@ -83,6 +96,8 @@ public function fix(\SplFileInfo $file, Tokens $tokens): void continue; } + $constructorNameIndex = $tokens->getNextMeaningfulToken($constructorAnalysis->getConstructorIndex()); + $classOpenBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); \assert(\is_int($classOpenBraceIndex)); $classCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpenBraceIndex); @@ -119,34 +134,20 @@ private function fixParameters( int $constructorCloseParenthesisIndex ): void { for ($index = $constructorCloseParenthesisIndex; $index > $constructorOpenParenthesisIndex; $index--) { - if ( - !$tokens[$index]->isGivenKind([ - CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, - CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, - CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, - ]) - ) { - continue; - } - - $nextIndex = $tokens->getNextMeaningfulToken($index); - if ($tokens[$nextIndex]->isGivenKind(\T_READONLY)) { + if (!$tokens[$index]->isGivenKind(\T_VARIABLE)) { continue; } - $prevIndex = $tokens->getPrevMeaningfulToken($index); - if ($tokens[$prevIndex]->isGivenKind(\T_READONLY)) { + $insertIndex = $this->getInsertIndex($tokens, $index); + if ($insertIndex === null) { continue; } - $propertyIndex = $tokens->getNextTokenOfKind($index, [[\T_VARIABLE]]); - \assert(\is_int($propertyIndex)); - $propertyAssignment = $tokens->findSequence( [ [\T_VARIABLE, '$this'], [\T_OBJECT_OPERATOR], - [\T_STRING, \substr($tokens[$propertyIndex]->getContent(), 1)], + [\T_STRING, \substr($tokens[$index]->getContent(), 1)], ], $classOpenBraceIndex, $classCloseBraceIndex, @@ -156,7 +157,7 @@ private function fixParameters( } $tokens->insertAt( - $index + 1, + $insertIndex + 1, [ new Token([\T_WHITESPACE, ' ']), new Token([\T_READONLY, 'readonly']), @@ -164,4 +165,24 @@ private function fixParameters( ); } } + + private function getInsertIndex(Tokens $tokens, int $index): ?int + { + $insertIndex = null; + + $index = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($index)); + while (!$tokens[$index]->equalsAny([',', '('])) { + $index = $tokens->getPrevMeaningfulToken($index); + \assert(\is_int($index)); + if ($tokens[$index]->isGivenKind(\T_READONLY)) { + return null; + } + if ($insertIndex === null && $tokens[$index]->isGivenKind($this->promotedPropertyVisibilityKinds)) { + $insertIndex = $index; + } + } + + return $insertIndex; + } } diff --git a/tests/Fixer/AbstractFixerTestCase.php b/tests/Fixer/AbstractFixerTestCase.php index 37034cc2..b0113617 100644 --- a/tests/Fixer/AbstractFixerTestCase.php +++ b/tests/Fixer/AbstractFixerTestCase.php @@ -40,6 +40,7 @@ abstract class AbstractFixerTestCase extends TestCase 'testFix80', 'testFix81', 'testFix82', + 'testFix84', 'testIsRisky', 'testReversingCodeSample', 'testStringIsTheSame', diff --git a/tests/Fixer/ReadonlyPromotedPropertiesFixerTest.php b/tests/Fixer/ReadonlyPromotedPropertiesFixerTest.php index 2ceed82f..66ddd2c9 100644 --- a/tests/Fixer/ReadonlyPromotedPropertiesFixerTest.php +++ b/tests/Fixer/ReadonlyPromotedPropertiesFixerTest.php @@ -173,4 +173,87 @@ class Baz { public function __construct(public int $x) {} } ', ]; } + + /** + * @dataProvider provideFix84Cases + * + * @requires PHP >= 8.4 + */ + public function testFix84(string $expected, ?string $input = null): void + { + $this->doTest($expected, $input); + } + + /** + * @return iterable + */ + public static function provideFix84Cases(): iterable + { + yield 'asymmetric visibility with both visibilities' => [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' + = 8.2 + * @requires PHP >= 8.4 * * @covers \PhpCsFixerCustomFixersDev\Readme\ReadmeCommand */