Skip to content

Commit 4a0f561

Browse files
marcospassospepakriz
authored andcommitted
[Hotfix] Fix crash reading annotations of anonymous classes (fix #71) (#72)
As the `ThrowsAnnotationReader` class is `@internal`, there is no BC break.
1 parent b4ef146 commit 4a0f561

File tree

3 files changed

+82
-45
lines changed

3 files changed

+82
-45
lines changed

src/Rules/ThrowsPhpDocRule.php

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@
4040
use PHPStan\Type\TypeCombinator;
4141
use PHPStan\Type\TypeUtils;
4242
use PHPStan\Type\VoidType;
43-
use ReflectionException;
44-
use ReflectionFunction;
4543
use function array_diff;
4644
use function array_filter;
4745
use function array_map;
@@ -477,23 +475,11 @@ private function filterUnusedExceptions(array $declaredThrows, array $usedThrows
477475

478476
$unusedThrows = array_diff($unusedThrows, TypeUtils::getDirectClassNames($defaultThrowsType));
479477

480-
try {
481-
if ($functionReflection instanceof MethodReflection) {
482-
$nativeClassReflection = $functionReflection->getDeclaringClass()->getNativeReflection();
483-
$nativeFunctionReflection = $nativeClassReflection->getMethod($functionReflection->getName());
484-
485-
} else {
486-
$nativeFunctionReflection = new ReflectionFunction($functionReflection->getName());
487-
}
488-
} catch (ReflectionException $exception) {
489-
return $unusedThrows;
490-
}
491-
492478
if (!$this->ignoreDescriptiveUncheckedExceptions) {
493479
return $unusedThrows;
494480
}
495481

496-
$throwsAnnotations = $this->throwsAnnotationReader->read($nativeFunctionReflection);
482+
$throwsAnnotations = $this->throwsAnnotationReader->read($scope);
497483

498484
return array_filter($unusedThrows, static function (string $type) use ($throwsAnnotations, $usedThrowsAnnotations): bool {
499485
return !in_array($type, $usedThrowsAnnotations, true)

src/ThrowsAnnotationReader.php

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
use PhpParser\NodeTraverser;
77
use PhpParser\NodeVisitorAbstract;
88
use PHPStan\Analyser\NameScope;
9+
use PHPStan\Analyser\Scope;
910
use PHPStan\Parser\Parser;
1011
use PHPStan\PhpDocParser\Lexer\Lexer;
1112
use PHPStan\PhpDocParser\Parser\PhpDocParser;
1213
use PHPStan\PhpDocParser\Parser\TokenIterator;
13-
use ReflectionFunctionAbstract;
14-
use ReflectionMethod;
14+
use PHPStan\Reflection\MethodReflection;
15+
use ReflectionException;
16+
use ReflectionFunction;
1517
use function sprintf;
1618
use function strtolower;
1719

@@ -46,31 +48,54 @@ public function __construct(Parser $phpParser, Lexer $phpDocLexer, PhpDocParser
4648
/**
4749
* @return string[][]
4850
*/
49-
public function read(ReflectionFunctionAbstract $reflection): array
51+
public function read(Scope $scope): array
5052
{
51-
$functionName = $reflection->getName();
53+
$reflection = $scope->getFunction();
5254

53-
if (!isset($this->annotations[$functionName])) {
54-
$this->annotations[$functionName] = $this->parse($reflection);
55+
if ($reflection === null) {
56+
return [];
57+
}
58+
59+
$namespace = $scope->getNamespace();
60+
$sourceFile = $scope->getFile();
61+
62+
$key = $namespace . '::' . $sourceFile . '::';
63+
64+
$classReflection = $scope->getClassReflection();
65+
66+
if ($classReflection !== null) {
67+
$key .= $classReflection->getName();
68+
}
69+
70+
$key .= $reflection->getName();
71+
72+
if (!isset($this->annotations[$key])) {
73+
$this->annotations[$key] = $this->parse($reflection, $sourceFile, $namespace);
5574
}
5675

57-
return $this->annotations[$functionName];
76+
return $this->annotations[$key];
5877
}
5978

6079
/**
80+
* @param \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection $reflection
81+
*
6182
* @return string[][]
6283
*/
63-
private function parse(ReflectionFunctionAbstract $reflection): array
84+
private function parse($reflection, string $sourceFile, ?string $namespace = null): array
6485
{
65-
$docComment = $reflection->getDocComment();
86+
try {
87+
$docBlock = $this->getDocblock($reflection);
88+
} catch (ReflectionException $exception) {
89+
return [];
90+
}
6691

67-
if ($docComment === false) {
92+
if ($docBlock === null) {
6893
return [];
6994
}
7095

71-
$tokens = new TokenIterator($this->phpDocLexer->tokenize($docComment));
96+
$tokens = new TokenIterator($this->phpDocLexer->tokenize($docBlock));
7297
$phpDocNode = $this->phpDocParser->parse($tokens);
73-
$nameScope = $this->createNameScope($reflection);
98+
$nameScope = $this->createNameScope($sourceFile, $namespace);
7499

75100
$annotations = [];
76101
foreach ($phpDocNode->getThrowsTagValues() as $tagValue) {
@@ -86,17 +111,31 @@ private function parse(ReflectionFunctionAbstract $reflection): array
86111
return $annotations;
87112
}
88113

89-
private function createNameScope(ReflectionFunctionAbstract $reflection): NameScope
114+
/**
115+
* @param \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection $reflection
116+
*
117+
* @throws ReflectionException
118+
*/
119+
private function getDocblock($reflection): ?string
90120
{
91-
$namespace = $reflection instanceof ReflectionMethod ?
92-
$reflection->getDeclaringClass()->getNamespaceName() :
93-
$reflection->getNamespaceName();
121+
if ($reflection instanceof MethodReflection) {
122+
$declaringClass = $reflection->getDeclaringClass();
123+
$nativeClassReflection = $declaringClass->getNativeReflection();
124+
$nativeMethodReflection = $nativeClassReflection->getMethod($reflection->getName());
125+
$docBlock = $nativeMethodReflection->getDocComment();
94126

95-
/** @var string $fileName */
96-
$fileName = $reflection->getFileName();
97-
$usesMap = $this->getUsesMap($fileName, $namespace);
127+
return $docBlock !== false ? $docBlock : null;
128+
}
98129

99-
return new NameScope($namespace, $usesMap);
130+
$functionReflection = new ReflectionFunction($reflection->getName());
131+
$docBlock = $functionReflection->getDocComment();
132+
133+
return $docBlock !== false ? $docBlock : null;
134+
}
135+
136+
private function createNameScope(string $sourceFile, ?string $namespace = null): NameScope
137+
{
138+
return new NameScope($namespace, $this->getUsesMap($sourceFile, (string) $namespace));
100139
}
101140

102141
/**
@@ -114,7 +153,7 @@ private function getUsesMap(string $fileName, string $namespace): array
114153
/**
115154
* @return string[][]
116155
*/
117-
private function createUsesMap(string $fileName): array
156+
private function createUsesMap(string $sourceFile): array
118157
{
119158
$visitor = new class extends NodeVisitorAbstract {
120159

@@ -170,7 +209,7 @@ private function addUse(string $alias, string $className): void
170209

171210
$traverser = new NodeTraverser();
172211
$traverser->addVisitor($visitor);
173-
$traverser->traverse($this->phpParser->parseFile($fileName));
212+
$traverser->traverse($this->phpParser->parseFile($sourceFile));
174213

175214
return $visitor->uses;
176215
}

tests/src/Rules/data/unused-descriptive-throws.php

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ public function unusedBarAnnotation(): void // error: Unused @throws Pepakriz\PH
103103
$this->throwFooExceptions();
104104
}
105105

106-
/**
107-
* @throws FooException
108-
* @throws BarException Description
109-
*/
110-
public function unusedDescriptiveAnnotation(): void // error: Unused @throws Pepakriz\PHPStanExceptionRules\Rules\UnusedDescriptiveThrows\BarException annotation
111-
{
112-
$this->throwFooExceptions();
113-
}
106+
/**
107+
* @throws FooException
108+
* @throws BarException Description
109+
*/
110+
public function unusedDescriptiveAnnotation(): void // error: Unused @throws Pepakriz\PHPStanExceptionRules\Rules\UnusedDescriptiveThrows\BarException annotation
111+
{
112+
$this->throwFooExceptions();
113+
}
114114

115115
/**
116116
* @throws LogicException Description.
@@ -137,3 +137,15 @@ public function unusedLogic(): void // error: Unused @throws LogicException anno
137137
}
138138

139139
}
140+
141+
new class {
142+
143+
/**
144+
* @throws RuntimeException Overridden description.
145+
*/
146+
public function descriptiveAnnotationAlias(): void
147+
{
148+
throw new RuntimeException();
149+
}
150+
151+
};

0 commit comments

Comments
 (0)