Repository navigation
Expand file tree
/
Copy pathAssertIssetToSpecificMethodRector.php
More file actions
130 lines (112 loc) · 3.86 KB
/
Copy pathAssertIssetToSpecificMethodRector.php
File metadata and controls
130 lines (112 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<?php
declare(strict_types=1);
namespace Rector\PHPUnit\CodeQuality\Rector\MethodCall;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Type\ObjectType;
use Rector\PHPUnit\Enum\AssertMethod;
use Rector\PHPUnit\NodeAnalyzer\IdentifierManipulator;
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\RuleDoc\CodeSample\CodeSample;
use Rector\RuleDoc\RuleDefinition;
/**
* @see \Rector\PHPUnit\Tests\CodeQuality\Rector\MethodCall\AssertIssetToSpecificMethodRector\AssertIssetToSpecificMethodRectorTest
*/
final class AssertIssetToSpecificMethodRector extends AbstractRector
{
/**
* @var string[]
*/
private const array SUPER_GLOBAL_VARIABLE_NAMES = [
'GLOBALS',
'_SERVER',
'_GET',
'_POST',
'_FILES',
'_COOKIE',
'_SESSION',
'_REQUEST',
'_ENV',
];
public function __construct(
private readonly IdentifierManipulator $identifierManipulator,
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Turns assertTrue() + isset() comparisons to more precise assertArrayHasKey() method',
[
new CodeSample(
'$this->assertTrue(isset($anything["foo"]), "message");',
'$this->assertArrayHasKey("foo", $anything, "message");'
),
]
);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [MethodCall::class, StaticCall::class];
}
/**
* @param MethodCall|StaticCall $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->testsNodeAnalyzer->isPHPUnitMethodCallNames(
$node,
[AssertMethod::ASSERT_TRUE, AssertMethod::ASSERT_FALSE]
)) {
return null;
}
if ($node->isFirstClassCallable()) {
return null;
}
$firstArg = $node->getArgs()[0];
$firstArgumentValue = $firstArg->value;
// is property access
if (! $firstArgumentValue instanceof Isset_) {
return null;
}
$issetExpr = $firstArgumentValue->vars[0];
if (! $issetExpr instanceof ArrayDimFetch) {
return null;
}
// Keep the non-evaluating behavior of isset() for global state. A superglobal
// may be unavailable or unset, while passing it directly evaluates the variable.
if ($this->isNames($issetExpr->var, self::SUPER_GLOBAL_VARIABLE_NAMES)) {
return null;
}
// isset() on an ArrayAccess object is not equivalent to assertArrayHasKey():
// the key may be any type (assertArrayHasKey() requires int|string) and offsetExists()
// semantics can differ from array_key_exists()
if ($this->isObjectType($issetExpr->var, new ObjectType('ArrayAccess'))) {
return null;
}
return $this->refactorArrayDimFetchNode($node, $issetExpr);
}
/**
* @template TNode of MethodCall|StaticCall
* @param TNode $node
* @return TNode
*/
private function refactorArrayDimFetchNode(MethodCall|StaticCall $node, ArrayDimFetch $arrayDimFetch): MethodCall|StaticCall
{
$this->identifierManipulator->renameNodeWithMap($node, [
AssertMethod::ASSERT_TRUE => 'assertArrayHasKey',
AssertMethod::ASSERT_FALSE => 'assertArrayNotHasKey',
]);
$oldArgs = $node->getArgs();
unset($oldArgs[0]);
$node->args = [...$this->nodeFactory->createArgs([$arrayDimFetch->dim, $arrayDimFetch->var]), ...$oldArgs];
return $node;
}
}