Skip to content

Commit 4db6301

Browse files
authored
[rule] Add NoMissnamedDocTagRule (#257)
1 parent 38e3a6e commit 4db6301

File tree

9 files changed

+223
-0
lines changed

9 files changed

+223
-0
lines changed

config/static-rules.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ rules:
1515
- Symplify\PHPStanRules\Rules\NoReferenceRule
1616
- Symplify\PHPStanRules\Rules\ForbiddenStaticClassConstFetchRule
1717
- Symplify\PHPStanRules\Rules\Complexity\ForbiddenArrayMethodCallRule
18+
19+
# docblock
20+
- Symplify\PHPStanRules\Rules\NoMissnamedDocTagRule

src/Enum/RuleIdentifier.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,6 @@ final class RuleIdentifier
7979
public const RULE_IDENTIFIER = 'symplify.foreachCeption';
8080

8181
public const NO_MISSING_VARIABLE_DIM_FETCH = 'symplify.noMissingVariableDimFetch';
82+
83+
public const NO_MISSNAMED_DOC_TAG = 'symplify.noMissnamedDocTag';
8284
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Rules;
6+
7+
use Nette\Utils\Strings;
8+
use PhpParser\Node;
9+
use PhpParser\Node\Stmt\Class_;
10+
use PHPStan\Analyser\Scope;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleError;
13+
use PHPStan\Rules\RuleErrorBuilder;
14+
use Symplify\PHPStanRules\Enum\RuleIdentifier;
15+
16+
/**
17+
* @implements Rule<Class_>
18+
*
19+
* @see \Symplify\PHPStanRules\Tests\Rules\NoMissnamedDocTagRule\NoMissnamedDocTagRuleTest
20+
*/
21+
final class NoMissnamedDocTagRule implements Rule
22+
{
23+
/**
24+
* @api used in tests
25+
*/
26+
public const CONSTANT_ERROR_MESSAGE = 'Constant doc comment tag must be @var, "%s" given';
27+
28+
/**
29+
* @api used in tests
30+
*/
31+
public const PROPERTY_ERROR_MESSAGE = 'Property doc comment tag must be @var, "%s" given';
32+
33+
/**
34+
* @api used in tests
35+
*/
36+
public const METHOD_ERROR_MESSAGE = 'Method doc comment tag must be @param or @return, "%s" given';
37+
38+
public function getNodeType(): string
39+
{
40+
return Class_::class;
41+
}
42+
43+
/**
44+
* @param Class_ $node
45+
* @return array<RuleError>
46+
*/
47+
public function processNode(Node $node, Scope $scope): array
48+
{
49+
$ruleErrors = [];
50+
51+
foreach ($node->getMethods() as $classMethod) {
52+
// match "@return" and "@param" tags
53+
if ($classMethod->getDocComment() === null) {
54+
continue;
55+
}
56+
57+
$matches = Strings::match($classMethod->getDocComment()->getText(), '#(@var)\b#mi');
58+
if ($matches === null) {
59+
continue;
60+
}
61+
62+
$ruleErrors[] = RuleErrorBuilder::message(sprintf(self::METHOD_ERROR_MESSAGE, $matches[1]))
63+
->identifier(RuleIdentifier::NO_MISSNAMED_DOC_TAG)
64+
->line($classMethod->getStartLine())
65+
->build();
66+
}
67+
68+
foreach ($node->getProperties() as $property) {
69+
// match "@return" and "@param" tags
70+
if ($property->getDocComment() === null) {
71+
continue;
72+
}
73+
74+
$matches = Strings::match($property->getDocComment()->getText(), '#(@param|@return)\b#mi');
75+
if ($matches === null) {
76+
continue;
77+
}
78+
79+
$ruleErrors[] = RuleErrorBuilder::message(sprintf(self::PROPERTY_ERROR_MESSAGE, $matches[1]))
80+
->identifier(RuleIdentifier::NO_MISSNAMED_DOC_TAG)
81+
->line($property->getStartLine())
82+
->build();
83+
}
84+
85+
foreach ($node->getConstants() as $classConst) {
86+
if ($classConst->getDocComment() === null) {
87+
continue;
88+
}
89+
90+
$matches = Strings::match($classConst->getDocComment()->getText(), '#(@param|@return)\b#mi');
91+
if ($matches === null) {
92+
continue;
93+
}
94+
95+
$ruleErrors[] = RuleErrorBuilder::message(sprintf(self::CONSTANT_ERROR_MESSAGE, $matches[1]))
96+
->identifier(RuleIdentifier::NO_MISSNAMED_DOC_TAG)
97+
->line($classConst->getStartLine())
98+
->build();
99+
}
100+
101+
return $ruleErrors;
102+
}
103+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\NoMissnamedDocTagRule\Fixture;
6+
7+
final class ClassMethodNonReturn
8+
{
9+
/**
10+
* @var string
11+
*/
12+
public function run()
13+
{
14+
}
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\NoMissnamedDocTagRule\Fixture;
6+
7+
class SkipValidPropertyTag
8+
{
9+
/**
10+
* @var string
11+
*/
12+
private $property;
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\NoMissnamedDocTagRule\Fixture;
6+
7+
class SomeClass
8+
{
9+
/**
10+
* @return string
11+
*/
12+
private $property;
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\NoMissnamedDocTagRule\Fixture;
6+
7+
class SomeConstant
8+
{
9+
/**
10+
* @return string
11+
*/
12+
private const NAME = 'value';
13+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\NoMissnamedDocTagRule;
6+
7+
use Iterator;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use Symplify\PHPStanRules\Rules\NoMissnamedDocTagRule;
12+
13+
final class NoMissnamedDocTagRuleTest extends RuleTestCase
14+
{
15+
/**
16+
* @param array<int, array<string|int>> $expectedErrorMessagesWithLines
17+
*/
18+
#[DataProvider('provideData')]
19+
public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
20+
{
21+
$this->analyse([$filePath], $expectedErrorMessagesWithLines);
22+
}
23+
24+
/**
25+
* @return Iterator<mixed>
26+
*/
27+
public static function provideData(): Iterator
28+
{
29+
yield [__DIR__ . '/Fixture/ClassMethodNonReturn.php', [
30+
[sprintf(NoMissnamedDocTagRule::METHOD_ERROR_MESSAGE, '@var'), 12],
31+
]];
32+
33+
yield [__DIR__ . '/Fixture/SomeClass.php', [
34+
[sprintf(NoMissnamedDocTagRule::PROPERTY_ERROR_MESSAGE, '@return'), 12],
35+
]];
36+
37+
yield [__DIR__ . '/Fixture/SomeConstant.php', [
38+
[sprintf(NoMissnamedDocTagRule::CONSTANT_ERROR_MESSAGE, '@return'), 12],
39+
]];
40+
41+
yield [__DIR__ . '/Fixture/SkipValidPropertyTag.php', []];
42+
}
43+
44+
/**
45+
* @return array<int, string>
46+
*/
47+
public static function getAdditionalConfigFiles(): array
48+
{
49+
return [__DIR__ . '/config/configured_rule.neon'];
50+
}
51+
52+
protected function getRule(): Rule
53+
{
54+
return self::getContainer()->getByType(NoMissnamedDocTagRule::class);
55+
}
56+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
includes:
2+
- ../../../config/included_services.neon
3+
4+
rules:
5+
- Symplify\PHPStanRules\Rules\NoMissnamedDocTagRule

0 commit comments

Comments
 (0)