1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus framework.
7: *
8: * (c) John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\PHPStan\Rules\PhpDoc;
15:
16: use PhpParser\Node;
17: use PHPStan\Analyser\Scope;
18: use PHPStan\Node\VirtualNode;
19: use PHPStan\PhpDocParser\Lexer\Lexer;
20: use PHPStan\PhpDocParser\Parser\PhpDocParser;
21: use PHPStan\PhpDocParser\Parser\TokenIterator;
22: use PHPStan\Rules\Rule;
23: use PHPStan\Rules\RuleErrorBuilder;
24:
25: /**
26: * @implements Rule<Node\Stmt>
27: */
28: final class DisallowedPhpstanDocTagRule implements Rule
29: {
30: private const array ALLOWED_PHPSTAN_TAGS = [
31: '@phpstan-ignore',
32: '@phpstan-ignore-next-line',
33: '@phpstan-ignore-line',
34: '@phpstan-type',
35: '@phpstan-import-type',
36: '@phpstan-assert',
37: '@phpstan-assert-if-true',
38: '@phpstan-assert-if-false',
39: '@phpstan-self-out',
40: '@phpstan-this-out',
41: '@phpstan-allow-private-mutation',
42: '@phpstan-readonly-allow-private-mutation',
43: '@phpstan-require-extends',
44: '@phpstan-require-implements',
45: ];
46:
47: public function __construct(
48: private Lexer $phpDocLexer,
49: private PhpDocParser $phpDocParser,
50: ) {}
51:
52: #[\Override]
53: public function getNodeType(): string
54: {
55: return Node\Stmt::class;
56: }
57:
58: #[\Override]
59: public function processNode(Node $node, Scope $scope): array
60: {
61: if ($node instanceof VirtualNode) {
62: return [];
63: }
64:
65: if ($node instanceof Node\Stmt\Expression) {
66: if (
67: ! $node->expr instanceof Node\Expr\Assign
68: && ! $node->expr instanceof Node\Expr\AssignRef
69: ) {
70: return [];
71: }
72: }
73:
74: $docComment = $node->getDocComment();
75:
76: if (null === $docComment) {
77: return [];
78: }
79:
80: $phpDocString = $docComment->getText();
81: $tokens = new TokenIterator($this->phpDocLexer->tokenize($phpDocString));
82: $phpDocNode = $this->phpDocParser->parse($tokens);
83: $errors = [];
84:
85: foreach ($phpDocNode->getTags() as $phpDocTagNode) {
86: $phpDocName = $phpDocTagNode->name;
87:
88: if (! str_starts_with($phpDocName, '@phpstan-')) {
89: continue;
90: }
91:
92: if (\in_array($phpDocName, self::ALLOWED_PHPSTAN_TAGS, true)) {
93: continue;
94: }
95:
96: $errors[] = RuleErrorBuilder::message(\sprintf('Disallowed PHPStan-prefixed PHPDoc tag: %s', $phpDocTagNode->__toString()))
97: ->tip(\sprintf('Use the native PHPDoc instead: %s', str_replace('@phpstan-', '@', $phpDocTagNode->__toString())))
98: ->line($docComment->getStartLine())
99: ->identifier('nexus.phpstanPhpdocTag')
100: ->build()
101: ;
102: }
103:
104: return $errors;
105: }
106: }
107: