forked from symplify/phpstan-rules
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRequireStringArgumentInConstructorRule.php
131 lines (114 loc) · 3.46 KB
/
RequireStringArgumentInConstructorRule.php
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
131
<?php
declare(strict_types=1);
namespace Symplify\PHPStanRules\Rules;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Scalar\String_;
use PHPStan\Analyser\Scope;
use Symplify\Astral\Naming\SimpleNameResolver;
use Symplify\Astral\TypeAnalyzer\ContainsTypeAnalyser;
use Symplify\RuleDocGenerator\Contract\ConfigurableRuleInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* Useful for prefixed phar build, to keep original references to class un-prefixed
*
* @see \Symplify\PHPStanRules\Tests\Rules\RequireStringArgumentInConstructorRule\RequireStringArgumentInConstructorRuleTest
*/
final class RequireStringArgumentInConstructorRule extends AbstractSymplifyRule implements ConfigurableRuleInterface
{
/**
* @var string
*/
public const ERROR_MESSAGE = 'Use quoted string in constructor "new %s()" argument on position %d instead of "::class. It prevent scoping of the class in building prefixed package.';
/**
* @param array<class-string, array<int>> $stringArgPositionsByType
*/
public function __construct(
private SimpleNameResolver $simpleNameResolver,
private ContainsTypeAnalyser $containsTypeAnalyser,
private array $stringArgPositionsByType
) {
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [New_::class];
}
/**
* @param New_ $node
* @return string[]
*/
public function process(Node $node, Scope $scope): array
{
$errorMessages = [];
foreach ($this->stringArgPositionsByType as $type => $positions) {
if (! $this->containsTypeAnalyser->containsExprType($node, $scope, $type)) {
continue;
}
foreach ($node->args as $key => $arg) {
if (! $arg instanceof Arg) {
continue;
}
if ($this->shouldSkipArg($key, $positions, $arg)) {
continue;
}
$errorMessages[] = sprintf(self::ERROR_MESSAGE, $type, $key);
}
}
return $errorMessages;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(self::ERROR_MESSAGE, [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
class AnotherClass
{
public function run()
{
new SomeClass(YetAnotherClass:class);
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class AnotherClass
{
public function run()
{
new SomeClass('YetAnotherClass');
}
}
CODE_SAMPLE
,
[
'stringArgPositionsByType' => [
'SomeClass' => [0],
],
]
),
]);
}
/**
* @param int[] $positions
*/
private function shouldSkipArg(int $key, array $positions, Arg $arg): bool
{
if (! in_array($key, $positions, true)) {
return true;
}
if ($arg->value instanceof String_) {
return true;
}
$classConstFetch = $arg->value;
if (! $classConstFetch instanceof ClassConstFetch) {
return true;
}
return ! $this->simpleNameResolver->isName($classConstFetch->name, 'class');
}
}