From c495d3d56d3a9eb84efd55e40e4f9949d85220a7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Jun 2025 16:06:28 +0200 Subject: [PATCH 1/8] Playground: Implement PhpdocCommentRule --- issue-bot/playground.neon | 1 + src/Rules/Playground/PhpdocCommentRule.php | 55 +++++++++++++++++++ .../Playground/PhpdocCommentRuleTest.php | 33 +++++++++++ .../Rules/Playground/data/comments.php | 35 ++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 src/Rules/Playground/PhpdocCommentRule.php create mode 100644 tests/PHPStan/Rules/Playground/PhpdocCommentRuleTest.php create mode 100644 tests/PHPStan/Rules/Playground/data/comments.php diff --git a/issue-bot/playground.neon b/issue-bot/playground.neon index d4072a4170..a252e3bac8 100644 --- a/issue-bot/playground.neon +++ b/issue-bot/playground.neon @@ -3,6 +3,7 @@ rules: - PHPStan\Rules\Playground\MethodNeverRule - PHPStan\Rules\Playground\NotAnalysedTraitRule - PHPStan\Rules\Playground\NoPhpCodeRule + - PHPStan\Rules\Playground\PhpdocCommentRule conditionalTags: PHPStan\Rules\Playground\StaticVarWithoutTypeRule: diff --git a/src/Rules/Playground/PhpdocCommentRule.php b/src/Rules/Playground/PhpdocCommentRule.php new file mode 100644 index 0000000000..8d0ff24f84 --- /dev/null +++ b/src/Rules/Playground/PhpdocCommentRule.php @@ -0,0 +1,55 @@ + + */ +final class PhpdocCommentRule implements Rule +{ + + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof VirtualNode) { + return []; + } + + $comments = $node->getAttribute('comments', []); + + $errors = []; + foreach ($comments as $comment) { + if (!$comment instanceof Comment) { + throw new ShouldNotHappenException(); + } + if (!str_contains($comment->getText(), '@')) { + continue; + } + + if (str_starts_with($comment->getText(), '/**')) { + continue; + } + + $errors[] = RuleErrorBuilder::message('Comment contains phpdoc-tag but does not start with /** tag.') + ->identifier('phpstanPlayground.noPhpdoc') + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Playground/PhpdocCommentRuleTest.php b/tests/PHPStan/Rules/Playground/PhpdocCommentRuleTest.php new file mode 100644 index 0000000000..94fcbd9cf7 --- /dev/null +++ b/tests/PHPStan/Rules/Playground/PhpdocCommentRuleTest.php @@ -0,0 +1,33 @@ + + */ +class PhpdocCommentRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new PhpdocCommentRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/comments.php'], [ + [ + 'Comment contains phpdoc-tag but does not start with /** tag.', + 13, + ], + [ + 'Comment contains phpdoc-tag but does not start with /** tag.', + 23, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Playground/data/comments.php b/tests/PHPStan/Rules/Playground/data/comments.php new file mode 100644 index 0000000000..a71c42c97f --- /dev/null +++ b/tests/PHPStan/Rules/Playground/data/comments.php @@ -0,0 +1,35 @@ +foo = $foo; } + + /* + * @return T + */ + public function getFoo(): FooInterface + { + return $this->foo; + } + + /* + * some method + */ + public function getBar(): FooInterface + { + return $this->foo; + } +} From f35a5ea2f324d433463dca950ed0be029c872607 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Jun 2025 16:08:17 +0200 Subject: [PATCH 2/8] cs --- src/Rules/Playground/PhpdocCommentRule.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Rules/Playground/PhpdocCommentRule.php b/src/Rules/Playground/PhpdocCommentRule.php index 8d0ff24f84..0f97ba9566 100644 --- a/src/Rules/Playground/PhpdocCommentRule.php +++ b/src/Rules/Playground/PhpdocCommentRule.php @@ -36,6 +36,7 @@ public function processNode(Node $node, Scope $scope): array if (!$comment instanceof Comment) { throw new ShouldNotHappenException(); } + if (!str_contains($comment->getText(), '@')) { continue; } From 1483c25fa12684bc668bba1dc2c360b03d2842c4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Jun 2025 16:13:21 +0200 Subject: [PATCH 3/8] ignore // and # comments --- src/Rules/Playground/PhpdocCommentRule.php | 6 ++++-- tests/PHPStan/Rules/Playground/data/comments.php | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Rules/Playground/PhpdocCommentRule.php b/src/Rules/Playground/PhpdocCommentRule.php index 0f97ba9566..3a169168d5 100644 --- a/src/Rules/Playground/PhpdocCommentRule.php +++ b/src/Rules/Playground/PhpdocCommentRule.php @@ -41,8 +41,10 @@ public function processNode(Node $node, Scope $scope): array continue; } - if (str_starts_with($comment->getText(), '/**')) { - continue; + foreach(['/**', '//', '#'] as $startTag) { + if (str_starts_with($comment->getText(), $startTag)) { + continue 2; + } } $errors[] = RuleErrorBuilder::message('Comment contains phpdoc-tag but does not start with /** tag.') diff --git a/tests/PHPStan/Rules/Playground/data/comments.php b/tests/PHPStan/Rules/Playground/data/comments.php index a71c42c97f..5d4e3ff520 100644 --- a/tests/PHPStan/Rules/Playground/data/comments.php +++ b/tests/PHPStan/Rules/Playground/data/comments.php @@ -32,4 +32,7 @@ public function getBar(): FooInterface { return $this->foo; } + + // this should not error: @var + # this should not error: @var } From 024c63ee12d2c5dd9a14f797ea6e2d6d44513e53 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Jun 2025 16:18:28 +0200 Subject: [PATCH 4/8] cs --- src/Rules/Playground/PhpdocCommentRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/Playground/PhpdocCommentRule.php b/src/Rules/Playground/PhpdocCommentRule.php index 3a169168d5..badfcdc228 100644 --- a/src/Rules/Playground/PhpdocCommentRule.php +++ b/src/Rules/Playground/PhpdocCommentRule.php @@ -41,7 +41,7 @@ public function processNode(Node $node, Scope $scope): array continue; } - foreach(['/**', '//', '#'] as $startTag) { + foreach (['/**', '//', '#'] as $startTag) { if (str_starts_with($comment->getText(), $startTag)) { continue 2; } From 3cf6b9c1b7e5ad6e58048f94c670cd8064879c79 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Jun 2025 16:21:36 +0200 Subject: [PATCH 5/8] improve wording --- src/Rules/Playground/PhpdocCommentRule.php | 2 +- tests/PHPStan/Rules/Playground/PhpdocCommentRuleTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Rules/Playground/PhpdocCommentRule.php b/src/Rules/Playground/PhpdocCommentRule.php index badfcdc228..998e6dec73 100644 --- a/src/Rules/Playground/PhpdocCommentRule.php +++ b/src/Rules/Playground/PhpdocCommentRule.php @@ -47,7 +47,7 @@ public function processNode(Node $node, Scope $scope): array } } - $errors[] = RuleErrorBuilder::message('Comment contains phpdoc-tag but does not start with /** tag.') + $errors[] = RuleErrorBuilder::message('Comment contains PHPDoc tag but does not start with /** prefix.') ->identifier('phpstanPlayground.noPhpdoc') ->build(); } diff --git a/tests/PHPStan/Rules/Playground/PhpdocCommentRuleTest.php b/tests/PHPStan/Rules/Playground/PhpdocCommentRuleTest.php index 94fcbd9cf7..7642b2356f 100644 --- a/tests/PHPStan/Rules/Playground/PhpdocCommentRuleTest.php +++ b/tests/PHPStan/Rules/Playground/PhpdocCommentRuleTest.php @@ -20,11 +20,11 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/comments.php'], [ [ - 'Comment contains phpdoc-tag but does not start with /** tag.', + 'Comment contains PHPDoc tag but does not start with /** prefix.', 13, ], [ - 'Comment contains phpdoc-tag but does not start with /** tag.', + 'Comment contains PHPDoc tag but does not start with /** prefix.', 23, ], ]); From 4b115da6c6e7d5f894391de0bc8f24b9e691c854 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Jun 2025 17:30:14 +0200 Subject: [PATCH 6/8] improve error identifier --- src/Rules/Playground/PhpdocCommentRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/Playground/PhpdocCommentRule.php b/src/Rules/Playground/PhpdocCommentRule.php index 998e6dec73..ec1c5367c2 100644 --- a/src/Rules/Playground/PhpdocCommentRule.php +++ b/src/Rules/Playground/PhpdocCommentRule.php @@ -48,7 +48,7 @@ public function processNode(Node $node, Scope $scope): array } $errors[] = RuleErrorBuilder::message('Comment contains PHPDoc tag but does not start with /** prefix.') - ->identifier('phpstanPlayground.noPhpdoc') + ->identifier('phpstanPlayground.phpDoc') ->build(); } From 8f3c540786f861431b6dc41e9bd0fce751d6dde8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Jun 2025 20:32:42 +0200 Subject: [PATCH 7/8] simplify --- src/Rules/Playground/PhpdocCommentRule.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Rules/Playground/PhpdocCommentRule.php b/src/Rules/Playground/PhpdocCommentRule.php index ec1c5367c2..1858e170d9 100644 --- a/src/Rules/Playground/PhpdocCommentRule.php +++ b/src/Rules/Playground/PhpdocCommentRule.php @@ -2,13 +2,11 @@ namespace PHPStan\Rules\Playground; -use PhpParser\Comment; use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\VirtualNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\ShouldNotHappenException; use function str_contains; use function str_starts_with; @@ -29,14 +27,10 @@ public function processNode(Node $node, Scope $scope): array return []; } - $comments = $node->getAttribute('comments', []); + $comments = $node->getComments(); $errors = []; foreach ($comments as $comment) { - if (!$comment instanceof Comment) { - throw new ShouldNotHappenException(); - } - if (!str_contains($comment->getText(), '@')) { continue; } From 1c8c4783bb1fde5f21244eb757e60a0cf9d498b4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 30 Jun 2025 10:00:06 +0200 Subject: [PATCH 8/8] prevent false positive on phpDoc look-like comments --- src/Rules/Playground/PhpdocCommentRule.php | 9 +++++---- tests/PHPStan/Rules/Playground/data/comments.php | 11 +++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Rules/Playground/PhpdocCommentRule.php b/src/Rules/Playground/PhpdocCommentRule.php index 1858e170d9..73d0536a9d 100644 --- a/src/Rules/Playground/PhpdocCommentRule.php +++ b/src/Rules/Playground/PhpdocCommentRule.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Playground; +use Nette\Utils\Strings; use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\VirtualNode; @@ -31,16 +32,16 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($comments as $comment) { - if (!str_contains($comment->getText(), '@')) { - continue; - } - foreach (['/**', '//', '#'] as $startTag) { if (str_starts_with($comment->getText(), $startTag)) { continue 2; } } + if (!Strings::match($comment->getText(), '{(\s|^)@\w+(\s|$)}')) { + continue; + } + $errors[] = RuleErrorBuilder::message('Comment contains PHPDoc tag but does not start with /** prefix.') ->identifier('phpstanPlayground.phpDoc') ->build(); diff --git a/tests/PHPStan/Rules/Playground/data/comments.php b/tests/PHPStan/Rules/Playground/data/comments.php index 5d4e3ff520..83f67ba589 100644 --- a/tests/PHPStan/Rules/Playground/data/comments.php +++ b/tests/PHPStan/Rules/Playground/data/comments.php @@ -35,4 +35,15 @@ public function getBar(): FooInterface // this should not error: @var # this should not error: @var + + /* + * comments which look like phpdoc should be ignored + * + * x@x.cz + * 10 amps @ 1 volt + */ + public function ignoreComments(): FooInterface + { + return $this->foo; + } }