From be3ff0db7d1a90930fa57eb13ff5ea2963ef9d84 Mon Sep 17 00:00:00 2001 From: Ilja Kiel Date: Thu, 18 Sep 2025 16:03:14 +0200 Subject: [PATCH 1/2] Extend PhpDocPropertySorterFixer to support @property-read and @property-write --- src/Fixer/PhpDocPropertySorterFixer.php | 2 +- tests/Fixer/PhpDocPropertySorterFixerTest.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Fixer/PhpDocPropertySorterFixer.php b/src/Fixer/PhpDocPropertySorterFixer.php index 33fda85d..77d05462 100644 --- a/src/Fixer/PhpDocPropertySorterFixer.php +++ b/src/Fixer/PhpDocPropertySorterFixer.php @@ -126,7 +126,7 @@ private static function sortPropertiesByName(array &$properties): void private static function extractPropertyName(string $propertyLine): ?string { $matches = []; - Preg::match('/@property\\s+[^\\s]+\\s+\\$(\\w+)/', $propertyLine, $matches); + Preg::match('/@property(?:-read|-write)?\\s+[^\\s]+\\s+\\$(\\w+)/', $propertyLine, $matches); /** @var array $matches */ if (\count($matches) > 1) { return $matches[1]; diff --git a/tests/Fixer/PhpDocPropertySorterFixerTest.php b/tests/Fixer/PhpDocPropertySorterFixerTest.php index 5b23ca85..c9d5fb44 100644 --- a/tests/Fixer/PhpDocPropertySorterFixerTest.php +++ b/tests/Fixer/PhpDocPropertySorterFixerTest.php @@ -183,6 +183,23 @@ function example($x) {} * @return bool */ function example($x) {} +', + ]; + + yield [ + ' Date: Mon, 22 Sep 2025 16:10:23 +0200 Subject: [PATCH 2/2] Extend PhpDocPropertySorterFixer to support @property-read and @property-write --- composer.json | 4 +- src/Fixer/PhpDocPropertySorterFixer.php | 111 ++------- src/Fixer/PhpdocPropertySortedFixer.php | 137 +++++++++++ tests/Fixer/PhpDocPropertySorterFixerTest.php | 174 +------------ tests/Fixer/PhpdocPropertySortedFixerTest.php | 229 ++++++++++++++++++ 5 files changed, 395 insertions(+), 260 deletions(-) create mode 100644 src/Fixer/PhpdocPropertySortedFixer.php create mode 100644 tests/Fixer/PhpdocPropertySortedFixerTest.php diff --git a/composer.json b/composer.json index c9ea53cb..0c9520a0 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ ], "infection": [ "@prepare-dev-tools", - "@composer --working-dir=.dev-tools infection" + "@composer --working-dir=.dev-tools infection -- --coverage=build --skip-initial-tests" ], "prepare-dev-tools": [ "@putenv PHP_CS_FIXER_IGNORE_ENV=1", @@ -58,7 +58,7 @@ ], "test": [ "phpunit --check-php-configuration", - "phpunit" + "phpunit --coverage-xml=.dev-tools/build/coverage-xml --log-junit=.dev-tools/build/junit.xml --coverage-text" ], "verify": [ "@analyse", diff --git a/src/Fixer/PhpDocPropertySorterFixer.php b/src/Fixer/PhpDocPropertySorterFixer.php index 77d05462..20054a03 100644 --- a/src/Fixer/PhpDocPropertySorterFixer.php +++ b/src/Fixer/PhpDocPropertySorterFixer.php @@ -11,127 +11,54 @@ namespace PhpCsFixerCustomFixers\Fixer; -use PhpCsFixer\FixerDefinition\CodeSample; -use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\Fixer\DeprecatedFixerInterface; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Preg; -use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** + * @deprecated + * * @no-named-arguments */ -final class PhpDocPropertySorterFixer extends AbstractFixer +final class PhpDocPropertySorterFixer extends AbstractFixer implements DeprecatedFixerInterface { + private PhpdocPropertySortedFixer $phpdocPropertySortedFixer; + + public function __construct() + { + $this->phpdocPropertySortedFixer = new PhpdocPropertySortedFixer(); + } + public function getDefinition(): FixerDefinitionInterface { - return new FixerDefinition( - 'Sorts @property annotations in PHPDoc blocks alphabetically within groups separated by empty lines.', - [new CodeSample('phpdocPropertySortedFixer->getDefinition(); } public function getPriority(): int { - return 0; + return $this->phpdocPropertySortedFixer->getPriority(); } public function isCandidate(Tokens $tokens): bool { - return $tokens->isTokenKindFound(\T_DOC_COMMENT); + return $this->phpdocPropertySortedFixer->isCandidate($tokens); } public function isRisky(): bool { - return false; + return $this->phpdocPropertySortedFixer->isRisky(); } public function fix(\SplFileInfo $file, Tokens $tokens): void { - foreach ($tokens as $index => $token) { - if (!$token->isGivenKind(\T_DOC_COMMENT)) { - continue; - } - - $originalDocContent = $token->getContent(); - $sortedDocContent = self::sortPropertiesInDocBlock($originalDocContent); - - if ($originalDocContent !== $sortedDocContent) { - $tokens[$index] = new Token([\T_DOC_COMMENT, $sortedDocContent]); - } - } - } - - private static function sortPropertiesInDocBlock(string $docContent): string - { - $docLines = \explode("\n", $docContent); - $processedLines = []; - $currentPropertyGroup = []; - - foreach ($docLines as $line) { - if (self::isPropertyAnnotation($line)) { - $currentPropertyGroup[] = $line; - } else { - self::flushPropertyGroup($currentPropertyGroup, $processedLines); - $processedLines[] = $line; - } - } - - return \implode("\n", $processedLines); - } - - private static function isPropertyAnnotation(string $line): bool - { - return Preg::match('/@property/', $line); - } - - /** - * Sorts and adds a property group to the processed lines. - * - * @param list $propertyGroup - * @param list $processedLines - */ - private static function flushPropertyGroup(array &$propertyGroup, array &$processedLines): void - { - if (\count($propertyGroup) === 0) { - return; - } - - self::sortPropertiesByName($propertyGroup); - $processedLines = \array_merge($processedLines, $propertyGroup); - $propertyGroup = []; + $this->phpdocPropertySortedFixer->fix($file, $tokens); } /** - * @param list $properties + * @return list */ - private static function sortPropertiesByName(array &$properties): void + public function getSuccessorsNames(): array { - \usort($properties, static function (string $propertyA, string $propertyB): int { - $nameA = self::extractPropertyName($propertyA); - $nameB = self::extractPropertyName($propertyB); - - return \strcmp($nameA ?? '', $nameB ?? ''); - }); - } - - private static function extractPropertyName(string $propertyLine): ?string - { - $matches = []; - Preg::match('/@property(?:-read|-write)?\\s+[^\\s]+\\s+\\$(\\w+)/', $propertyLine, $matches); - /** @var array $matches */ - if (\count($matches) > 1) { - return $matches[1]; - } - - return null; + return [$this->phpdocPropertySortedFixer->getName()]; } } diff --git a/src/Fixer/PhpdocPropertySortedFixer.php b/src/Fixer/PhpdocPropertySortedFixer.php new file mode 100644 index 00000000..bfe35c37 --- /dev/null +++ b/src/Fixer/PhpdocPropertySortedFixer.php @@ -0,0 +1,137 @@ +isTokenKindFound(\T_DOC_COMMENT); + } + + public function isRisky(): bool + { + return false; + } + + public function fix(\SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(\T_DOC_COMMENT)) { + continue; + } + + $originalDocContent = $token->getContent(); + $sortedDocContent = self::sortPropertiesInDocBlock($originalDocContent); + + if ($originalDocContent !== $sortedDocContent) { + $tokens[$index] = new Token([\T_DOC_COMMENT, $sortedDocContent]); + } + } + } + + private static function sortPropertiesInDocBlock(string $docContent): string + { + $docLines = \explode("\n", $docContent); + $processedLines = []; + $currentPropertyGroup = []; + + foreach ($docLines as $line) { + if (self::isPropertyAnnotation($line)) { + $currentPropertyGroup[] = $line; + } else { + self::flushPropertyGroup($currentPropertyGroup, $processedLines); + $processedLines[] = $line; + } + } + + return \implode("\n", $processedLines); + } + + private static function isPropertyAnnotation(string $line): bool + { + return Preg::match('/@property/', $line); + } + + /** + * Sorts and adds a property group to the processed lines. + * + * @param list $propertyGroup + * @param list $processedLines + */ + private static function flushPropertyGroup(array &$propertyGroup, array &$processedLines): void + { + if (\count($propertyGroup) === 0) { + return; + } + + self::sortPropertiesByName($propertyGroup); + $processedLines = \array_merge($processedLines, $propertyGroup); + $propertyGroup = []; + } + + /** + * @param list $properties + */ + private static function sortPropertiesByName(array &$properties): void + { + \usort($properties, static function (string $propertyA, string $propertyB): int { + $nameA = self::extractPropertyName($propertyA); + $nameB = self::extractPropertyName($propertyB); + + return \strcmp($nameA ?? '', $nameB ?? ''); + }); + } + + private static function extractPropertyName(string $propertyLine): ?string + { + $matches = []; + Preg::match('/@property(?:-read|-write)?\\s+[^\\s]+\\s+\\$(\\w+)/', $propertyLine, $matches); + /** @var array $matches */ + if (\count($matches) > 1) { + return $matches[1]; + } + + return null; + } +} diff --git a/tests/Fixer/PhpDocPropertySorterFixerTest.php b/tests/Fixer/PhpDocPropertySorterFixerTest.php index c9d5fb44..49356a2e 100644 --- a/tests/Fixer/PhpDocPropertySorterFixerTest.php +++ b/tests/Fixer/PhpDocPropertySorterFixerTest.php @@ -11,6 +11,8 @@ namespace Tests\Fixer; +use PhpCsFixerCustomFixers\Fixer\PhpdocPropertySortedFixer; + /** * @internal * @@ -18,6 +20,11 @@ */ final class PhpDocPropertySorterFixerTest extends AbstractFixerTestCase { + public function testSuccessorName(): void + { + self::assertSuccessorName(PhpdocPropertySortedFixer::name()); + } + public function testIsRisky(): void { self::assertRiskiness(false); @@ -36,171 +43,6 @@ public function testFix(string $expected, ?string $input = null): void */ public static function provideFixCases(): iterable { - yield ['doTest($expected, $input); + } + + /** + * @return iterable + */ + public static function provideFixCases(): iterable + { + yield ['