Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 9b88cef

Browse files
Add rule that checks for invalid and unrecognized annotations
1 parent 68017cc commit 9b88cef

9 files changed

+467
-0
lines changed

‎extension.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ services:
5353
- phpstan.broker.dynamicMethodReturnTypeExtension
5454
-
5555
class: PHPStan\Rules\PHPUnit\CoversHelper
56+
-
57+
class: PHPStan\Rules\PHPUnit\AnnotationHelper
5658

5759
conditionalTags:
5860
PHPStan\PhpDoc\PHPUnit\MockObjectTypeNodeResolverExtension:

‎rules.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ rules:
88
services:
99
- class: PHPStan\Rules\PHPUnit\ClassCoversExistsRule
1010
- class: PHPStan\Rules\PHPUnit\ClassMethodCoversExistsRule
11+
- class: PHPStan\Rules\PHPUnit\NoMissingSpaceInClassAnnotationRule
12+
- class: PHPStan\Rules\PHPUnit\NoMissingSpaceInMethodAnnotationRule
1113

1214
conditionalTags:
1315
PHPStan\Rules\PHPUnit\ClassCoversExistsRule:
1416
phpstan.rules.rule: %featureToggles.bleedingEdge%
1517
PHPStan\Rules\PHPUnit\ClassMethodCoversExistsRule:
1618
phpstan.rules.rule: %featureToggles.bleedingEdge%
19+
PHPStan\Rules\PHPUnit\NoMissingSpaceInClassAnnotationRule:
20+
phpstan.rules.rule: %featureToggles.bleedingEdge%
21+
PHPStan\Rules\PHPUnit\NoMissingSpaceInMethodAnnotationRule:
22+
phpstan.rules.rule: %featureToggles.bleedingEdge%

‎src/Rules/PHPUnit/AnnotationHelper.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PhpParser\Comment\Doc;
6+
use PHPStan\Rules\RuleError;
7+
use PHPStan\Rules\RuleErrorBuilder;
8+
use function array_key_exists;
9+
use function in_array;
10+
use function preg_match;
11+
use function preg_split;
12+
13+
class AnnotationHelper
14+
{
15+
16+
private const ANNOTATIONS_WITH_PARAMS = [
17+
'backupGlobals',
18+
'backupStaticAttributes',
19+
'covers',
20+
'coversDefaultClass',
21+
'dataProvider',
22+
'depends',
23+
'group',
24+
'preserveGlobalState',
25+
'requires',
26+
'testDox',
27+
'testWith',
28+
'ticket',
29+
'uses',
30+
];
31+
32+
/**
33+
* @return RuleError[] errors
34+
*/
35+
public function processDocComment(Doc $docComment): array
36+
{
37+
$errors = [];
38+
$docCommentLines = preg_split("/((\r?\n)|(\r\n?))/", $docComment->getText());
39+
if ($docCommentLines === false) {
40+
return [];
41+
}
42+
43+
foreach ($docCommentLines as $docCommentLine) {
44+
// These annotations can't be retrieved using the getResolvedPhpDoc method on the FileTypeMapper as they are not present when they are invalid
45+
$annotation = preg_match('/(?<annotation>@(?<property>[a-zA-Z]+)(?<whitespace>\s*)(?<value>.*))/', $docCommentLine, $matches);
46+
if ($annotation === false) {
47+
continue; // Line without annotation
48+
}
49+
50+
if (array_key_exists('property', $matches) === false || array_key_exists('whitespace', $matches) === false || array_key_exists('annotation', $matches) === false) {
51+
continue;
52+
}
53+
54+
if (!in_array($matches['property'], self::ANNOTATIONS_WITH_PARAMS, true) || $matches['whitespace'] !== '') {
55+
continue;
56+
}
57+
58+
$errors[] = RuleErrorBuilder::message(
59+
'Annotation "' . $matches['annotation'] . '" is invalid, "@' . $matches['property'] . '" should be followed by a space and a value.'
60+
)->build();
61+
}
62+
63+
return $errors;
64+
}
65+
66+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\InClassNode;
8+
use PHPStan\Rules\Rule;
9+
use PHPUnit\Framework\TestCase;
10+
11+
/**
12+
* @implements Rule<InClassNode>
13+
*/
14+
class NoMissingSpaceInClassAnnotationRule implements Rule
15+
{
16+
17+
/**
18+
* Covers helper.
19+
*
20+
* @var AnnotationHelper
21+
*/
22+
private $annotationHelper;
23+
24+
public function __construct(AnnotationHelper $annotationHelper)
25+
{
26+
$this->annotationHelper = $annotationHelper;
27+
}
28+
29+
public function getNodeType(): string
30+
{
31+
return InClassNode::class;
32+
}
33+
34+
public function processNode(Node $node, Scope $scope): array
35+
{
36+
$classReflection = $scope->getClassReflection();
37+
if ($classReflection === null || $classReflection->isSubclassOf(TestCase::class) === false) {
38+
return [];
39+
}
40+
41+
$docComment = $node->getDocComment();
42+
if ($docComment === null) {
43+
return [];
44+
}
45+
46+
return $this->annotationHelper->processDocComment($docComment);
47+
}
48+
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\InClassMethodNode;
8+
use PHPStan\Rules\Rule;
9+
use PHPUnit\Framework\TestCase;
10+
11+
/**
12+
* @implements Rule<InClassMethodNode>
13+
*/
14+
class NoMissingSpaceInMethodAnnotationRule implements Rule
15+
{
16+
17+
/**
18+
* Covers helper.
19+
*
20+
* @var AnnotationHelper
21+
*/
22+
private $annotationHelper;
23+
24+
public function __construct(AnnotationHelper $annotationHelper)
25+
{
26+
$this->annotationHelper = $annotationHelper;
27+
}
28+
29+
public function getNodeType(): string
30+
{
31+
return InClassMethodNode::class;
32+
}
33+
34+
public function processNode(Node $node, Scope $scope): array
35+
{
36+
$classReflection = $scope->getClassReflection();
37+
if ($classReflection === null || $classReflection->isSubclassOf(TestCase::class) === false) {
38+
return [];
39+
}
40+
41+
$docComment = $node->getDocComment();
42+
if ($docComment === null) {
43+
return [];
44+
}
45+
46+
return $this->annotationHelper->processDocComment($docComment);
47+
}
48+
49+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<NoMissingSpaceInClassAnnotationRule>
10+
*/
11+
class NoMissingSpaceInClassAnnotationRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new NoMissingSpaceInClassAnnotationRule(new AnnotationHelper());
17+
}
18+
19+
public function testRule(): void
20+
{
21+
$this->analyse([__DIR__ . '/data/InvalidClassCoversAnnotation.php'], [
22+
[
23+
'Annotation "@backupGlobals" is invalid, "@backupGlobals" should be followed by a space and a value.',
24+
36,
25+
],
26+
[
27+
'Annotation "@backupStaticAttributes" is invalid, "@backupStaticAttributes" should be followed by a space and a value.',
28+
36,
29+
],
30+
[
31+
'Annotation "@covers\Dummy\Foo::assertSame" is invalid, "@covers" should be followed by a space and a value.',
32+
36,
33+
],
34+
[
35+
'Annotation "@covers::assertSame" is invalid, "@covers" should be followed by a space and a value.',
36+
36,
37+
],
38+
[
39+
'Annotation "@coversDefaultClass\Dummy\Foo" is invalid, "@coversDefaultClass" should be followed by a space and a value.',
40+
36,
41+
],
42+
[
43+
'Annotation "@dataProvider" is invalid, "@dataProvider" should be followed by a space and a value.',
44+
36,
45+
],
46+
[
47+
'Annotation "@depends" is invalid, "@depends" should be followed by a space and a value.',
48+
36,
49+
],
50+
[
51+
'Annotation "@preserveGlobalState" is invalid, "@preserveGlobalState" should be followed by a space and a value.',
52+
36,
53+
],
54+
[
55+
'Annotation "@requires" is invalid, "@requires" should be followed by a space and a value.',
56+
36,
57+
],
58+
[
59+
'Annotation "@testDox" is invalid, "@testDox" should be followed by a space and a value.',
60+
36,
61+
],
62+
[
63+
'Annotation "@testWith" is invalid, "@testWith" should be followed by a space and a value.',
64+
36,
65+
],
66+
[
67+
'Annotation "@ticket" is invalid, "@ticket" should be followed by a space and a value.',
68+
36,
69+
],
70+
[
71+
'Annotation "@uses" is invalid, "@uses" should be followed by a space and a value.',
72+
36,
73+
],
74+
]);
75+
}
76+
77+
/**
78+
* @return string[]
79+
*/
80+
public static function getAdditionalConfigFiles(): array
81+
{
82+
return [
83+
__DIR__ . '/../../../extension.neon',
84+
];
85+
}
86+
87+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PHPUnit;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<NoMissingSpaceInMethodAnnotationRule>
10+
*/
11+
class NoMissingSpaceInMethodAnnotationRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new NoMissingSpaceInMethodAnnotationRule(new AnnotationHelper());
17+
}
18+
19+
public function testRule(): void
20+
{
21+
$this->analyse([__DIR__ . '/data/InvalidMethodCoversAnnotation.php'], [
22+
[
23+
'Annotation "@backupGlobals" is invalid, "@backupGlobals" should be followed by a space and a value.',
24+
12,
25+
],
26+
[
27+
'Annotation "@backupStaticAttributes" is invalid, "@backupStaticAttributes" should be followed by a space and a value.',
28+
19,
29+
],
30+
[
31+
'Annotation "@covers\Dummy\Foo::assertSame" is invalid, "@covers" should be followed by a space and a value.',
32+
27,
33+
],
34+
[
35+
'Annotation "@covers::assertSame" is invalid, "@covers" should be followed by a space and a value.',
36+
27,
37+
],
38+
[
39+
'Annotation "@coversDefaultClass\Dummy\Foo" is invalid, "@coversDefaultClass" should be followed by a space and a value.',
40+
33,
41+
],
42+
[
43+
'Annotation "@dataProvider" is invalid, "@dataProvider" should be followed by a space and a value.',
44+
39,
45+
],
46+
[
47+
'Annotation "@depends" is invalid, "@depends" should be followed by a space and a value.',
48+
45,
49+
],
50+
[
51+
'Annotation "@preserveGlobalState" is invalid, "@preserveGlobalState" should be followed by a space and a value.',
52+
52,
53+
],
54+
[
55+
'Annotation "@requires" is invalid, "@requires" should be followed by a space and a value.',
56+
58,
57+
],
58+
[
59+
'Annotation "@testDox" is invalid, "@testDox" should be followed by a space and a value.',
60+
64,
61+
],
62+
[
63+
'Annotation "@testWith" is invalid, "@testWith" should be followed by a space and a value.',
64+
70,
65+
],
66+
[
67+
'Annotation "@ticket" is invalid, "@ticket" should be followed by a space and a value.',
68+
76,
69+
],
70+
[
71+
'Annotation "@uses" is invalid, "@uses" should be followed by a space and a value.',
72+
82,
73+
],
74+
]);
75+
}
76+
77+
/**
78+
* @return string[]
79+
*/
80+
public static function getAdditionalConfigFiles(): array
81+
{
82+
return [
83+
__DIR__ . '/../../../extension.neon',
84+
];
85+
}
86+
87+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /