forked from symplify/phpstan-rules
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathIfNewTypeThenImplementInterfaceRule.php
132 lines (114 loc) · 3.43 KB
/
IfNewTypeThenImplementInterfaceRule.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
132
<?php
declare(strict_types=1);
namespace Symplify\PHPStanRules\Rules;
use PhpParser\Node;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\NodeFinder;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;
use PHPStan\Reflection\ClassReflection;
use Symplify\Astral\Naming\SimpleNameResolver;
use Symplify\RuleDocGenerator\Contract\ConfigurableRuleInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Symplify\PHPStanRules\Tests\Rules\IfNewTypeThenImplementInterfaceRule\IfNewTypeThenImplementInterfaceRuleTest
*/
final class IfNewTypeThenImplementInterfaceRule extends AbstractSymplifyRule implements ConfigurableRuleInterface
{
/**
* @var string
*/
public const ERROR_MESSAGE = 'Class must implement "%s" interface';
/**
* @param array<string, string> $interfacesByNewTypes
*/
public function __construct(
private NodeFinder $nodeFinder,
private SimpleNameResolver $simpleNameResolver,
private array $interfacesByNewTypes
) {
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [InClassNode::class];
}
/**
* @param InClassNode $node
* @return string[]
*/
public function process(Node $node, Scope $scope): array
{
$classLike = $node->getOriginalNode();
if (! $classLike instanceof Class_) {
return [];
}
$expectedInterface = $this->resolveExpectedInterface($classLike);
if ($expectedInterface === null) {
return [];
}
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return [];
}
if ($classReflection->implementsInterface($expectedInterface)) {
return [];
}
$errorMessage = sprintf(self::ERROR_MESSAGE, $expectedInterface);
return [$errorMessage];
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(self::ERROR_MESSAGE, [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
class SomeRule
{
public function run()
{
return new ConfiguredCodeSample('...');
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeRule implements ConfiguredRuleInterface
{
public function run()
{
return new ConfiguredCodeSample('...');
}
}
CODE_SAMPLE
,
[
'interfacesByNewTypes' => [
'ConfiguredCodeSample' => 'ConfiguredRuleInterface',
],
]
),
]);
}
private function resolveExpectedInterface(Class_ $class): ?string
{
$expectedInterface = null;
$this->nodeFinder->findFirst($class, function (Node $node) use (&$expectedInterface): bool {
if (! $node instanceof New_) {
return false;
}
foreach ($this->interfacesByNewTypes as $newType => $interface) {
if (! $this->simpleNameResolver->isName($node->class, $newType)) {
continue;
}
$expectedInterface = $interface;
return true;
}
return false;
});
return $expectedInterface;
}
}