1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace DependencyAnalyzer\DependencyGraphBuilder; |
5
|
|
|
|
6
|
|
|
use DependencyAnalyzer\DependencyDumper; |
7
|
|
|
use DependencyAnalyzer\DependencyGraph\ExtraPhpDocTags\DepsInternal; |
8
|
|
|
use DependencyAnalyzer\DependencyGraph\ExtraPhpDocTags\Internal; |
9
|
|
|
use DependencyAnalyzer\DependencyGraph\FullyQualifiedStructuralElementName as FQSEN; |
10
|
|
|
use DependencyAnalyzer\Exceptions\InvalidFullyQualifiedStructureElementNameException; |
11
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; |
12
|
|
|
use PHPStan\PhpDocParser\Lexer\Lexer; |
13
|
|
|
use PHPStan\PhpDocParser\Parser\PhpDocParser; |
14
|
|
|
use PHPStan\PhpDocParser\Parser\TokenIterator; |
15
|
|
|
use PHPStan\Reflection\ClassReflection; |
16
|
|
|
|
17
|
|
|
class ExtraPhpDocTagResolver |
18
|
|
|
{ |
19
|
|
|
const ONLY_USED_BY_TAGS = '@canOnlyUsedBy'; |
20
|
|
|
const DEPENDER_TAGS = '@dependee'; |
21
|
|
|
const DEPENDEE_TAGS = '@dependee'; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var Lexer |
25
|
|
|
*/ |
26
|
|
|
protected $phpDocLexer; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var PhpDocParser |
30
|
|
|
*/ |
31
|
|
|
protected $phpDocParser; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var ObserverInterface |
35
|
|
|
*/ |
36
|
|
|
protected $observer = null; |
37
|
|
|
|
38
|
|
|
public function __construct(Lexer $phpDocLexer, PhpDocParser $phpDocParser) |
39
|
|
|
{ |
40
|
|
|
$this->phpDocLexer = $phpDocLexer; |
41
|
|
|
$this->phpDocParser = $phpDocParser; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
public function setObserver(ObserverInterface $observer): void |
45
|
|
|
{ |
46
|
|
|
$this->observer = $observer; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
protected function notifyError(string $file, string $fqsen, InvalidFullyQualifiedStructureElementNameException $exception) |
50
|
|
|
{ |
51
|
|
|
if (!is_null($this->observer)) { |
52
|
|
|
$this->observer->notifyResolvePhpDocError($file, $fqsen, $exception); |
53
|
|
|
} |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @param \ReflectionClass $reflectionClass |
58
|
|
|
* @return DepsInternal[] |
59
|
|
|
*/ |
60
|
|
|
public function resolveDepsInternalTag(\ReflectionClass $reflectionClass): array |
61
|
|
|
{ |
62
|
|
|
$ret = []; |
63
|
|
|
|
64
|
|
|
foreach ($this->collectReflectionsHavingTag($reflectionClass, DepsInternal::getTagName()) as $reflection) { |
65
|
|
|
try { |
66
|
|
|
$ret[] = new DepsInternal( |
67
|
|
|
FQSEN::createFromReflection($reflection), |
68
|
|
|
$this->resolve($reflection->getDocComment(), DepsInternal::getTagName()) |
69
|
|
|
); |
70
|
|
|
} catch (InvalidFullyQualifiedStructureElementNameException $e) { |
71
|
|
|
$this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e); |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
return $ret; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @param \ReflectionClass $reflectionClass |
80
|
|
|
* @return Internal[] |
81
|
|
|
*/ |
82
|
|
|
public function resolveInternalTag(\ReflectionClass $reflectionClass): array |
83
|
|
|
{ |
84
|
|
|
$ret = []; |
85
|
|
|
$package = $this->resolvePackageTag($reflectionClass) ?? |
|
|
|
|
86
|
|
|
FQSEN::createFromReflection($reflectionClass)->getFullyQualifiedNamespaceName()->toString(); |
87
|
|
|
|
88
|
|
|
foreach ($this->collectReflectionsHavingTag($reflectionClass, Internal::getTagName()) as $reflection) { |
89
|
|
|
try { |
90
|
|
|
$ret[] = new Internal(FQSEN::createFromReflection($reflection), $package); |
91
|
|
|
} catch (InvalidFullyQualifiedStructureElementNameException $e) { |
92
|
|
|
$this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e); |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
return $ret; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
public function resolvePackageTag(\ReflectionClass $reflectionClass): ?string |
100
|
|
|
{ |
101
|
|
|
if ($this->haveTag($reflectionClass, '@package')) { |
102
|
|
|
try { |
103
|
|
|
$ret[] = new DepsInternal( |
|
|
|
|
104
|
|
|
FQSEN::createClass($reflectionClass->getName()), |
105
|
|
|
$this->resolve($reflectionClass->getDocComment(), '@package') |
|
|
|
|
106
|
|
|
); |
107
|
|
|
} catch (InvalidFullyQualifiedStructureElementNameException $e) { |
108
|
|
|
$this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e); |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
return null; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
protected function collectReflectionsHavingTag(\ReflectionClass $reflectionClass, string $tagName): array |
116
|
|
|
{ |
117
|
|
|
$ret = []; |
118
|
|
|
|
119
|
|
|
if ($this->haveTag($reflectionClass, $tagName)) { |
120
|
|
|
$ret[] = $reflectionClass; |
121
|
|
|
} |
122
|
|
|
foreach ($reflectionClass->getProperties() as $reflectionProperty) { |
123
|
|
|
if ($this->haveTag($reflectionProperty, $tagName)) { |
124
|
|
|
$ret[] = $reflectionProperty; |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
foreach ($reflectionClass->getMethods() as $reflectionMethod) { |
128
|
|
|
if ($this->haveTag($reflectionMethod, DepsInternal::getTagName())) { |
129
|
|
|
$ret[] = $reflectionMethod; |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
foreach ($reflectionClass->getReflectionConstants() as $reflectionClassConstant) { |
133
|
|
|
if ($this->haveTag($reflectionClassConstant, DepsInternal::getTagName())) { |
134
|
|
|
$ret[] = $reflectionClassConstant; |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
return $ret; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
|
142
|
|
|
// protected function resolveUsesTag(\ReflectionClass $reflectionClass) |
143
|
|
|
// { |
144
|
|
|
// $reflectionClass->getDocComment(); |
145
|
|
|
// } |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* @param \ReflectionClass $reflectionClass |
149
|
|
|
* @return DepsInternal[] |
150
|
|
|
*/ |
151
|
|
|
public function resolveCanOnlyUsedByTag(\ReflectionClass $reflectionClass): array |
152
|
|
|
{ |
153
|
|
|
$ret = []; |
154
|
|
|
if ($this->haveTag($reflectionClass, self::ONLY_USED_BY_TAGS)) { |
155
|
|
|
try { |
156
|
|
|
$ret[] = new DepsInternal( |
157
|
|
|
FQSEN::createClass($reflectionClass->getName()), |
158
|
|
|
$this->resolve($reflectionClass->getDocComment(), self::ONLY_USED_BY_TAGS) |
|
|
|
|
159
|
|
|
); |
160
|
|
|
} catch (InvalidFullyQualifiedStructureElementNameException $e) { |
161
|
|
|
$this->notifyError($reflectionClass->getFileName(), $reflectionClass->getName(), $e); |
162
|
|
|
} |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
return $ret; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* @param ClassReflection $classReflection |
170
|
|
|
* @return string[] |
171
|
|
|
*/ |
172
|
|
|
public function resolveDependerTag(ClassReflection $classReflection): array |
173
|
|
|
{ |
174
|
|
|
if ($phpdoc = $classReflection->getNativeReflection()->getDocComment()) { |
175
|
|
|
return $this->resolve($phpdoc, self::DEPENDER_TAGS); |
|
|
|
|
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
return []; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* @param ClassReflection $classReflection |
183
|
|
|
* @return string[] |
184
|
|
|
*/ |
185
|
|
|
public function resolveDependeeTag(ClassReflection $classReflection): array |
186
|
|
|
{ |
187
|
|
|
if ($phpdoc = $classReflection->getNativeReflection()->getDocComment()) { |
188
|
|
|
return $this->resolve($phpdoc, self::DEPENDEE_TAGS); |
|
|
|
|
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
return []; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
protected function haveTag(\Reflector $reflector, string $tag): bool |
195
|
|
|
{ |
196
|
|
|
if (!method_exists($reflector, 'getDocComment') && $reflector->getDocComment() !== false) { |
|
|
|
|
197
|
|
|
return false; |
198
|
|
|
} elseif (($docComment = $reflector->getDocComment()) === false) { |
|
|
|
|
199
|
|
|
return false; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
$tokens = new TokenIterator($this->phpDocLexer->tokenize($reflector->getDocComment())); |
203
|
|
|
$phpDocNode = $this->phpDocParser->parse($tokens); |
204
|
|
|
|
205
|
|
|
foreach ($phpDocNode->getTagsByName($tag) as $phpDocTagNode) { |
206
|
|
|
/** @var PhpDocTagNode $phpDocTagNode */ |
207
|
|
|
if (preg_match('/^' . $tag . '/', $phpDocTagNode->__toString()) === 1) { |
208
|
|
|
return true; |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
return false; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
protected function resolve(string $phpdoc, string $tag): array |
216
|
|
|
{ |
217
|
|
|
$tokens = new TokenIterator($this->phpDocLexer->tokenize($phpdoc)); |
218
|
|
|
$phpDocNode = $this->phpDocParser->parse($tokens); |
219
|
|
|
|
220
|
|
|
$ret = []; |
221
|
|
|
foreach ($phpDocNode->getTagsByName($tag) as $phpDocTagNode) { |
222
|
|
|
/** @var PhpDocTagNode $phpDocTagNode */ |
223
|
|
|
if (preg_match('/^' . $tag . '\s+(.+)$/', $phpDocTagNode->__toString(), $matches) === 1) { |
224
|
|
|
$ret[] = $matches[1]; |
225
|
|
|
} |
226
|
|
|
}; |
227
|
|
|
|
228
|
|
|
return $ret; |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.