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 @@
+