Skip to content

Commit a132192

Browse files
committed
Add rule validating @Friend tags
1 parent 1240b10 commit a132192

File tree

4 files changed

+118
-0
lines changed

4 files changed

+118
-0
lines changed

conf/config.level2.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ rules:
2828
- PHPStan\Rules\Operators\InvalidComparisonOperationRule
2929
- PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule
3030
- PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule
31+
- PHPStan\Rules\PhpDoc\InvalidFriendTagTargetRule
3132
- PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule
3233
- PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule
3334
- PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PhpDoc;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\ReflectionProvider;
8+
use PHPStan\Rules\RuleErrorBuilder;
9+
use PHPStan\Type\FileTypeMapper;
10+
11+
/**
12+
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\ClassMethod>
13+
*/
14+
class InvalidFriendTagTargetRule implements \PHPStan\Rules\Rule
15+
{
16+
17+
private FileTypeMapper $fileTypeMapper;
18+
19+
private ReflectionProvider $reflectionProvider;
20+
21+
public function __construct(FileTypeMapper $fileTypeMapper, ReflectionProvider $reflectionProvider)
22+
{
23+
$this->fileTypeMapper = $fileTypeMapper;
24+
$this->reflectionProvider = $reflectionProvider;
25+
}
26+
27+
public function getNodeType(): string
28+
{
29+
return \PhpParser\Node\Stmt\ClassMethod::class;
30+
}
31+
32+
public function processNode(Node $node, Scope $scope): array
33+
{
34+
$docComment = $node->getDocComment();
35+
if ($docComment === null) {
36+
return [];
37+
}
38+
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
39+
$scope->getFile(),
40+
$scope->isInClass() ? $scope->getClassReflection()->getName() : null,
41+
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
42+
$node->name->name,
43+
$docComment->getText()
44+
);
45+
$errors = [];
46+
foreach ($resolvedPhpDoc->getFriends() as $friendTag) {
47+
$className = $friendTag->getType()->getClassName();
48+
if (!$this->reflectionProvider->hasClass($className)) {
49+
$errors[] = RuleErrorBuilder::message(sprintf(
50+
'Class %s specified by a @friend tag does not exist.',
51+
$className
52+
))->build();
53+
continue;
54+
}
55+
$reflection = $this->reflectionProvider->getClass($className);
56+
$methodName = $friendTag->getMethod();
57+
if ($methodName === null || $reflection->hasMethod($methodName)) {
58+
continue;
59+
}
60+
$errors[] = RuleErrorBuilder::message(sprintf(
61+
'Method %s::%s() specified by a @friend tag does not exist.',
62+
$className,
63+
$methodName
64+
))->build();
65+
}
66+
return $errors;
67+
}
68+
69+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PhpDoc;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Type\FileTypeMapper;
7+
8+
/**
9+
* @extends \PHPStan\Testing\RuleTestCase<InvalidFriendTagTargetRule>
10+
*/
11+
class InvalidFriendTagTargetRuleTest extends \PHPStan\Testing\RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new InvalidFriendTagTargetRule(
17+
self::getContainer()->getByType(FileTypeMapper::class),
18+
$this->createReflectionProvider()
19+
);
20+
}
21+
22+
public function testRule(): void
23+
{
24+
$this->analyse([__DIR__ . '/data/invalid-friends.php'], [
25+
[
26+
'Class InvalidFriends\Bar specified by a @friend tag does not exist.',
27+
11,
28+
],
29+
[
30+
'Method InvalidFriends\Foo::notExists() specified by a @friend tag does not exist.',
31+
11,
32+
],
33+
]);
34+
}
35+
36+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace InvalidFriends;
4+
5+
class Foo
6+
{
7+
/**
8+
* @friend Bar
9+
* @friend Foo::notExists
10+
*/
11+
public function bar() { }
12+
}

0 commit comments

Comments
 (0)