From 9df9d12f4e00278d03febde68609c225680dfb4f Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 1 Sep 2025 14:20:07 +0200 Subject: [PATCH 1/3] add ... --- .../AddAssertArrayFromClassMethodDocblockRector.php | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php diff --git a/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php b/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php new file mode 100644 index 00000000000..0b56cd90834 --- /dev/null +++ b/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php @@ -0,0 +1,10 @@ + Date: Mon, 1 Sep 2025 15:10:24 +0200 Subject: [PATCH 2/3] [assert] Kick of experimental rule, AddAssertArrayFromClassMethodDocblockRector --- ...ArrayFromClassMethodDocblockRectorTest.php | 28 +++ .../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 ++ .../config/configured_rule.php | 10 + rules/Assert/Enum/AssertClassName.php | 13 ++ .../ExistingAssertStaticCallResolver.php | 51 +++++ ...sertArrayFromClassMethodDocblockRector.php | 186 +++++++++++++++++- .../Rector/FuncCall/ChrArgModuloRector.php | 4 +- 10 files changed, 384 insertions(+), 3 deletions(-) create mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/AddAssertArrayFromClassMethodDocblockRectorTest.php create mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/multiple_parameters.php.inc create mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/simple_array.php.inc create mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/skip_already_set.php.inc create mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/skip_no_array.php.inc create mode 100644 rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/config/configured_rule.php create mode 100644 rules/Assert/Enum/AssertClassName.php create mode 100644 rules/Assert/NodeAnalyzer/ExistingAssertStaticCallResolver.php diff --git a/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/AddAssertArrayFromClassMethodDocblockRectorTest.php b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/AddAssertArrayFromClassMethodDocblockRectorTest.php new file mode 100644 index 00000000000..5bfabbcf375 --- /dev/null +++ b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/AddAssertArrayFromClassMethodDocblockRectorTest.php @@ -0,0 +1,28 @@ +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/Fixture/multiple_parameters.php.inc b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/multiple_parameters.php.inc new file mode 100644 index 00000000000..40cb00376b5 --- /dev/null +++ b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/multiple_parameters.php.inc @@ -0,0 +1,36 @@ + $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 new file mode 100644 index 00000000000..693cec4da04 --- /dev/null +++ b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/simple_array.php.inc @@ -0,0 +1,32 @@ + +----- + 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 new file mode 100644 index 00000000000..b3163619454 --- /dev/null +++ b/rules-tests/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector/Fixture/skip_already_set.php.inc @@ -0,0 +1,14 @@ +rule(AddAssertArrayFromClassMethodDocblockRector::class); +}; diff --git a/rules/Assert/Enum/AssertClassName.php b/rules/Assert/Enum/AssertClassName.php new file mode 100644 index 00000000000..9a653249215 --- /dev/null +++ b/rules/Assert/Enum/AssertClassName.php @@ -0,0 +1,13 @@ +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 ($staticCall->class->toString() !== AssertClassName::ASSERT) { + continue; + } + + $existingAssertCallHashes[] = $standard->prettyPrintExpr($staticCall); + } + + return $existingAssertCallHashes; + } +} diff --git a/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php b/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php index 0b56cd90834..b100402de5f 100644 --- a/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php +++ b/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php @@ -1,10 +1,194 @@ 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 ($param->type->name !== 'array') { + continue; + } + + if (! $param->var instanceof Expr\Variable) { + continue; + } + + $paramName = $param->var->name; + if (! is_string($paramName)) { + continue; + } + + $paramDocType = $methodPhpDocInfo->getParamType($paramName); + if (! $paramDocType instanceof ArrayType) { + continue; + } + + // assert value + if ($paramDocType->getItemType() instanceof IntegerType) { + $assertStaticCallStmts[] = $this->createAssertExpression($param->var, 'allInteger'); + } elseif ($paramDocType->getItemType() instanceof StringType) { + $assertStaticCallStmts[] = $this->createAssertExpression($param->var, 'allString'); + } + + // assert keys + $arrayKeys = new FuncCall(new Name('array_keys'), [new Arg($param->var)]); + if ($paramDocType->getKeyType() instanceof StringType) { + $assertStaticCallStmts[] = $this->createAssertExpression($arrayKeys, 'allString'); + } elseif ($paramDocType->getKeyType() instanceof IntegerType) { + $assertStaticCallStmts[] = $this->createAssertExpression($arrayKeys, 'allInteger'); + } + } + + // 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; + } + + private function createAssertExpression(Expr $expr, string $methodName): Expression + { + $staticCall = new StaticCall(new FullyQualified(AssertClassName::ASSERT), $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); + }); + } } diff --git a/rules/Php85/Rector/FuncCall/ChrArgModuloRector.php b/rules/Php85/Rector/FuncCall/ChrArgModuloRector.php index 8a36f3085c7..73af63c27ab 100644 --- a/rules/Php85/Rector/FuncCall/ChrArgModuloRector.php +++ b/rules/Php85/Rector/FuncCall/ChrArgModuloRector.php @@ -23,7 +23,7 @@ final class ChrArgModuloRector extends AbstractRector implements MinPhpVersionIn { public function __construct( private readonly ValueResolver $valueResolver - ){ + ) { } @@ -80,7 +80,7 @@ public function refactor(Node $node): ?Node return null; } - if ( $value >= 0 && $value <= 255) { + if ($value >= 0 && $value <= 255) { return null; } From a4e7c6458525aebb3158108904be35f19c393361 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 1 Sep 2025 13:59:30 +0000 Subject: [PATCH 3/3] [ci-review] Rector Rectify --- .../AddAssertArrayFromClassMethodDocblockRector.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php b/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php index b100402de5f..c4143c794a8 100644 --- a/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php +++ b/rules/Assert/Rector/ClassMethod/AddAssertArrayFromClassMethodDocblockRector.php @@ -4,6 +4,7 @@ namespace Rector\Assert\Rector\ClassMethod; +use PhpParser\Node\Expr\Variable; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr; @@ -118,7 +119,7 @@ public function refactor(Node $node): ?ClassMethod continue; } - if (! $param->var instanceof Expr\Variable) { + if (! $param->var instanceof Variable) { continue; }