From 17dfe253d91b7939c3247f0359dca7c20e1efbdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Rumi=C5=84ski?= Date: Mon, 18 Nov 2024 02:42:01 +0100 Subject: [PATCH 1/4] fix: fix typing property wrongly typed as non-nullable (#8290) --- src/Console/Command/DescribeCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/Command/DescribeCommand.php b/src/Console/Command/DescribeCommand.php index 4153c88661d..cf91f8493fc 100644 --- a/src/Console/Command/DescribeCommand.php +++ b/src/Console/Command/DescribeCommand.php @@ -67,7 +67,7 @@ final class DescribeCommand extends Command private FixerFactory $fixerFactory; /** - * @var array + * @var null|array */ private $fixers; From e37cb56a4f5200de2e8c86fe4ebf915ff14998ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Rumi=C5=84ski?= Date: Mon, 18 Nov 2024 09:46:42 +0100 Subject: [PATCH 2/4] fix: proper base class used for AbstractDoctrineAnnotationFixer templates generation (#8291) --- src/AbstractDoctrineAnnotationFixer.php | 6 ++---- .../ConfigurableFixerTemplateFixer.php | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/AbstractDoctrineAnnotationFixer.php b/src/AbstractDoctrineAnnotationFixer.php index 7ca969fb27f..6a2be5f94de 100644 --- a/src/AbstractDoctrineAnnotationFixer.php +++ b/src/AbstractDoctrineAnnotationFixer.php @@ -28,12 +28,10 @@ * @internal * * @phpstan-type _AutogeneratedInputConfiguration array{ - * scalar_types?: bool, - * union_types?: bool + * ignored_tags?: list * } * @phpstan-type _AutogeneratedComputedConfiguration array{ - * scalar_types: bool, - * union_types: bool + * ignored_tags: list * } * * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> diff --git a/src/Fixer/Internal/ConfigurableFixerTemplateFixer.php b/src/Fixer/Internal/ConfigurableFixerTemplateFixer.php index 2404ec88447..bed75e1c271 100644 --- a/src/Fixer/Internal/ConfigurableFixerTemplateFixer.php +++ b/src/Fixer/Internal/ConfigurableFixerTemplateFixer.php @@ -19,6 +19,7 @@ use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer; use PhpCsFixer\Console\Command\HelpCommand; use PhpCsFixer\DocBlock\DocBlock; +use PhpCsFixer\Doctrine\Annotation\Tokens as DoctrineAnnotationTokens; use PhpCsFixer\Fixer\AttributeNotation\OrderedAttributesFixer; use PhpCsFixer\Fixer\Casing\ConstantCaseFixer; use PhpCsFixer\Fixer\ClassNotation\FinalInternalClassFixer; @@ -36,6 +37,7 @@ use PhpCsFixer\Fixer\Phpdoc\PhpdocTagTypeFixer; use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; use PhpCsFixer\FixerConfiguration\AllowedValueSubset; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; @@ -525,7 +527,7 @@ public function isCandidate(Tokens $tokens): bool } }; } elseif (AbstractDoctrineAnnotationFixer::class === $className) { - return new class extends AbstractPhpdocToTypeDeclarationFixer { + return new class extends AbstractDoctrineAnnotationFixer { protected function isSkippedType(string $type): bool { throw new \LogicException('Not implemented.'); @@ -550,6 +552,21 @@ public function isCandidate(Tokens $tokens): bool { throw new \LogicException('Not implemented.'); } + + public function configure(array $configuration): void + { + // void + } + + protected function fixAnnotations(DoctrineAnnotationTokens $doctrineAnnotationTokens): void + { + throw new \LogicException('Not implemented.'); + } + + public function getConfigurationDefinition(): FixerConfigurationResolverInterface + { + return $this->createConfigurationDefinition(); + } }; } From ccad080a5c59f5d49bceda60e7018453f8139134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Rumi=C5=84ski?= Date: Mon, 18 Nov 2024 13:39:46 +0100 Subject: [PATCH 3/4] chore: update dev dependencies in root (#8289) --- composer.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 94518ce1824..104d8683ce2 100644 --- a/composer.json +++ b/composer.json @@ -46,18 +46,18 @@ "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "facile-it/paraunit": "^1.3 || ^2.3", - "infection/infection": "^0.29.5", - "justinrainbow/json-schema": "^5.2", + "facile-it/paraunit": "^1.3.1 || ^2.4", + "infection/infection": "^0.29.8", + "justinrainbow/json-schema": "^5.3 || ^6.0", "keradus/cli-executor": "^2.1", - "mikey179/vfsstream": "^1.6.11", + "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.7", "php-cs-fixer/accessible-object": "^1.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", - "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2", - "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + "phpunit/phpunit": "^9.6.21 || ^10.5.38 || ^11.4.3", + "symfony/var-dumper": "^5.4.47 || ^6.4.15 || ^7.1.8", + "symfony/yaml": "^5.4.45 || ^6.4.13 || ^7.1.6" }, "suggest": { "ext-dom": "For handling output formats in XML", From 52dc0621051bd55be2a89bfb4aa9e1dda85ba149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Rumi=C5=84ski?= Date: Mon, 18 Nov 2024 14:13:25 +0100 Subject: [PATCH 4/4] feat: phpdoc_to_property_type - handle virtual types and null initialization, enable in php-highest CI job (#8283) --- .php-cs-fixer.php-highest.php | 7 ++ dev-tools/phpstan/baseline.php | 72 +++++++++++++++++++ .../phpdoc_to_param_type.rst | 9 +++ .../phpdoc_to_property_type.rst | 9 +++ .../phpdoc_to_return_type.rst | 9 +++ src/AbstractPhpdocToTypeDeclarationFixer.php | 10 +++ src/Console/Command/CheckCommand.php | 2 - src/Console/Command/DescribeCommand.php | 1 - src/Console/Command/DocumentationCommand.php | 1 - src/Console/Command/FixCommand.php | 2 - src/Console/Command/HelpCommand.php | 1 - src/Console/Command/ListFilesCommand.php | 1 - src/Console/Command/ListSetsCommand.php | 1 - src/Console/Command/SelfUpdateCommand.php | 1 - src/Console/Command/WorkerCommand.php | 2 - .../PhpdocToParamTypeFixer.php | 2 + .../PhpdocToPropertyTypeFixer.php | 29 ++++++-- .../PhpdocToReturnTypeFixer.php | 2 + .../Import/FullyQualifiedStrictTypesFixer.php | 2 +- tests/AutoReview/FixerFactoryTest.php | 1 + tests/AutoReview/ProjectCodeTest.php | 4 ++ .../PhpdocToPropertyTypeFixerTest.php | 24 +++++-- ...rty_type,fully_qualified_strict_types.test | 24 +++++++ 23 files changed, 191 insertions(+), 25 deletions(-) create mode 100644 tests/Fixtures/Integration/priority/phpdoc_to_property_type,fully_qualified_strict_types.test diff --git a/.php-cs-fixer.php-highest.php b/.php-cs-fixer.php-highest.php index c8759100d67..4a8685af583 100644 --- a/.php-cs-fixer.php-highest.php +++ b/.php-cs-fixer.php-highest.php @@ -24,6 +24,13 @@ $config->setRules(array_merge($config->getRules(), [ '@PHP83Migration' => true, '@PHP82Migration:risky' => true, + 'phpdoc_to_property_type' => [ // experimental + 'map_types' => [ + 'TFixerInputConfig' => 'array', + 'TFixerComputedConfig' => 'array', + 'TFixer' => '\PhpCsFixer\AbstractFixer', + ], + ], 'fully_qualified_strict_types' => ['import_symbols' => true], 'php_unit_attributes' => false, // as is not yet supported by PhpCsFixerInternal/configurable_fixer_template ])); diff --git a/dev-tools/phpstan/baseline.php b/dev-tools/phpstan/baseline.php index 1ff7f38b77b..30724f7b5a7 100644 --- a/dev-tools/phpstan/baseline.php +++ b/dev-tools/phpstan/baseline.php @@ -61,6 +61,18 @@ 'count' => 1, 'path' => __DIR__ . '/../../src/Console/Command/CheckCommand.php', ]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\CheckCommand\\:\\:\\$defaultDescription has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/CheckCommand.php', +]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\CheckCommand\\:\\:\\$defaultName has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/CheckCommand.php', +]; $ignoreErrors[] = [ // identifier: offsetAccess.notFound 'message' => '#^Offset 1 might not exist on array\\.$#', @@ -103,6 +115,18 @@ 'count' => 1, 'path' => __DIR__ . '/../../src/Console/Command/DescribeCommand.php', ]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\DescribeCommand\\:\\:\\$defaultName has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/DescribeCommand.php', +]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\DocumentationCommand\\:\\:\\$defaultName has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/DocumentationCommand.php', +]; $ignoreErrors[] = [ // identifier: argument.type 'message' => '#^Parameter \\#3 \\$cwd of class PhpCsFixer\\\\Console\\\\ConfigurationResolver constructor expects string, string\\|false given\\.$#', @@ -115,6 +139,24 @@ 'count' => 1, 'path' => __DIR__ . '/../../src/Console/Command/FixCommand.php', ]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\FixCommand\\:\\:\\$defaultDescription has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/FixCommand.php', +]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\FixCommand\\:\\:\\$defaultName has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/FixCommand.php', +]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\HelpCommand\\:\\:\\$defaultName has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/HelpCommand.php', +]; $ignoreErrors[] = [ // identifier: argument.type 'message' => '#^Parameter \\#2 \\$basePath of static method Symfony\\\\Component\\\\Filesystem\\\\Path\\:\\:makeRelative\\(\\) expects string, string\\|false given\\.$#', @@ -127,6 +169,18 @@ 'count' => 1, 'path' => __DIR__ . '/../../src/Console/Command/ListFilesCommand.php', ]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\ListFilesCommand\\:\\:\\$defaultName has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/ListFilesCommand.php', +]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\ListSetsCommand\\:\\:\\$defaultName has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/ListSetsCommand.php', +]; $ignoreErrors[] = [ // identifier: offsetAccess.notFound 'message' => '#^Offset \'argv\' might not exist on array\\.$#', @@ -139,12 +193,30 @@ 'count' => 1, 'path' => __DIR__ . '/../../src/Console/Command/SelfUpdateCommand.php', ]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\SelfUpdateCommand\\:\\:\\$defaultName has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/SelfUpdateCommand.php', +]; $ignoreErrors[] = [ // identifier: offsetAccess.notFound 'message' => '#^Offset \'files\' might not exist on array\\.$#', 'count' => 1, 'path' => __DIR__ . '/../../src/Console/Command/WorkerCommand.php', ]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\WorkerCommand\\:\\:\\$defaultDescription has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/WorkerCommand.php', +]; +$ignoreErrors[] = [ + // identifier: missingType.property + 'message' => '#^Property PhpCsFixer\\\\Console\\\\Command\\\\WorkerCommand\\:\\:\\$defaultName has no type specified\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/../../src/Console/Command/WorkerCommand.php', +]; $ignoreErrors[] = [ // identifier: offsetAccess.notFound 'message' => '#^Offset \'allow\\-risky\' might not exist on array\\\\.$#', diff --git a/doc/rules/function_notation/phpdoc_to_param_type.rst b/doc/rules/function_notation/phpdoc_to_param_type.rst index 04a3f257a1c..56a11503015 100644 --- a/doc/rules/function_notation/phpdoc_to_param_type.rst +++ b/doc/rules/function_notation/phpdoc_to_param_type.rst @@ -27,6 +27,15 @@ actions are required if inherited signatures are not properly documented. Configuration ------------- +``map_types`` +~~~~~~~~~~~~~ + +Map of custom types, eg template types from PHPStan. + +Allowed types: ``array`` + +Default value: ``[]`` + ``scalar_types`` ~~~~~~~~~~~~~~~~ diff --git a/doc/rules/function_notation/phpdoc_to_property_type.rst b/doc/rules/function_notation/phpdoc_to_property_type.rst index 5be782055c4..80cbcf6b417 100644 --- a/doc/rules/function_notation/phpdoc_to_property_type.rst +++ b/doc/rules/function_notation/phpdoc_to_property_type.rst @@ -27,6 +27,15 @@ be required for newly typed properties that are read before initialization. Configuration ------------- +``map_types`` +~~~~~~~~~~~~~ + +Map of custom types, eg template types from PHPStan. + +Allowed types: ``array`` + +Default value: ``[]`` + ``scalar_types`` ~~~~~~~~~~~~~~~~ diff --git a/doc/rules/function_notation/phpdoc_to_return_type.rst b/doc/rules/function_notation/phpdoc_to_return_type.rst index 6c6cc7fc534..aac3839fb1a 100644 --- a/doc/rules/function_notation/phpdoc_to_return_type.rst +++ b/doc/rules/function_notation/phpdoc_to_return_type.rst @@ -27,6 +27,15 @@ Manual actions are required if inherited signatures are not properly documented. Configuration ------------- +``map_types`` +~~~~~~~~~~~~~ + +Map of custom types, eg template types from PHPStan. + +Allowed types: ``array`` + +Default value: ``[]`` + ``scalar_types`` ~~~~~~~~~~~~~~~~ diff --git a/src/AbstractPhpdocToTypeDeclarationFixer.php b/src/AbstractPhpdocToTypeDeclarationFixer.php index e67d27e5671..32f3becc35f 100644 --- a/src/AbstractPhpdocToTypeDeclarationFixer.php +++ b/src/AbstractPhpdocToTypeDeclarationFixer.php @@ -33,10 +33,12 @@ * * @phpstan-type _CommonTypeInfo array{commonType: string, isNullable: bool} * @phpstan-type _AutogeneratedInputConfiguration array{ + * map_types?: array, * scalar_types?: bool, * union_types?: bool * } * @phpstan-type _AutogeneratedComputedConfiguration array{ + * map_types: array, * scalar_types: bool, * union_types: bool * } @@ -95,6 +97,10 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn ->setAllowedTypes(['bool']) ->setDefault(\PHP_VERSION_ID >= 8_00_00) ->getOption(), + (new FixerOptionBuilder('map_types', 'Map of custom types, eg template types from PHPStan.')) + ->setAllowedTypes(['array']) + ->setDefault([]) + ->getOption(), ]); } @@ -213,6 +219,10 @@ protected function getCommonTypeInfo(TypeExpression $typesExpression, bool $isRe return null; } + if (\array_key_exists($commonType, $this->configuration['map_types'])) { + $commonType = $this->configuration['map_types'][$commonType]; + } + if (isset($this->scalarTypes[$commonType])) { if (false === $this->configuration['scalar_types']) { return null; diff --git a/src/Console/Command/CheckCommand.php b/src/Console/Command/CheckCommand.php index 1e7d7537f1b..cb261916b47 100644 --- a/src/Console/Command/CheckCommand.php +++ b/src/Console/Command/CheckCommand.php @@ -27,10 +27,8 @@ #[AsCommand(name: 'check', description: 'Checks if configured files/directories comply with configured rules.')] final class CheckCommand extends FixCommand { - /** @var string */ protected static $defaultName = 'check'; - /** @var string */ protected static $defaultDescription = 'Checks if configured files/directories comply with configured rules.'; public function __construct(ToolInfoInterface $toolInfo) diff --git a/src/Console/Command/DescribeCommand.php b/src/Console/Command/DescribeCommand.php index cf91f8493fc..752779e8670 100644 --- a/src/Console/Command/DescribeCommand.php +++ b/src/Console/Command/DescribeCommand.php @@ -56,7 +56,6 @@ #[AsCommand(name: 'describe')] final class DescribeCommand extends Command { - /** @var string */ protected static $defaultName = 'describe'; /** diff --git a/src/Console/Command/DocumentationCommand.php b/src/Console/Command/DocumentationCommand.php index e1adc98725f..a2e34695bcd 100644 --- a/src/Console/Command/DocumentationCommand.php +++ b/src/Console/Command/DocumentationCommand.php @@ -33,7 +33,6 @@ #[AsCommand(name: 'documentation')] final class DocumentationCommand extends Command { - /** @var string */ protected static $defaultName = 'documentation'; private Filesystem $filesystem; diff --git a/src/Console/Command/FixCommand.php b/src/Console/Command/FixCommand.php index cafb8d048d5..7c157f9580e 100644 --- a/src/Console/Command/FixCommand.php +++ b/src/Console/Command/FixCommand.php @@ -52,10 +52,8 @@ #[AsCommand(name: 'fix', description: 'Fixes a directory or a file.')] /* final */ class FixCommand extends Command { - /** @var string */ protected static $defaultName = 'fix'; - /** @var string */ protected static $defaultDescription = 'Fixes a directory or a file.'; private EventDispatcherInterface $eventDispatcher; diff --git a/src/Console/Command/HelpCommand.php b/src/Console/Command/HelpCommand.php index 192c735deb3..6dace362d50 100644 --- a/src/Console/Command/HelpCommand.php +++ b/src/Console/Command/HelpCommand.php @@ -32,7 +32,6 @@ #[AsCommand(name: 'help')] final class HelpCommand extends BaseHelpCommand { - /** @var string */ protected static $defaultName = 'help'; /** diff --git a/src/Console/Command/ListFilesCommand.php b/src/Console/Command/ListFilesCommand.php index dcfabd093bf..935491167a9 100644 --- a/src/Console/Command/ListFilesCommand.php +++ b/src/Console/Command/ListFilesCommand.php @@ -33,7 +33,6 @@ #[AsCommand(name: 'list-files')] final class ListFilesCommand extends Command { - /** @var string */ protected static $defaultName = 'list-files'; private ConfigInterface $defaultConfig; diff --git a/src/Console/Command/ListSetsCommand.php b/src/Console/Command/ListSetsCommand.php index 1f7e83fb727..97737a799c0 100644 --- a/src/Console/Command/ListSetsCommand.php +++ b/src/Console/Command/ListSetsCommand.php @@ -36,7 +36,6 @@ #[AsCommand(name: 'list-sets')] final class ListSetsCommand extends Command { - /** @var string */ protected static $defaultName = 'list-sets'; protected function configure(): void diff --git a/src/Console/Command/SelfUpdateCommand.php b/src/Console/Command/SelfUpdateCommand.php index 229e1c8dd96..ab2b90492b8 100644 --- a/src/Console/Command/SelfUpdateCommand.php +++ b/src/Console/Command/SelfUpdateCommand.php @@ -37,7 +37,6 @@ #[AsCommand(name: 'self-update')] final class SelfUpdateCommand extends Command { - /** @var string */ protected static $defaultName = 'self-update'; private NewVersionCheckerInterface $versionChecker; diff --git a/src/Console/Command/WorkerCommand.php b/src/Console/Command/WorkerCommand.php index 7ed694dd4d9..92f03bf91af 100644 --- a/src/Console/Command/WorkerCommand.php +++ b/src/Console/Command/WorkerCommand.php @@ -49,10 +49,8 @@ final class WorkerCommand extends Command /** @var string Prefix used before JSON-encoded error printed in the worker's process */ public const ERROR_PREFIX = 'WORKER_ERROR::'; - /** @var string */ protected static $defaultName = 'worker'; - /** @var string */ protected static $defaultDescription = 'Internal command for running fixers in parallel'; private ToolInfoInterface $toolInfo; diff --git a/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php b/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php index 9c1818387db..da726930b33 100644 --- a/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php +++ b/src/Fixer/FunctionNotation/PhpdocToParamTypeFixer.php @@ -30,10 +30,12 @@ * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @phpstan-type _AutogeneratedInputConfiguration array{ + * map_types?: array, * scalar_types?: bool, * union_types?: bool * } * @phpstan-type _AutogeneratedComputedConfiguration array{ + * map_types: array, * scalar_types: bool, * union_types: bool * } diff --git a/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php b/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php index df4d572abdf..7857154f46c 100644 --- a/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php +++ b/src/Fixer/FunctionNotation/PhpdocToPropertyTypeFixer.php @@ -30,10 +30,12 @@ * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @phpstan-type _AutogeneratedInputConfiguration array{ + * map_types?: array, * scalar_types?: bool, * union_types?: bool * } * @phpstan-type _AutogeneratedComputedConfiguration array{ + * map_types: array, * scalar_types: bool, * union_types: bool * } @@ -101,12 +103,12 @@ public function isCandidate(Tokens $tokens): bool /** * {@inheritdoc} * - * Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer. + * Must run before FullyQualifiedStrictTypesFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer. * Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer. */ public function getPriority(): int { - return 7; + return 8; } protected function isSkippedType(string $type): bool @@ -185,10 +187,27 @@ private function fixClass(Tokens $tokens, int $index): void [new Token([T_WHITESPACE, ' '])] ); - $tokens->insertAt(current($propertyIndices), $newTokens); + $firstPropertyIndex = current($propertyIndices); + $slices = [$firstPropertyIndex => $newTokens]; + + // handling of adding `= null` currently supports only _single_ declaration + if ($isNullable && 1 === \count($propertyIndices)) { + $nextTokenIndex = $tokens->getNextTokenOfKind($firstPropertyIndex, ['=', ';']); + if (null !== $nextTokenIndex && $tokens[$nextTokenIndex]->equals(';')) { + $slices[$nextTokenIndex] = [ + new Token([T_WHITESPACE, ' ']), + new Token('='), + new Token([T_WHITESPACE, ' ']), + new Token([T_STRING, 'null']), + ]; + } + } + + $tokens->insertSlices($slices); + $tokensInSlicesCount = \count($slices, COUNT_RECURSIVE) - \count($slices); - $index = max($propertyIndices) + \count($newTokens) + 1; - $classEndIndex += \count($newTokens); + $index = max($propertyIndices) + $tokensInSlicesCount + 1; + $classEndIndex += $tokensInSlicesCount; } } diff --git a/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php b/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php index 960b372795c..f6887792f06 100644 --- a/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php +++ b/src/Fixer/FunctionNotation/PhpdocToReturnTypeFixer.php @@ -32,10 +32,12 @@ * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> * * @phpstan-type _AutogeneratedInputConfiguration array{ + * map_types?: array, * scalar_types?: bool, * union_types?: bool * } * @phpstan-type _AutogeneratedComputedConfiguration array{ + * map_types: array, * scalar_types: bool, * union_types: bool * } diff --git a/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php b/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php index 763cfdf3430..62c28eef33d 100644 --- a/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php +++ b/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php @@ -217,7 +217,7 @@ public function foo(): \Other\FunctionReturnType * {@inheritdoc} * * Must run before NoSuperfluousPhpdocTagsFixer, OrderedAttributesFixer, OrderedImportsFixer, OrderedInterfacesFixer, StatementIndentationFixer. - * Must run after ClassKeywordFixer, PhpUnitAttributesFixer, PhpdocToReturnTypeFixer. + * Must run after ClassKeywordFixer, PhpUnitAttributesFixer, PhpdocToPropertyTypeFixer, PhpdocToReturnTypeFixer. */ public function getPriority(): int { diff --git a/tests/AutoReview/FixerFactoryTest.php b/tests/AutoReview/FixerFactoryTest.php index 1e71cf2adca..79f3127a01a 100644 --- a/tests/AutoReview/FixerFactoryTest.php +++ b/tests/AutoReview/FixerFactoryTest.php @@ -848,6 +848,7 @@ private static function getFixersPriorityGraph(): array 'no_superfluous_phpdoc_tags', ], 'phpdoc_to_property_type' => [ + 'fully_qualified_strict_types', 'no_superfluous_phpdoc_tags', ], 'phpdoc_to_return_type' => [ diff --git a/tests/AutoReview/ProjectCodeTest.php b/tests/AutoReview/ProjectCodeTest.php index 8034a90762f..44de22e448b 100644 --- a/tests/AutoReview/ProjectCodeTest.php +++ b/tests/AutoReview/ProjectCodeTest.php @@ -585,6 +585,10 @@ public function testAllCodeContainSingleClassy(string $className): void T_WHITESPACE, ]; + if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required + $headerTypes[] = T_READONLY; + } + $tokens = $this->createTokensForClass($className); $classyIndex = null; diff --git a/tests/Fixer/FunctionNotation/PhpdocToPropertyTypeFixerTest.php b/tests/Fixer/FunctionNotation/PhpdocToPropertyTypeFixerTest.php index 4ad4ae4b89d..863afac257b 100644 --- a/tests/Fixer/FunctionNotation/PhpdocToPropertyTypeFixerTest.php +++ b/tests/Fixer/FunctionNotation/PhpdocToPropertyTypeFixerTest.php @@ -173,25 +173,35 @@ class Foo { ]; yield 'nullable type' => [ - ' [ - ' [ - ' [ - ' [ + ' [ + ' [ ' */ private array $foo; }', ' */ private $foo; }', @@ -208,7 +218,7 @@ class Foo { ]; yield 'nullable array of types' => [ - ' [ - '