diff --git a/config/set/type-declaration-docblocks.php b/config/set/type-declaration-docblocks.php index 0db20f012bf..26d2bb82f80 100644 --- a/config/set/type-declaration-docblocks.php +++ b/config/set/type-declaration-docblocks.php @@ -14,6 +14,7 @@ use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDimFetchAccessRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForArrayDimAssignedObjectRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector; +use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector; @@ -38,6 +39,7 @@ AddReturnDocblockForScalarArrayFromAssignsRector::class, DocblockReturnArrayFromDirectArrayInstanceRector::class, AddReturnDocblockForArrayDimAssignedObjectRector::class, + AddReturnDocblockForJsonArrayRector::class, // tests AddParamArrayDocblockFromDataProviderRector::class, diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/AddReturnDocblockForJsonArrayRectorTest.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/AddReturnDocblockForJsonArrayRectorTest.php new file mode 100644 index 00000000000..b4f2a909e35 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/AddReturnDocblockForJsonArrayRectorTest.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/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/Fixture/json_utils.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/Fixture/json_utils.php.inc new file mode 100644 index 00000000000..705920b65dc --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/Fixture/json_utils.php.inc @@ -0,0 +1,38 @@ + +----- + + */ + public function provide(string $contents): array + { + return Json::decode($contents, true); + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/Fixture/skip_already_filled.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/Fixture/skip_already_filled.php.inc new file mode 100644 index 00000000000..0f33e855bc5 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/Fixture/skip_already_filled.php.inc @@ -0,0 +1,16 @@ +> + */ + public function provide(string $contents): array + { + return json_decode($contents, true); + } +} diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/Fixture/skip_missing_arg.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/Fixture/skip_missing_arg.php.inc new file mode 100644 index 00000000000..960d3db80bc --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/Fixture/skip_missing_arg.php.inc @@ -0,0 +1,13 @@ + +----- + + */ + public function provide(string $contents): array + { + return json_decode($contents, true); + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/config/configured_rule.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/config/configured_rule.php new file mode 100644 index 00000000000..296575c8aba --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForJsonArrayRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([AddReturnDocblockForJsonArrayRector::class]); diff --git a/rules/NetteUtils/Rector/StaticCall/UtilsJsonStaticCallNamedArgRector.php b/rules/NetteUtils/Rector/StaticCall/UtilsJsonStaticCallNamedArgRector.php index 37a1ef71bdd..ef5aade25d6 100644 --- a/rules/NetteUtils/Rector/StaticCall/UtilsJsonStaticCallNamedArgRector.php +++ b/rules/NetteUtils/Rector/StaticCall/UtilsJsonStaticCallNamedArgRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Identifier; use Rector\Rector\AbstractRector; +use Rector\TypeDeclarationDocblocks\Enum\NetteClassName; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -49,7 +50,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->isName($node->class, 'Nette\Utils\Json')) { + if (! $this->isName($node->class, NetteClassName::JSON)) { return null; } diff --git a/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php b/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php index de0c9230f88..a3a977e2dad 100644 --- a/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php +++ b/rules/Php84/Rector/Foreach_/ForeachToArrayAllRector.php @@ -102,14 +102,14 @@ public function provideMinPhpVersion(): int return PhpVersionFeature::ARRAY_ALL; } - private function refactorBooleanAssignmentPattern(StmtsAwareInterface $node): ?Node + private function refactorBooleanAssignmentPattern(StmtsAwareInterface $stmtsAware): ?Node { - foreach ($node->stmts as $key => $stmt) { + foreach ($stmtsAware->stmts as $key => $stmt) { if (! $stmt instanceof Foreach_) { continue; } - $prevStmt = $node->stmts[$key - 1] ?? null; + $prevStmt = $stmtsAware->stmts[$key - 1] ?? null; if (! $prevStmt instanceof Expression) { continue; } @@ -136,7 +136,7 @@ private function refactorBooleanAssignmentPattern(StmtsAwareInterface $node): ?N } if ($this->stmtsManipulator->isVariableUsedInNextStmt( - $node, + $stmtsAware, $key + 1, (string) $this->getName($foreach->valueVar) )) { @@ -174,26 +174,26 @@ private function refactorBooleanAssignmentPattern(StmtsAwareInterface $node): ?N $newAssign = new Assign($assignedVariable, $funcCall); $newExpression = new Expression($newAssign); - unset($node->stmts[$key - 1]); - $node->stmts[$key] = $newExpression; + unset($stmtsAware->stmts[$key - 1]); + $stmtsAware->stmts[$key] = $newExpression; - $node->stmts = array_values($node->stmts); + $stmtsAware->stmts = array_values($stmtsAware->stmts); - return $node; + return $stmtsAware; } return null; } - private function refactorEarlyReturnPattern(StmtsAwareInterface $node): ?Node + private function refactorEarlyReturnPattern(StmtsAwareInterface $stmtsAware): ?Node { - foreach ($node->stmts as $key => $stmt) { + foreach ($stmtsAware->stmts as $key => $stmt) { if (! $stmt instanceof Foreach_) { continue; } $foreach = $stmt; - $nextStmt = $node->stmts[$key + 1] ?? null; + $nextStmt = $stmtsAware->stmts[$key + 1] ?? null; if (! $nextStmt instanceof Return_) { continue; @@ -236,11 +236,11 @@ private function refactorEarlyReturnPattern(StmtsAwareInterface $node): ?Node $funcCall = $this->nodeFactory->createFuncCall('array_all', [$foreach->expr, $arrowFunction]); - $node->stmts[$key] = new Return_($funcCall); - unset($node->stmts[$key + 1]); - $node->stmts = array_values($node->stmts); + $stmtsAware->stmts[$key] = new Return_($funcCall); + unset($stmtsAware->stmts[$key + 1]); + $stmtsAware->stmts = array_values($stmtsAware->stmts); - return $node; + return $stmtsAware; } return null; diff --git a/rules/TypeDeclarationDocblocks/Enum/NetteClassName.php b/rules/TypeDeclarationDocblocks/Enum/NetteClassName.php new file mode 100644 index 00000000000..93fe01c0101 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/Enum/NetteClassName.php @@ -0,0 +1,13 @@ + + */ + public function provide(string $contents): array + { + return json_decode($contents, true); + } +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Function_::class]; + } + + /** + * @param ClassMethod|Function_ $node + */ + public function refactor(Node $node): ?Node + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + $returnType = $phpDocInfo->getReturnType(); + + if (! $returnType instanceof MixedType || $returnType->isExplicitMixed()) { + return null; + } + + // definitely not an array return + if ($node->returnType instanceof Node && ! $this->isName($node->returnType, 'array')) { + return null; + } + + $onlyReturnWithExpr = $this->returnNodeFinder->findOnlyReturnWithExpr($node); + if (! $onlyReturnWithExpr instanceof Return_) { + return null; + } + + $returnedExpr = $onlyReturnWithExpr->expr; + if (! $returnedExpr instanceof Expr) { + return null; + } + + if (! $this->isJsonDecodeToArray($returnedExpr)) { + return null; + } + + $classMethodDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + // already filled + if ($classMethodDocInfo->getReturnTagValue() instanceof ReturnTagValueNode) { + return null; + } + + $hasChanged = $this->phpDocTypeChanger->changeReturnType( + $node, + $phpDocInfo, + new ArrayType(new StringType(), new MixedType()) + ); + + if (! $hasChanged) { + return null; + } + + return $node; + } + + private function isJsonDecodeToArray(Expr $expr): bool + { + if ($expr instanceof FuncCall) { + if (! $this->isName($expr, 'json_decode')) { + return false; + } + + if (count($expr->getArgs()) !== 2) { + return false; + } + + $secondArg = $expr->getArgs()[1]; + return $this->valueResolver->isTrue($secondArg->value); + } + + if ($expr instanceof StaticCall) { + if (! $this->isName($expr->class, NetteClassName::JSON)) { + return false; + } + + if (! $this->isName($expr->name, 'decode')) { + return false; + } + + if (count($expr->getArgs()) !== 2) { + return false; + } + + $secondArg = $expr->getArgs()[1]; + return $this->valueResolver->isTrue($secondArg->value); + } + + return false; + } +}