Skip to content

Commit df6bebd

Browse files
committed
[rule] Add NoMissnamedDocTagRule
1 parent 38e3a6e commit df6bebd

8 files changed

Lines changed: 183 additions & 0 deletions

File tree

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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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 PROPERTY_ERROR_MESSAGE = 'Property doc comment tag must be @var, "%s" given';
27+
28+
/**
29+
* @api used in tests
30+
*/
31+
public const METHOD_ERROR_MESSAGE = 'Method doc comment tag must be @param or @return, "%s" given';
32+
33+
public function getNodeType(): string
34+
{
35+
return Class_::class;
36+
}
37+
38+
/**
39+
* @param Class_ $node
40+
* @return array<RuleError>
41+
*/
42+
public function processNode(Node $node, Scope $scope): array
43+
{
44+
$ruleErrors = [];
45+
46+
foreach ($node->getMethods() as $classMethod) {
47+
// match "@return" and "@param" tags
48+
if ($classMethod->getDocComment() === null) {
49+
continue;
50+
}
51+
52+
$matches = Strings::match($classMethod->getDocComment()->getText(), '#(@var)\b#mi');
53+
if ($matches === null) {
54+
continue;
55+
}
56+
57+
$ruleErrors[] = RuleErrorBuilder::message(sprintf(self::METHOD_ERROR_MESSAGE, $matches[1]))
58+
->identifier(RuleIdentifier::NO_MISSNAMED_DOC_TAG)
59+
->build();
60+
}
61+
62+
foreach ($node->getProperties() as $property) {
63+
// match "@return" and "@param" tags
64+
if ($property->getDocComment() === null) {
65+
continue;
66+
}
67+
68+
$matches = Strings::match($property->getDocComment()->getText(), '#(@param|@return)\b#mi');
69+
if ($matches === null) {
70+
continue;
71+
}
72+
73+
$ruleErrors[] = RuleErrorBuilder::message(sprintf(self::PROPERTY_ERROR_MESSAGE, $matches[1]))
74+
->identifier(RuleIdentifier::NO_MISSNAMED_DOC_TAG)
75+
->build();
76+
}
77+
78+
return $ruleErrors;
79+
}
80+
}
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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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'), 7],
31+
]];
32+
33+
yield [__DIR__ . '/Fixture/SomeClass.php', [
34+
[sprintf(NoMissnamedDocTagRule::PROPERTY_ERROR_MESSAGE, '@return'), 7],
35+
]];
36+
37+
yield [__DIR__ . '/Fixture/SkipValidPropertyTag.php', []];
38+
}
39+
40+
/**
41+
* @return array<int, string>
42+
*/
43+
public static function getAdditionalConfigFiles(): array
44+
{
45+
return [__DIR__ . '/config/configured_rule.neon'];
46+
}
47+
48+
protected function getRule(): Rule
49+
{
50+
return self::getContainer()->getByType(NoMissnamedDocTagRule::class);
51+
}
52+
}
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)