diff --git a/composer.json b/composer.json index c915e1a1143..696ea08853f 100644 --- a/composer.json +++ b/composer.json @@ -73,7 +73,8 @@ "rules", "src" ], - "Rector\\Utils\\": "utils" + "Rector\\Utils\\": "utils", + "Rector\\Utils\\PHPStan\\": "utils/phpstan/src" }, "files": [ "src/functions/node_helper.php" @@ -86,6 +87,7 @@ "tests" ], "Rector\\Utils\\Tests\\": "utils-tests", + "Rector\\Utils\\PHPStan\\Tests\\": "utils/phpstan/tests", "E2e\\Parallel\\Reflection\\Resolver\\": [ "e2e/parallel-reflection-resolver/src/", "e2e/no-parallel-reflection-resolver/src" diff --git a/config/set/php-polyfills.php b/config/set/php-polyfills.php index 8b9bd56921f..53794b4824f 100644 --- a/config/set/php-polyfills.php +++ b/config/set/php-polyfills.php @@ -7,8 +7,16 @@ use Rector\Php73\Rector\FuncCall\ArrayKeyFirstLastRector; use Rector\Php80\Rector\Identical\StrEndsWithRector; use Rector\Php80\Rector\Identical\StrStartsWithRector; +use Rector\Php80\Rector\NotIdentical\MbStrContainsRector; use Rector\Php80\Rector\NotIdentical\StrContainsRector; use Rector\Php80\Rector\Ternary\GetDebugTypeRector; +use Rector\Php83\Rector\BooleanAnd\JsonValidateRector; +use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector; +use Rector\Php84\Rector\Class_\DeprecatedAnnotationToDeprecatedAttributeRector; +use Rector\Php84\Rector\Foreach_\ForeachToArrayAllRector; +use Rector\Php84\Rector\Foreach_\ForeachToArrayAnyRector; +use Rector\Php84\Rector\Foreach_\ForeachToArrayFindKeyRector; +use Rector\Php84\Rector\Foreach_\ForeachToArrayFindRector; // @note longer rule registration must be used here, to separate from withRules() from root rector.php @@ -22,5 +30,17 @@ StrStartsWithRector::class, StrEndsWithRector::class, StrContainsRector::class, + MbStrContainsRector::class, + + // PHP 8.3 + JsonValidateRector::class, + AddOverrideAttributeToOverriddenMethodsRector::class, + + // PHP 8.4 + ForeachToArrayAllRector::class, + ForeachToArrayAnyRector::class, + ForeachToArrayFindRector::class, + ForeachToArrayFindKeyRector::class, + DeprecatedAnnotationToDeprecatedAttributeRector::class, ]); }; diff --git a/phpstan.neon b/phpstan.neon index 7ee0150788f..6585a348409 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,6 +2,14 @@ includes: - vendor/symplify/phpstan-rules/config/symplify-rules.neon - vendor/symplify/phpstan-rules/config/rector-rules.neon +services: + - + class: Rector\Utils\PHPStan\Rule\RegisterRelatedPolyfillRectorRule + arguments: + polyfillConfigFilePath: %currentWorkingDirectory%/config/set/php-polyfills.php + tags: + - phpstan.rules.rule + parameters: level: 8 diff --git a/phpunit.xml b/phpunit.xml index deaec9b43a2..a1286aa25c1 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -14,6 +14,7 @@ tests rules-tests utils-tests + utils/phpstan/tests rules-tests/Php83/Rector/ClassMethod/AddOverrideAttributeToOverriddenMethodsRector/Source/SomeAbstractTest.php diff --git a/rules/DeadCode/NodeAnalyzer/IsClassMethodUsedAnalyzer.php b/rules/DeadCode/NodeAnalyzer/IsClassMethodUsedAnalyzer.php index f1eca7b9ef7..3af10f7cd9e 100644 --- a/rules/DeadCode/NodeAnalyzer/IsClassMethodUsedAnalyzer.php +++ b/rules/DeadCode/NodeAnalyzer/IsClassMethodUsedAnalyzer.php @@ -167,14 +167,10 @@ private function doesMethodExistInTrait(ClassMethod $classMethod, string $classM $traits = $this->astResolver->parseClassReflectionTraits($classReflection); $className = $classReflection->getName(); - - foreach ($traits as $trait) { - if ($this->isUsedByTrait($trait, $classMethodName, $className)) { - return true; - } - } - - return false; + return array_any( + $traits, + fn (Trait_ $trait): bool => $this->isUsedByTrait($trait, $classMethodName, $className) + ); } private function isUsedByTrait(Trait_ $trait, string $classMethodName, string $className): bool diff --git a/rules/DeadCode/Rector/If_/RemoveTypedPropertyDeadInstanceOfRector.php b/rules/DeadCode/Rector/If_/RemoveTypedPropertyDeadInstanceOfRector.php index 6edae6c5282..2a52fed02b9 100644 --- a/rules/DeadCode/Rector/If_/RemoveTypedPropertyDeadInstanceOfRector.php +++ b/rules/DeadCode/Rector/If_/RemoveTypedPropertyDeadInstanceOfRector.php @@ -208,13 +208,6 @@ private function isInPropertyPromotedParams(Class_ $class, PropertyFetch|StaticP /** @var string $propertyName */ $propertyName = $this->getName($propertyFetch); $params = $this->promotedPropertyResolver->resolveFromClass($class); - - foreach ($params as $param) { - if ($this->isName($param, $propertyName)) { - return true; - } - } - - return false; + return array_any($params, fn (Node $param): bool => $this->isName($param, $propertyName)); } } diff --git a/rules/Php55/Rector/String_/StringClassNameToClassConstantRector.php b/rules/Php55/Rector/String_/StringClassNameToClassConstantRector.php index 267e07d6dcf..07d3d371347 100644 --- a/rules/Php55/Rector/String_/StringClassNameToClassConstantRector.php +++ b/rules/Php55/Rector/String_/StringClassNameToClassConstantRector.php @@ -4,6 +4,7 @@ namespace Rector\Php55\Rector\String_; +use Deprecated; use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Name\FullyQualified; @@ -23,9 +24,7 @@ */ final class StringClassNameToClassConstantRector extends AbstractRector implements MinPhpVersionInterface, ConfigurableRectorInterface { - /** - * @deprecated since 2.2.12. Default behavior now. - */ + #[Deprecated(message: 'since 2.2.12. Default behavior now.')] public const string SHOULD_KEEP_PRE_SLASH = 'should_keep_pre_slash'; /** diff --git a/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php b/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php index 44a4576129b..6376539f8fa 100644 --- a/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php +++ b/rules/Php80/NodeAnalyzer/PhpAttributeAnalyzer.php @@ -48,13 +48,10 @@ public function hasPhpAttributes( Property | ClassLike | ClassMethod | Function_ | Param $node, array $attributeClasses ): bool { - foreach ($attributeClasses as $attributeClass) { - if ($this->hasPhpAttribute($node, $attributeClass)) { - return true; - } - } - - return false; + return array_any( + $attributeClasses, + fn (string $attributeClass): bool => $this->hasPhpAttribute($node, $attributeClass) + ); } /** diff --git a/rules/Php80/NodeResolver/SwitchExprsResolver.php b/rules/Php80/NodeResolver/SwitchExprsResolver.php index af6178fb640..4e7a5ec99e4 100644 --- a/rules/Php80/NodeResolver/SwitchExprsResolver.php +++ b/rules/Php80/NodeResolver/SwitchExprsResolver.php @@ -160,12 +160,6 @@ private function isValidCase(Case_ $case): bool private function areCasesValid(Switch_ $newSwitch): bool { - foreach ($newSwitch->cases as $case) { - if (! $this->isValidCase($case)) { - return false; - } - } - - return true; + return array_all($newSwitch->cases, fn (Case_ $case): bool => $this->isValidCase($case)); } } diff --git a/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php b/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php index eeac9d2aa78..cf8da7d4600 100644 --- a/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php +++ b/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php @@ -22,14 +22,16 @@ use Rector\PhpParser\Node\Value\ValueResolver; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; +use Rector\ValueObject\PolyfillPackage; use Rector\VersionBonding\Contract\MinPhpVersionInterface; +use Rector\VersionBonding\Contract\RelatedPolyfillInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAllRector\ForeachToArrayAllRectorTest */ -final class ForeachToArrayAllRector extends AbstractRector implements MinPhpVersionInterface +final class ForeachToArrayAllRector extends AbstractRector implements MinPhpVersionInterface, RelatedPolyfillInterface { public function __construct( private readonly ValueResolver $valueResolver, @@ -102,6 +104,11 @@ public function provideMinPhpVersion(): int return PhpVersionFeature::ARRAY_ALL; } + public function providePolyfillPackage(): string + { + return PolyfillPackage::PHP_84; + } + /** * @param StmtsAware $stmtsAware */ diff --git a/rules/Php84/Rector/Foreach_/ForeachToArrayAnyRector.php b/rules/Php84/Rector/Foreach_/ForeachToArrayAnyRector.php index e12d4140398..ca3dfaef1e3 100644 --- a/rules/Php84/Rector/Foreach_/ForeachToArrayAnyRector.php +++ b/rules/Php84/Rector/Foreach_/ForeachToArrayAnyRector.php @@ -21,14 +21,16 @@ use Rector\PhpParser\Node\Value\ValueResolver; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; +use Rector\ValueObject\PolyfillPackage; use Rector\VersionBonding\Contract\MinPhpVersionInterface; +use Rector\VersionBonding\Contract\RelatedPolyfillInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayAnyRector\ForeachToArrayAnyRectorTest */ -final class ForeachToArrayAnyRector extends AbstractRector implements MinPhpVersionInterface +final class ForeachToArrayAnyRector extends AbstractRector implements MinPhpVersionInterface, RelatedPolyfillInterface { public function __construct( private readonly ValueResolver $valueResolver, @@ -101,6 +103,11 @@ public function provideMinPhpVersion(): int return PhpVersionFeature::ARRAY_ANY; } + public function providePolyfillPackage(): string + { + return PolyfillPackage::PHP_84; + } + /** * @param StmtsAware $stmtsAware */ diff --git a/rules/Php84/Rector/Foreach_/ForeachToArrayFindKeyRector.php b/rules/Php84/Rector/Foreach_/ForeachToArrayFindKeyRector.php index 10223631be0..2e89fd7011c 100644 --- a/rules/Php84/Rector/Foreach_/ForeachToArrayFindKeyRector.php +++ b/rules/Php84/Rector/Foreach_/ForeachToArrayFindKeyRector.php @@ -20,14 +20,16 @@ use Rector\PhpParser\Node\Value\ValueResolver; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; +use Rector\ValueObject\PolyfillPackage; use Rector\VersionBonding\Contract\MinPhpVersionInterface; +use Rector\VersionBonding\Contract\RelatedPolyfillInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayFindKeyRector\ForeachToArrayFindKeyRectorTest */ -final class ForeachToArrayFindKeyRector extends AbstractRector implements MinPhpVersionInterface +final class ForeachToArrayFindKeyRector extends AbstractRector implements MinPhpVersionInterface, RelatedPolyfillInterface { public function __construct( private readonly ValueResolver $valueResolver, @@ -165,6 +167,11 @@ public function provideMinPhpVersion(): int return PhpVersionFeature::ARRAY_FIND_KEY; } + public function providePolyfillPackage(): string + { + return PolyfillPackage::PHP_84; + } + private function isValidForeachStructure(Foreach_ $foreach, Variable $assignedVariable): bool { if ( diff --git a/rules/Php84/Rector/Foreach_/ForeachToArrayFindRector.php b/rules/Php84/Rector/Foreach_/ForeachToArrayFindRector.php index 82d73649e79..ddfd5d6b957 100644 --- a/rules/Php84/Rector/Foreach_/ForeachToArrayFindRector.php +++ b/rules/Php84/Rector/Foreach_/ForeachToArrayFindRector.php @@ -19,14 +19,16 @@ use Rector\PhpParser\Node\Value\ValueResolver; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; +use Rector\ValueObject\PolyfillPackage; use Rector\VersionBonding\Contract\MinPhpVersionInterface; +use Rector\VersionBonding\Contract\RelatedPolyfillInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see \Rector\Tests\Php84\Rector\Foreach_\ForeachToArrayFindRector\ForeachToArrayFindRectorTest */ -final class ForeachToArrayFindRector extends AbstractRector implements MinPhpVersionInterface +final class ForeachToArrayFindRector extends AbstractRector implements MinPhpVersionInterface, RelatedPolyfillInterface { public function __construct( private readonly ValueResolver $valueResolver, @@ -160,6 +162,11 @@ public function provideMinPhpVersion(): int return PhpVersionFeature::ARRAY_FIND; } + public function providePolyfillPackage(): string + { + return PolyfillPackage::PHP_84; + } + private function isValidForeachStructure(Foreach_ $foreach, Variable $assignedVariable): bool { if (count($foreach->stmts) !== 1) { diff --git a/rules/TypeDeclaration/NodeAnalyzer/NeverFuncCallAnalyzer.php b/rules/TypeDeclaration/NodeAnalyzer/NeverFuncCallAnalyzer.php index 0ca20bab991..f0a55482043 100644 --- a/rules/TypeDeclaration/NodeAnalyzer/NeverFuncCallAnalyzer.php +++ b/rules/TypeDeclaration/NodeAnalyzer/NeverFuncCallAnalyzer.php @@ -21,13 +21,7 @@ public function __construct( public function hasNeverFuncCall(ClassMethod | Closure | Function_ $functionLike): bool { - foreach ((array) $functionLike->stmts as $stmt) { - if ($this->isWithNeverTypeExpr($stmt)) { - return true; - } - } - - return false; + return array_any((array) $functionLike->stmts, fn (Stmt $stmt): bool => $this->isWithNeverTypeExpr($stmt)); } public function isWithNeverTypeExpr(Stmt $stmt, bool $withNativeNeverType = true): bool diff --git a/rules/TypeDeclaration/NodeAnalyzer/StrictTypeSafetyChecker.php b/rules/TypeDeclaration/NodeAnalyzer/StrictTypeSafetyChecker.php index 1a7e787db3f..d5b03cf3587 100644 --- a/rules/TypeDeclaration/NodeAnalyzer/StrictTypeSafetyChecker.php +++ b/rules/TypeDeclaration/NodeAnalyzer/StrictTypeSafetyChecker.php @@ -62,13 +62,7 @@ public function isFileStrictTypeSafe(FileNode $fileNode): bool } $assigns = $this->betterNodeFinder->findInstanceOf($fileNode->stmts, Assign::class); - foreach ($assigns as $assign) { - if (! $this->isPropertyAssignSafe($assign)) { - return false; - } - } - - return true; + return array_all($assigns, fn (Assign $assign): bool => $this->isPropertyAssignSafe($assign)); } private function isCallLikeSafe(CallLike $callLike): bool diff --git a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php index e004b3c55db..6b89fc9ac3b 100644 --- a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php +++ b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php @@ -168,13 +168,7 @@ public function hasByType(string $type): bool */ public function hasByTypes(array $types): bool { - foreach ($types as $type) { - if ($this->hasByType($type)) { - return true; - } - } - - return false; + return array_any($types, fn (string $type): bool => $this->hasByType($type)); } /** @@ -182,13 +176,7 @@ public function hasByTypes(array $types): bool */ public function hasByNames(array $names): bool { - foreach ($names as $name) { - if ($this->hasByName($name)) { - return true; - } - } - - return false; + return array_any($names, fn (string $name): bool => $this->hasByName($name)); } public function hasByName(string $name): bool diff --git a/src/Configuration/VendorMissAnalyseGuard.php b/src/Configuration/VendorMissAnalyseGuard.php index d0261e8e4e0..172781fcf95 100644 --- a/src/Configuration/VendorMissAnalyseGuard.php +++ b/src/Configuration/VendorMissAnalyseGuard.php @@ -23,15 +23,13 @@ public function isVendorAnalyzed(array $filePaths): bool private function hasDowngradeSets(): bool { + /** @var string[] $registeredRectorSets */ $registeredRectorSets = SimpleParameterProvider::provideArrayParameter(Option::REGISTERED_RECTOR_SETS); - foreach ($registeredRectorSets as $registeredRectorSet) { - if (str_contains((string) $registeredRectorSet, 'downgrade-')) { - return true; - } - } - - return false; + return array_any( + $registeredRectorSets, + fn (string $registeredRectorSet): bool => str_contains($registeredRectorSet, 'downgrade-') + ); } /** diff --git a/src/CustomRules/SimpleNodeDumper.php b/src/CustomRules/SimpleNodeDumper.php index 1cbe1b9773c..5bee4c4bf3c 100644 --- a/src/CustomRules/SimpleNodeDumper.php +++ b/src/CustomRules/SimpleNodeDumper.php @@ -81,13 +81,7 @@ public static function dump(array|Node $node, bool $rootNode = true): string */ private static function isStringList(array $items): bool { - foreach ($items as $item) { - if (! is_string($item)) { - return false; - } - } - - return true; + return array_all($items, fn (mixed $item): bool => is_string($item)); } private static function dumpFlags(mixed $flags): string diff --git a/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php b/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php index e882449fa7e..cfd91937ce9 100644 --- a/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php +++ b/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php @@ -27,7 +27,10 @@ public function hasAbstractParentClassMethod(ClassReflection $classReflection, s return false; } - return array_any($parentClassMethods, fn ($parentClassMethod) => $parentClassMethod->isAbstract()); + return array_any( + $parentClassMethods, + fn (PhpMethodReflection $phpMethodReflection): bool => $phpMethodReflection->isAbstract() + ); } /** diff --git a/src/NodeAnalyzer/PropertyAnalyzer.php b/src/NodeAnalyzer/PropertyAnalyzer.php index ecbf9dfec96..a491e412391 100644 --- a/src/NodeAnalyzer/PropertyAnalyzer.php +++ b/src/NodeAnalyzer/PropertyAnalyzer.php @@ -34,13 +34,7 @@ public function hasForbiddenType(Property $property): bool } $types = $propertyType->getTypes(); - foreach ($types as $type) { - if ($this->isForbiddenType($type)) { - return true; - } - } - - return false; + return array_any($types, fn (Type $type): bool => $this->isForbiddenType($type)); } public function isForbiddenType(Type $type): bool diff --git a/src/NodeTypeResolver/NodeTypeResolver.php b/src/NodeTypeResolver/NodeTypeResolver.php index 2e9de65d57e..bbbddbe1164 100644 --- a/src/NodeTypeResolver/NodeTypeResolver.php +++ b/src/NodeTypeResolver/NodeTypeResolver.php @@ -98,13 +98,7 @@ public function __construct( */ public function isObjectTypes(Node $node, array $requiredTypes): bool { - foreach ($requiredTypes as $requiredType) { - if ($this->isObjectType($node, $requiredType)) { - return true; - } - } - - return false; + return array_any($requiredTypes, fn (ObjectType $objectType): bool => $this->isObjectType($node, $objectType)); } public function isObjectType(Node $node, ObjectType $requiredObjectType): bool diff --git a/src/PhpParser/Comparing/NodeComparator.php b/src/PhpParser/Comparing/NodeComparator.php index 04ffe9e5006..d9d5323c149 100644 --- a/src/PhpParser/Comparing/NodeComparator.php +++ b/src/PhpParser/Comparing/NodeComparator.php @@ -63,13 +63,10 @@ public function areNodesEqual(Node | array | null $firstNode, Node | array | nul */ public function isNodeEqual(Node $singleNode, array $availableNodes): bool { - foreach ($availableNodes as $availableNode) { - if ($this->areNodesEqual($singleNode, $availableNode)) { - return true; - } - } - - return false; + return array_any( + $availableNodes, + fn (Node|array|null $availableNode): bool => $this->areNodesEqual($singleNode, $availableNode) + ); } /** diff --git a/src/PhpParser/Node/Value/ValueResolver.php b/src/PhpParser/Node/Value/ValueResolver.php index 9fe7f1413e3..77a9b830dec 100644 --- a/src/PhpParser/Node/Value/ValueResolver.php +++ b/src/PhpParser/Node/Value/ValueResolver.php @@ -117,13 +117,7 @@ public function getValue(Arg|Expr|InterpolatedStringPart $expr, bool $resolvedCl */ public function isValues(Expr $expr, array $expectedValues): bool { - foreach ($expectedValues as $expectedValue) { - if ($this->isValue($expr, $expectedValue)) { - return true; - } - } - - return false; + return array_any($expectedValues, fn (mixed $expectedValue): bool => $this->isValue($expr, $expectedValue)); } public function isFalse(Expr $expr): bool diff --git a/src/PostRector/Collector/UseNodesToAddCollector.php b/src/PostRector/Collector/UseNodesToAddCollector.php index c845f1334a2..784c785b043 100644 --- a/src/PostRector/Collector/UseNodesToAddCollector.php +++ b/src/PostRector/Collector/UseNodesToAddCollector.php @@ -4,6 +4,7 @@ namespace Rector\PostRector\Collector; +use Deprecated; use Rector\Application\Provider\CurrentFileProvider; use Rector\PhpParser\Node\FileNode; use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType; @@ -72,9 +73,8 @@ public function __construct( /** * @api - * - * @deprecated Use $file->getFileNode()->getPendingImports()->addUseImport($type) instead. */ + #[Deprecated(message: 'Use $file->getFileNode()->getPendingImports()->addUseImport($type) instead.')] public function addUseImport(FullyQualifiedObjectType $fullyQualifiedObjectType): void { $this->warn('addUseImport()', '$file->getFileNode()->getPendingImports()->addUseImport($type)'); @@ -90,9 +90,8 @@ public function addUseImport(FullyQualifiedObjectType $fullyQualifiedObjectType) /** * @api - * - * @deprecated Use $file->getFileNode()->getPendingImports()->addConstantUseImport($type) instead. */ + #[Deprecated(message: 'Use $file->getFileNode()->getPendingImports()->addConstantUseImport($type) instead.')] public function addConstantUseImport(FullyQualifiedObjectType $fullyQualifiedObjectType): void { $this->warn('addConstantUseImport()', '$file->getFileNode()->getPendingImports()->addConstantUseImport($type)'); @@ -108,9 +107,8 @@ public function addConstantUseImport(FullyQualifiedObjectType $fullyQualifiedObj /** * @api - * - * @deprecated Use $file->getFileNode()->getPendingImports()->addFunctionUseImport($type) instead. */ + #[Deprecated(message: 'Use $file->getFileNode()->getPendingImports()->addFunctionUseImport($type) instead.')] public function addFunctionUseImport(FullyQualifiedObjectType $fullyQualifiedObjectType): void { $this->warn('addFunctionUseImport()', '$file->getFileNode()->getPendingImports()->addFunctionUseImport($type)'); @@ -127,10 +125,9 @@ public function addFunctionUseImport(FullyQualifiedObjectType $fullyQualifiedObj /** * @api * - * @deprecated Use $file->getFileNode()->resolveUsedImportTypes() instead. - * * @return array */ + #[Deprecated(message: 'Use $file->getFileNode()->resolveUsedImportTypes() instead.')] public function getUseImportTypesByNode(File $file): array { $this->warn('getUseImportTypesByNode()', '$file->getFileNode()->resolveUsedImportTypes()'); @@ -145,9 +142,8 @@ public function getUseImportTypesByNode(File $file): array /** * @api - * - * @deprecated Use $file->getFileNode()->hasImport($type) instead. */ + #[Deprecated(message: 'Use $file->getFileNode()->hasImport($type) instead.')] public function hasImport(File $file, FullyQualifiedObjectType $fullyQualifiedObjectType): bool { $this->warn('hasImport()', '$file->getFileNode()->hasImport($type)'); @@ -162,9 +158,8 @@ public function hasImport(File $file, FullyQualifiedObjectType $fullyQualifiedOb /** * @api - * - * @deprecated Use $file->getFileNode()->getPendingImports()->isShortImported($type) instead. */ + #[Deprecated(message: 'Use $file->getFileNode()->getPendingImports()->isShortImported($type) instead.')] public function isShortImported(File $file, FullyQualifiedObjectType $fullyQualifiedObjectType): bool { $this->warn('isShortImported()', '$file->getFileNode()->getPendingImports()->isShortImported($type)'); @@ -180,9 +175,8 @@ public function isShortImported(File $file, FullyQualifiedObjectType $fullyQuali /** * @api - * - * @deprecated Use $file->getFileNode()->getPendingImports()->isImportShortable($type) instead. */ + #[Deprecated(message: 'Use $file->getFileNode()->getPendingImports()->isImportShortable($type) instead.')] public function isImportShortable(File $file, FullyQualifiedObjectType $fullyQualifiedObjectType): bool { $this->warn('isImportShortable()', '$file->getFileNode()->getPendingImports()->isImportShortable($type)'); @@ -199,10 +193,9 @@ public function isImportShortable(File $file, FullyQualifiedObjectType $fullyQua /** * @api * - * @deprecated Use $file->getFileNode()->getPendingImports()->getUseImports() instead. - * * @return FullyQualifiedObjectType[] */ + #[Deprecated(message: 'Use $file->getFileNode()->getPendingImports()->getUseImports() instead.')] public function getObjectImportsByFilePath(string $filePath): array { $this->warn('getObjectImportsByFilePath()', '$file->getFileNode()->getPendingImports()->getUseImports()'); @@ -219,10 +212,9 @@ public function getObjectImportsByFilePath(string $filePath): array /** * @api * - * @deprecated Use $file->getFileNode()->getPendingImports()->getConstantImports() instead. - * * @return FullyQualifiedObjectType[] */ + #[Deprecated(message: 'Use $file->getFileNode()->getPendingImports()->getConstantImports() instead.')] public function getConstantImportsByFilePath(string $filePath): array { $this->warn( @@ -242,10 +234,9 @@ public function getConstantImportsByFilePath(string $filePath): array /** * @api * - * @deprecated Use $file->getFileNode()->getPendingImports()->getFunctionImports() instead. - * * @return FullyQualifiedObjectType[] */ + #[Deprecated(message: 'Use $file->getFileNode()->getPendingImports()->getFunctionImports() instead.')] public function getFunctionImportsByFilePath(string $filePath): array { $this->warn( diff --git a/src/Rector/AbstractRector.php b/src/Rector/AbstractRector.php index f472a380b5c..5dd55bf149e 100644 --- a/src/Rector/AbstractRector.php +++ b/src/Rector/AbstractRector.php @@ -4,6 +4,7 @@ namespace Rector\Rector; +use Deprecated; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Name; @@ -171,9 +172,7 @@ final public function enterNode(Node $node): int|Node|null|array return $this->postRefactorProcess($originalNode, $node, $refactoredNodeOrState, $filePath); } - /** - * @deprecated no longer used - */ + #[Deprecated(message: 'no longer used')] final public function leaveNode(Node $node): array|int|Node|null { return null; diff --git a/src/Set/ValueObject/SetList.php b/src/Set/ValueObject/SetList.php index e86985a3106..03d0833c95a 100644 --- a/src/Set/ValueObject/SetList.php +++ b/src/Set/ValueObject/SetList.php @@ -4,6 +4,8 @@ namespace Rector\Set\ValueObject; +use Deprecated; + /** * @api */ @@ -20,9 +22,7 @@ final class SetList public const string DEAD_CODE = __DIR__ . '/../../../config/set/dead-code.php'; - /** - * @deprecated As too strict and not practical. Use code quality and coding style sets instead. - */ + #[Deprecated(message: 'As too strict and not practical. Use code quality and coding style sets instead.')] public const string STRICT_BOOLEANS = __DIR__ . '/../../../config/set/strict-booleans.php'; public const string GMAGICK_TO_IMAGICK = __DIR__ . '/../../../config/set/gmagick-to-imagick.php'; diff --git a/utils/phpstan/src/Rule/RegisterRelatedPolyfillRectorRule.php b/utils/phpstan/src/Rule/RegisterRelatedPolyfillRectorRule.php new file mode 100644 index 00000000000..847bd241074 --- /dev/null +++ b/utils/phpstan/src/Rule/RegisterRelatedPolyfillRectorRule.php @@ -0,0 +1,70 @@ + + * @see \Rector\Utils\PHPStan\Tests\Rule\RegisterRelatedPolyfillRectorRule\RegisterRelatedPolyfillRectorRuleTest + */ +final readonly class RegisterRelatedPolyfillRectorRule implements Rule +{ + private const string ERROR_MESSAGE = 'Class "%s" implements RelatedPolyfillInterface, but is not registered in config/set/php-polyfills.php. Register it there.'; + + public function __construct( + private string $polyfillConfigFilePath + ) { + } + + public function getNodeType(): string + { + return InClassNode::class; + } + + /** + * @param InClassNode $node + * @return list + */ + public function processNode(Node $node, Scope $scope): array + { + $classReflection = $node->getClassReflection(); + if (! $classReflection->implementsInterface(RelatedPolyfillInterface::class)) { + return []; + } + + $className = $classReflection->getName(); + if ($this->isRegistered($className)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $className)) + ->identifier('rector.registerRelatedPolyfill') + ->build(), + ]; + } + + private function isRegistered(string $className): bool + { + $configFileContents = file_get_contents($this->polyfillConfigFilePath); + if ($configFileContents === false) { + return false; + } + + $shortClassName = substr((string) strrchr($className, '\\'), 1); + + return str_contains($configFileContents, $shortClassName . '::class'); + } +} diff --git a/utils/phpstan/tests/Rule/RegisterRelatedPolyfillRectorRule/RegisterRelatedPolyfillRectorRuleTest.php b/utils/phpstan/tests/Rule/RegisterRelatedPolyfillRectorRule/RegisterRelatedPolyfillRectorRuleTest.php new file mode 100644 index 00000000000..e1e8e25adba --- /dev/null +++ b/utils/phpstan/tests/Rule/RegisterRelatedPolyfillRectorRule/RegisterRelatedPolyfillRectorRuleTest.php @@ -0,0 +1,32 @@ + + */ +final class RegisterRelatedPolyfillRectorRuleTest extends RuleTestCase +{ + public function testUnregistered(): void + { + $expectedErrorMessage = 'Class "Rector\Utils\PHPStan\Tests\Rule\RegisterRelatedPolyfillRectorRule\Source\UnregisteredPolyfillRector" implements RelatedPolyfillInterface, but is not registered in config/set/php-polyfills.php. Register it there.'; + + $this->analyse([__DIR__ . '/Source/UnregisteredPolyfillRector.php'], [[$expectedErrorMessage, 10]]); + } + + public function testRegistered(): void + { + $this->analyse([__DIR__ . '/Source/RegisteredPolyfillRector.php'], []); + } + + protected function getRule(): Rule + { + return new RegisterRelatedPolyfillRectorRule(__DIR__ . '/config/some_polyfill_set.php'); + } +} diff --git a/utils/phpstan/tests/Rule/RegisterRelatedPolyfillRectorRule/Source/RegisteredPolyfillRector.php b/utils/phpstan/tests/Rule/RegisterRelatedPolyfillRectorRule/Source/RegisteredPolyfillRector.php new file mode 100644 index 00000000000..fefb8bc0959 --- /dev/null +++ b/utils/phpstan/tests/Rule/RegisterRelatedPolyfillRectorRule/Source/RegisteredPolyfillRector.php @@ -0,0 +1,16 @@ +