From af45c87fd44046a850cfc944b09b98a9fe6e2985 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 10 Aug 2026 21:07:17 +0200 Subject: [PATCH] [deprecation] Deprecate AddAssertArrayFromClassMethodDocblockRector, as runtime asserts from docblocks are risky and academic The rule turns a @param docblock type into a runtime Assert call in every method body. It pays a runtime cost on every call, and it trusts a docblock that is often stale or wrong, so the assert can fail on valid input. Write a custom rule if a project really wants this. --- config/set/assert.php | 10 - phpstan.neon | 1 + ...ArrayFromClassMethodDocblockRectorTest.php | 28 --- .../BeberleiTest.php | 28 --- .../Fixture/float_int_bool.php.inc | 38 ---- .../Fixture/multiple_parameters.php.inc | 36 ---- .../Fixture/simple_array.php.inc | 32 ---- .../Fixture/skip_already_set.php.inc | 14 -- .../Fixture/skip_no_array.php.inc | 13 -- .../FixtureBeberlei/beberlei_assert.php.inc | 39 ---- .../config/beberlei_assert.php | 13 -- .../config/configured_rule.php | 10 - rules/Assert/Enum/AssertClassName.php | 12 -- .../ExistingAssertStaticCallResolver.php | 55 ------ ...sertArrayFromClassMethodDocblockRector.php | 177 +----------------- src/Set/ValueObject/SetList.php | 5 - 16 files changed, 10 insertions(+), 501 deletions(-) delete mode 100644 config/set/assert.php delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/AddAssertArrayFromClassMethodDocblockRectorTest.php delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/BeberleiTest.php delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/float_int_bool.php.inc delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/multiple_parameters.php.inc delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/simple_array.php.inc delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/skip_already_set.php.inc delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/skip_no_array.php.inc delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/FixtureBeberlei/beberlei_assert.php.inc delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/config/beberlei_assert.php delete mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/config/configured_rule.php delete mode 100644 rules/Assert/Enum/AssertClassName.php delete mode 100644 rules/Assert/NodeAnalyzer/ExistingAssertStaticCallResolver.php diff --git a/config/set/assert.php b/config/set/assert.php deleted file mode 100644 index c98e4dc42d9..00000000000 --- a/config/set/assert.php +++ /dev/null @@ -1,10 +0,0 @@ -rules([AddAssertArrayFromClassMethodDocblockRector::class]); -}; diff --git a/phpstan.neon b/phpstan.neon index 7ea282e9f07..268e79a2ab6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -466,6 +466,7 @@ parameters: - '#Class "Rector\\CodeQuality\\Rector\\Concat\\JoinStringConcatRector" is missing @see annotation with test case class reference#' - '#Class "Rector\\CodeQuality\\Rector\\Switch_\\SwitchTrueToIfRector" is missing @see annotation with test case class reference#' - '#Class "Rector\\TypeDeclarationDocblocks\\Rector\\ClassMethod\\AddReturnDocblockForDimFetchArrayFromAssignsRector" is missing @see annotation with test case class reference#' + - '#Class "Rector\\Assert\\Rector\\ClassMethod\\AddAssertArrayFromClassMethodDocblockRector" is missing @see annotation with test case class reference#' # @todo fix in phpstan-rules - diff --git a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/AddAssertArrayFromClassMethodDocblockRectorTest.php b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/AddAssertArrayFromClassMethodDocblockRectorTest.php deleted file mode 100644 index 5bfabbcf375..00000000000 --- a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/AddAssertArrayFromClassMethodDocblockRectorTest.php +++ /dev/null @@ -1,28 +0,0 @@ -doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule.php'; - } -} diff --git a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/BeberleiTest.php b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/BeberleiTest.php deleted file mode 100644 index d688c57d432..00000000000 --- a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/BeberleiTest.php +++ /dev/null @@ -1,28 +0,0 @@ -doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/FixtureBeberlei'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/beberlei_assert.php'; - } -} diff --git a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/float_int_bool.php.inc b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/float_int_bool.php.inc deleted file mode 100644 index d7e47c0a078..00000000000 --- a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/float_int_bool.php.inc +++ /dev/null @@ -1,38 +0,0 @@ - ------ - diff --git a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/multiple_parameters.php.inc b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/multiple_parameters.php.inc deleted file mode 100644 index 40cb00376b5..00000000000 --- a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/multiple_parameters.php.inc +++ /dev/null @@ -1,36 +0,0 @@ - $items - * @param string[] $names - */ - public function run(array $items, array $names) - { - } -} - -?> ------ - $items - * @param string[] $names - */ - public function run(array $items, array $names) - { - \Webmozart\Assert\Assert::allString($items); - \Webmozart\Assert\Assert::allString(array_keys($items)); - \Webmozart\Assert\Assert::allString($names); - } -} - -?> diff --git a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/simple_array.php.inc b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/simple_array.php.inc deleted file mode 100644 index 693cec4da04..00000000000 --- a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/simple_array.php.inc +++ /dev/null @@ -1,32 +0,0 @@ - ------ - diff --git a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/skip_already_set.php.inc b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/skip_already_set.php.inc deleted file mode 100644 index b3163619454..00000000000 --- a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/skip_already_set.php.inc +++ /dev/null @@ -1,14 +0,0 @@ - ------ - diff --git a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/config/beberlei_assert.php b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/config/beberlei_assert.php deleted file mode 100644 index 3382248f654..00000000000 --- a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/config/beberlei_assert.php +++ /dev/null @@ -1,13 +0,0 @@ -ruleWithConfiguration(AddAssertArrayFromClassMethodDocblockRector::class, [ - AssertClassName::BEBERLEI, - ]); -}; diff --git a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/config/configured_rule.php b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/config/configured_rule.php deleted file mode 100644 index f8575aebe01..00000000000 --- a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/config/configured_rule.php +++ /dev/null @@ -1,10 +0,0 @@ -rule(AddAssertArrayFromClassMethodDocblockRector::class); -}; diff --git a/rules/Assert/Enum/AssertClassName.php b/rules/Assert/Enum/AssertClassName.php deleted file mode 100644 index 5efd9b28779..00000000000 --- a/rules/Assert/Enum/AssertClassName.php +++ /dev/null @@ -1,12 +0,0 @@ -stmts === null) { - return []; - } - - $existingAssertCallHashes = []; - $standard = new Standard(); - - foreach ($classMethod->stmts as $currentStmt) { - if (! $currentStmt instanceof Expression) { - continue; - } - - if (! $currentStmt->expr instanceof StaticCall) { - continue; - } - - $staticCall = $currentStmt->expr; - if (! $staticCall->class instanceof Name) { - continue; - } - - if (! in_array( - $staticCall->class->toString(), - [AssertClassName::WEBMOZART, AssertClassName::BEBERLEI], - true - )) { - continue; - } - - $existingAssertCallHashes[] = $standard->prettyPrintExpr($staticCall); - } - - return $existingAssertCallHashes; - } -} diff --git a/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php b/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php index 34d769278b4..13282bdc470 100644 --- a/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php +++ b/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php @@ -5,49 +5,19 @@ namespace Rector\Assert\Rector\ClassMethod; use PhpParser\Node; -use PhpParser\Node\Arg; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Expr\Variable; -use PhpParser\Node\Identifier; -use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Expression; -use PhpParser\PrettyPrinter\Standard; -use PHPStan\Type\ArrayType; -use PHPStan\Type\BooleanType; -use PHPStan\Type\FloatType; -use PHPStan\Type\IntegerType; -use PHPStan\Type\StringType; -use PHPStan\Type\Type; -use Rector\Assert\Enum\AssertClassName; -use Rector\Assert\NodeAnalyzer\ExistingAssertStaticCallResolver; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; +use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; use Rector\Contract\Rector\ConfigurableRectorInterface; -use Rector\PHPStan\ScopeFetcher; +use Rector\Exception\ShouldNotHappenException; use Rector\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; -use Webmozart\Assert\Assert; /** - * @experimental Check generic array key/value types in runtime with assert. Generics for impatient people. - * - * @see \Rector\Tests\Assert\Rector\ClassMethod\AddAssertArrayFromClassMethodDocblockRector\AddAssertArrayFromClassMethodDocblockRectorTest + * @deprecated This rule is deprecated, as turning a docblock type into a runtime assert is risky and academic. It adds runtime cost to every call and trusts a docblock that is often wrong. Write a custom rule if the project needs it. */ -final class AddAssertArrayFromClassMethodDocblockRector extends AbstractRector implements ConfigurableRectorInterface +final class AddAssertArrayFromClassMethodDocblockRector extends AbstractRector implements ConfigurableRectorInterface, DeprecatedInterface { - private string $assertClass = AssertClassName::WEBMOZART; - - public function __construct( - private readonly PhpDocInfoFactory $phpDocInfoFactory, - private readonly ExistingAssertStaticCallResolver $existingAssertStaticCallResolver - ) { - } - public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -86,7 +56,7 @@ public function run(array $items) } CODE_SAMPLE , - [AssertClassName::WEBMOZART] + ['Webmozart\Assert\Assert'] ), ] @@ -103,81 +73,10 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?ClassMethod { - $scope = ScopeFetcher::fetch($node); - if (! $scope->isInClass()) { - return null; - } - - if ($node->stmts === null || $node->isAbstract()) { - return null; - } - - $methodPhpDocInfo = $this->phpDocInfoFactory->createFromNode($node); - if (! $methodPhpDocInfo instanceof PhpDocInfo) { - return null; - } - - $paramTagValueNodes = $methodPhpDocInfo->getParamTagValueNodes(); - if ($paramTagValueNodes === []) { - return null; - } - - $assertStaticCallStmts = []; - - foreach ($node->getParams() as $param) { - if (! $param->type instanceof Identifier) { - continue; - } - - // handle arrays only - if (! $this->isName($param->type, 'array')) { - continue; - } - - if (! $param->var instanceof Variable) { - continue; - } - - $paramName = $param->var->name; - if (! is_string($paramName)) { - continue; - } - - $paramDocType = $methodPhpDocInfo->getParamType($paramName); - if (! $paramDocType instanceof ArrayType) { - continue; - } - - $valueAssertMethod = $this->matchTypeToAssertMethod($paramDocType->getItemType()); - - if (is_string($valueAssertMethod)) { - $assertStaticCallStmts[] = $this->createAssertExpression($param->var, $valueAssertMethod); - } - - $keyAssertMethod = $this->matchTypeToAssertMethod($paramDocType->getKeyType()); - - if (is_string($keyAssertMethod)) { - $arrayKeys = new FuncCall(new Name('array_keys'), [new Arg($param->var)]); - $assertStaticCallStmts[] = $this->createAssertExpression($arrayKeys, $keyAssertMethod); - } - } - - // filter existing assert to avoid duplication - if ($assertStaticCallStmts === []) { - return null; - } - - $existingAssertCallHashes = $this->existingAssertStaticCallResolver->resolve($node); - - $assertStaticCallStmts = $this->filterOutExistingStaticCall($assertStaticCallStmts, $existingAssertCallHashes); - - if ($assertStaticCallStmts === []) { - return null; - } - - $node->stmts = array_merge($assertStaticCallStmts, $node->stmts); - - return $node; + throw new ShouldNotHappenException(sprintf( + '"%s" rule is deprecated, as turning a docblock type into a runtime assert is risky and academic. Write a custom rule if the project needs it', + self::class + )); } /** @@ -185,63 +84,5 @@ public function refactor(Node $node): ?ClassMethod */ public function configure(array $configuration): void { - if ($configuration === []) { - // default - return; - } - - Assert::count($configuration, 1); - Assert::inArray($configuration[0], [AssertClassName::BEBERLEI, AssertClassName::WEBMOZART]); - - $this->assertClass = $configuration[0]; - } - - private function createAssertExpression(Expr $expr, string $methodName): Expression - { - $assertFullyQualified = new FullyQualified($this->assertClass); - - $staticCall = new StaticCall($assertFullyQualified, $methodName, [new Arg($expr)]); - - return new Expression($staticCall); - } - - /** - * @param Expression[] $assertStaticCallStmts - * @param string[] $existingAssertCallHashes - * @return Expression[] - */ - private function filterOutExistingStaticCall(array $assertStaticCallStmts, array $existingAssertCallHashes): array - { - $standard = new Standard(); - - return array_filter($assertStaticCallStmts, function (Expression $assertStaticCallExpression) use ( - $standard, - $existingAssertCallHashes - ): bool { - $currentStaticCallHash = $standard->prettyPrintExpr($assertStaticCallExpression->expr); - - return ! in_array($currentStaticCallHash, $existingAssertCallHashes, true); - }); - } - - private function matchTypeToAssertMethod(Type $type): ?string - { - if ($type instanceof IntegerType) { - return 'allInteger'; - } - - if ($type instanceof StringType) { - return 'allString'; - } - - if ($type instanceof FloatType) { - return 'allFloat'; - } - - if ($type instanceof BooleanType) { - return 'allBoolean'; - } - - return null; } } diff --git a/src/Set/ValueObject/SetList.php b/src/Set/ValueObject/SetList.php index c6fa325f779..b8d8d69c852 100644 --- a/src/Set/ValueObject/SetList.php +++ b/src/Set/ValueObject/SetList.php @@ -84,9 +84,4 @@ final class SetList public const string CARBON = __DIR__ . '/../../../config/set/datetime-to-carbon.php'; public const string BEHAT_ANNOTATIONS_TO_ATTRIBUTES = __DIR__ . '/../../../config/set/behat-annotations-to-attributes.php'; - - /** - * @experimental Check generic types in runtime with assert. Generics for impatient people. - */ - public const string ASSERT = __DIR__ . '/../../../config/set/assert.php'; }