Skip to content

Commit 6d32f5f

Browse files
committed
[type-declaration-docblocks] Add AddReturnDocblockForJsonArrayRector
1 parent 5781d97 commit 6d32f5f

10 files changed

Lines changed: 313 additions & 1 deletion

File tree

config/set/type-declaration-docblocks.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDimFetchAccessRector;
1515
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForArrayDimAssignedObjectRector;
1616
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector;
17+
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector;
1718
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector;
1819
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector;
1920

@@ -38,6 +39,7 @@
3839
AddReturnDocblockForScalarArrayFromAssignsRector::class,
3940
DocblockReturnArrayFromDirectArrayInstanceRector::class,
4041
AddReturnDocblockForArrayDimAssignedObjectRector::class,
42+
AddReturnDocblockForJsonArrayRector::class,
4143

4244
// tests
4345
AddParamArrayDocblockFromDataProviderRector::class,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class AddReturnDocblockForJsonArrayRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector\Fixture;
6+
7+
use Nette\Utils\Json;
8+
9+
final class JsonUtils
10+
{
11+
public function provide(string $contents): array
12+
{
13+
return Json::decode($contents, true);
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
declare(strict_types=1);
22+
23+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector\Fixture;
24+
25+
use Nette\Utils\Json;
26+
27+
final class JsonUtils
28+
{
29+
/**
30+
* @return array<string, mixed>
31+
*/
32+
public function provide(string $contents): array
33+
{
34+
return Json::decode($contents, true);
35+
}
36+
}
37+
38+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector\Fixture;
6+
7+
final class SkipAlreadyFilled
8+
{
9+
/**
10+
* @return array<string, array<string, mixed>>
11+
*/
12+
public function provide(string $contents): array
13+
{
14+
return json_decode($contents, true);
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector\Fixture;
6+
7+
final class SkipMissingArg
8+
{
9+
public function provide(string $contents): array
10+
{
11+
return json_decode($contents, false);
12+
}
13+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector\Fixture;
6+
7+
final class SomeClass
8+
{
9+
public function provide(string $contents): array
10+
{
11+
return json_decode($contents, true);
12+
}
13+
}
14+
15+
?>
16+
-----
17+
<?php
18+
19+
declare(strict_types=1);
20+
21+
namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector\Fixture;
22+
23+
final class SomeClass
24+
{
25+
/**
26+
* @return array<string, mixed>
27+
*/
28+
public function provide(string $contents): array
29+
{
30+
return json_decode($contents, true);
31+
}
32+
}
33+
34+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([AddReturnDocblockForJsonArrayRector::class]);

rules/NetteUtils/Rector/StaticCall/UtilsJsonStaticCallNamedArgRector.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PhpParser\Node\Expr\StaticCall;
1010
use PhpParser\Node\Identifier;
1111
use Rector\Rector\AbstractRector;
12+
use Rector\TypeDeclarationDocblocks\Enum\NetteClassName;
1213
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
1314
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
1415

@@ -49,7 +50,7 @@ public function getNodeTypes(): array
4950
*/
5051
public function refactor(Node $node): ?Node
5152
{
52-
if (! $this->isName($node->class, 'Nette\Utils\Json')) {
53+
if (! $this->isName($node->class, NetteClassName::JSON)) {
5354
return null;
5455
}
5556

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclarationDocblocks\Enum;
6+
7+
final class NetteClassName
8+
{
9+
/**
10+
* @var string
11+
*/
12+
public const JSON = 'Nette\Utils\Json';
13+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclarationDocblocks\Rector\ClassMethod;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\FuncCall;
9+
use PhpParser\Node\Stmt\ClassMethod;
10+
use PhpParser\Node\Stmt\Function_;
11+
use PhpParser\Node\Stmt\Return_;
12+
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
13+
use PHPStan\Type\ArrayType;
14+
use PHPStan\Type\MixedType;
15+
use PHPStan\Type\StringType;
16+
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
17+
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
18+
use Rector\PhpParser\Node\Value\ValueResolver;
19+
use Rector\Rector\AbstractRector;
20+
use Rector\TypeDeclarationDocblocks\Enum\NetteClassName;
21+
use Rector\TypeDeclarationDocblocks\NodeFinder\ReturnNodeFinder;
22+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
23+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
24+
25+
/**
26+
* @see \Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForJsonArrayRector\AddReturnDocblockForJsonArrayRectorTest
27+
*/
28+
final class AddReturnDocblockForJsonArrayRector extends AbstractRector
29+
{
30+
public function __construct(
31+
private readonly PhpDocInfoFactory $phpDocInfoFactory,
32+
private readonly ReturnNodeFinder $returnNodeFinder,
33+
private readonly PhpDocTypeChanger $phpDocTypeChanger,
34+
private readonly ValueResolver $valueResolver
35+
) {
36+
}
37+
38+
public function getRuleDefinition(): RuleDefinition
39+
{
40+
return new RuleDefinition(
41+
'Add @return docblock for array based on return of json_decode() return array',
42+
[
43+
new CodeSample(
44+
<<<'CODE_SAMPLE'
45+
final class SomeClass
46+
{
47+
public function provide(string $contents): array
48+
{
49+
return json_decode($contents, true);
50+
}
51+
}
52+
CODE_SAMPLE
53+
,
54+
<<<'CODE_SAMPLE'
55+
final class SomeClass
56+
{
57+
/**
58+
* @return array<string, mixed>
59+
*/
60+
public function provide(string $contents): array
61+
{
62+
return json_decode($contents, true);
63+
}
64+
}
65+
CODE_SAMPLE
66+
),
67+
]
68+
);
69+
}
70+
71+
/**
72+
* @return array<class-string<Node>>
73+
*/
74+
public function getNodeTypes(): array
75+
{
76+
return [ClassMethod::class, Function_::class];
77+
}
78+
79+
/**
80+
* @param ClassMethod|Function_ $node
81+
*/
82+
public function refactor(Node $node): ?Node
83+
{
84+
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
85+
$returnType = $phpDocInfo->getReturnType();
86+
87+
if (! $returnType instanceof MixedType || $returnType->isExplicitMixed()) {
88+
return null;
89+
}
90+
91+
// definitely not an array return
92+
if ($node->returnType instanceof Node && ! $this->isName($node->returnType, 'array')) {
93+
return null;
94+
}
95+
96+
$onlyReturnWithExpr = $this->returnNodeFinder->findOnlyReturnWithExpr($node);
97+
if (! $onlyReturnWithExpr instanceof Return_) {
98+
return null;
99+
}
100+
101+
$returnedExpr = $onlyReturnWithExpr->expr;
102+
if (! $this->isJsonDecodeToArray($returnedExpr)) {
103+
return null;
104+
}
105+
106+
$classMethodDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
107+
// already filled
108+
if ($classMethodDocInfo->getReturnTagValue() instanceof ReturnTagValueNode) {
109+
return null;
110+
}
111+
112+
$hasChanged = $this->phpDocTypeChanger->changeReturnType(
113+
$node,
114+
$phpDocInfo,
115+
new ArrayType(new StringType(), new MixedType())
116+
);
117+
if (! $hasChanged) {
118+
return null;
119+
}
120+
121+
return $node;
122+
}
123+
124+
private function isJsonDecodeToArray(Node\Expr $expr): bool
125+
{
126+
if ($expr instanceof FuncCall) {
127+
if (! $this->isName($expr, 'json_decode')) {
128+
return false;
129+
}
130+
131+
if (count($expr->getArgs()) !== 2) {
132+
return false;
133+
}
134+
135+
$secondArg = $expr->getArgs()[1];
136+
return $this->valueResolver->isTrue($secondArg->value);
137+
}
138+
139+
if ($expr instanceof Node\Expr\StaticCall) {
140+
if (! $this->isName($expr->class, NetteClassName::JSON)) {
141+
return false;
142+
}
143+
144+
if (! $this->isName($expr->name, 'decode')) {
145+
return false;
146+
}
147+
148+
if (count($expr->getArgs()) !== 2) {
149+
return false;
150+
}
151+
152+
$secondArg = $expr->getArgs()[1];
153+
return $this->valueResolver->isTrue($secondArg->value);
154+
}
155+
156+
return false;
157+
}
158+
}

0 commit comments

Comments
 (0)