Skip to content

Commit 9780d35

Browse files
committed
Bleeding edge - check invalid PHPDocs in previously unchecked statement types
1 parent 694f392 commit 9780d35

10 files changed

+185
-29
lines changed

conf/bleedingEdge.neon

+1
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ parameters:
3232
newRuleLevelHelper: true
3333
instanceofType: true
3434
paramOutVariance: true
35+
allInvalidPhpDocs: true

conf/config.level2.neon

+12-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ rules:
3939
- PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule
4040
- PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule
4141
- PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule
42-
- PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule
43-
- PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule
4442
- PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule
4543
- PHPStan\Rules\Properties\AccessPrivatePropertyThroughStaticRule
4644

@@ -77,13 +75,25 @@ services:
7775
class: PHPStan\Rules\Methods\IllegalConstructorMethodCallRule
7876
-
7977
class: PHPStan\Rules\Methods\IllegalConstructorStaticCallRule
78+
-
79+
class: PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule
80+
arguments:
81+
checkAllInvalidPhpDocs: %featureToggles.allInvalidPhpDocs%
82+
tags:
83+
- phpstan.rules.rule
8084
-
8185
class: PHPStan\Rules\PhpDoc\InvalidPhpDocVarTagTypeRule
8286
arguments:
8387
checkClassCaseSensitivity: %checkClassCaseSensitivity%
8488
checkMissingVarTagTypehint: %checkMissingVarTagTypehint%
8589
tags:
8690
- phpstan.rules.rule
91+
-
92+
class: PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule
93+
arguments:
94+
checkAllInvalidPhpDocs: %featureToggles.allInvalidPhpDocs%
95+
tags:
96+
- phpstan.rules.rule
8797
-
8898
class: PHPStan\Rules\PhpDoc\VarTagChangedExpressionTypeRule
8999
-

conf/config.neon

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ parameters:
6262
newRuleLevelHelper: false
6363
instanceofType: false
6464
paramOutVariance: false
65+
allInvalidPhpDocs: false
6566
fileExtensions:
6667
- php
6768
checkAdvancedIsset: false
@@ -291,6 +292,7 @@ parametersSchema:
291292
newRuleLevelHelper: bool()
292293
instanceofType: bool()
293294
paramOutVariance: bool()
295+
allInvalidPhpDocs: bool()
294296
])
295297
fileExtensions: listOf(string())
296298
checkAdvancedIsset: bool()

src/PhpDoc/StubValidator.php

+1
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ private function getRuleRegistry(Container $container): RuleRegistry
178178
new InvalidPhpDocTagValueRule(
179179
$container->getByType(Lexer::class),
180180
$container->getByType(PhpDocParser::class),
181+
$container->getParameter('featureToggles')['allInvalidPhpDocs'],
181182
),
182183
new InvalidThrowsPhpDocValueRule($fileTypeMapper),
183184

src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php

+28-10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\VirtualNode;
78
use PHPStan\PhpDocParser\Lexer\Lexer;
89
use PHPStan\PhpDocParser\Parser\PhpDocParser;
910
use PHPStan\PhpDocParser\Parser\TokenIterator;
@@ -53,7 +54,11 @@ class InvalidPHPStanDocTagRule implements Rule
5354
'@phpstan-readonly-allow-private-mutation',
5455
];
5556

56-
public function __construct(private Lexer $phpDocLexer, private PhpDocParser $phpDocParser)
57+
public function __construct(
58+
private Lexer $phpDocLexer,
59+
private PhpDocParser $phpDocParser,
60+
private bool $checkAllInvalidPhpDocs,
61+
)
5762
{
5863
}
5964

@@ -64,15 +69,28 @@ public function getNodeType(): string
6469

6570
public function processNode(Node $node, Scope $scope): array
6671
{
67-
if (
68-
!$node instanceof Node\Stmt\ClassLike
69-
&& !$node instanceof Node\FunctionLike
70-
&& !$node instanceof Node\Stmt\Foreach_
71-
&& !$node instanceof Node\Stmt\Property
72-
&& !$node instanceof Node\Expr\Assign
73-
&& !$node instanceof Node\Expr\AssignRef
74-
) {
75-
return [];
72+
if (!$this->checkAllInvalidPhpDocs) {
73+
if (
74+
!$node instanceof Node\Stmt\ClassLike
75+
&& !$node instanceof Node\FunctionLike
76+
&& !$node instanceof Node\Stmt\Foreach_
77+
&& !$node instanceof Node\Stmt\Property
78+
&& !$node instanceof Node\Expr\Assign
79+
&& !$node instanceof Node\Expr\AssignRef
80+
) {
81+
return [];
82+
}
83+
} else {
84+
// mirrored with InvalidPhpDocTagValueRule
85+
if ($node instanceof VirtualNode) {
86+
return [];
87+
}
88+
if ($node instanceof Node\Stmt\Expression) {
89+
return [];
90+
}
91+
if ($node instanceof Node\Expr && !$node instanceof Node\Expr\Assign && !$node instanceof Node\Expr\AssignRef) {
92+
return [];
93+
}
7694
}
7795

7896
$docComment = $node->getDocComment();

src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php

+29-11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\VirtualNode;
78
use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
89
use PHPStan\PhpDocParser\Lexer\Lexer;
910
use PHPStan\PhpDocParser\Parser\PhpDocParser;
@@ -19,7 +20,11 @@
1920
class InvalidPhpDocTagValueRule implements Rule
2021
{
2122

22-
public function __construct(private Lexer $phpDocLexer, private PhpDocParser $phpDocParser)
23+
public function __construct(
24+
private Lexer $phpDocLexer,
25+
private PhpDocParser $phpDocParser,
26+
private bool $checkAllInvalidPhpDocs,
27+
)
2328
{
2429
}
2530

@@ -30,16 +35,29 @@ public function getNodeType(): string
3035

3136
public function processNode(Node $node, Scope $scope): array
3237
{
33-
if (
34-
!$node instanceof Node\Stmt\ClassLike
35-
&& !$node instanceof Node\FunctionLike
36-
&& !$node instanceof Node\Stmt\Foreach_
37-
&& !$node instanceof Node\Stmt\Property
38-
&& !$node instanceof Node\Expr\Assign
39-
&& !$node instanceof Node\Expr\AssignRef
40-
&& !$node instanceof Node\Stmt\ClassConst
41-
) {
42-
return [];
38+
if (!$this->checkAllInvalidPhpDocs) {
39+
if (
40+
!$node instanceof Node\Stmt\ClassLike
41+
&& !$node instanceof Node\FunctionLike
42+
&& !$node instanceof Node\Stmt\Foreach_
43+
&& !$node instanceof Node\Stmt\Property
44+
&& !$node instanceof Node\Expr\Assign
45+
&& !$node instanceof Node\Expr\AssignRef
46+
&& !$node instanceof Node\Stmt\ClassConst
47+
) {
48+
return [];
49+
}
50+
} else {
51+
// mirrored with InvalidPHPStanDocTagRule
52+
if ($node instanceof VirtualNode) {
53+
return [];
54+
}
55+
if ($node instanceof Node\Stmt\Expression) {
56+
return [];
57+
}
58+
if ($node instanceof Node\Expr && !$node instanceof Node\Expr\Assign && !$node instanceof Node\Expr\AssignRef) {
59+
return [];
60+
}
4361
}
4462

4563
$docComment = $node->getDocComment();

tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php

+33-3
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,28 @@
66
use PHPStan\PhpDocParser\Parser\PhpDocParser;
77
use PHPStan\Rules\Rule;
88
use PHPStan\Testing\RuleTestCase;
9+
use function array_merge;
910

1011
/**
1112
* @extends RuleTestCase<InvalidPHPStanDocTagRule>
1213
*/
1314
class InvalidPHPStanDocTagRuleTest extends RuleTestCase
1415
{
1516

17+
private bool $checkAllInvalidPhpDocs;
18+
1619
protected function getRule(): Rule
1720
{
1821
return new InvalidPHPStanDocTagRule(
1922
self::getContainer()->getByType(Lexer::class),
2023
self::getContainer()->getByType(PhpDocParser::class),
24+
$this->checkAllInvalidPhpDocs,
2125
);
2226
}
2327

24-
public function testRule(): void
28+
public function dataRule(): iterable
2529
{
26-
$this->analyse([__DIR__ . '/data/invalid-phpstan-doc.php'], [
30+
$errors = [
2731
[
2832
'Unknown PHPDoc tag: @phpstan-extens',
2933
7,
@@ -32,11 +36,37 @@ public function testRule(): void
3236
'Unknown PHPDoc tag: @phpstan-pararm',
3337
14,
3438
],
35-
]);
39+
[
40+
'Unknown PHPDoc tag: @phpstan-varr',
41+
44,
42+
],
43+
];
44+
yield [false, $errors];
45+
yield [true, array_merge($errors, [
46+
[
47+
'Unknown PHPDoc tag: @phpstan-varr',
48+
47,
49+
],
50+
[
51+
'Unknown PHPDoc tag: @phpstan-varr',
52+
57,
53+
],
54+
])];
55+
}
56+
57+
/**
58+
* @dataProvider dataRule
59+
* @param list<array{0: string, 1: int, 2?: string}> $expectedErrors
60+
*/
61+
public function testRule(bool $checkAllInvalidPhpDocs, array $expectedErrors): void
62+
{
63+
$this->checkAllInvalidPhpDocs = $checkAllInvalidPhpDocs;
64+
$this->analyse([__DIR__ . '/data/invalid-phpstan-doc.php'], $expectedErrors);
3665
}
3766

3867
public function testBug8697(): void
3968
{
69+
$this->checkAllInvalidPhpDocs = true;
4070
$this->analyse([__DIR__ . '/data/bug-8697.php'], []);
4171
}
4272

tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php

+35-3
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,28 @@
66
use PHPStan\PhpDocParser\Parser\PhpDocParser;
77
use PHPStan\Rules\Rule;
88
use PHPStan\Testing\RuleTestCase;
9+
use function array_merge;
910

1011
/**
1112
* @extends RuleTestCase<InvalidPhpDocTagValueRule>
1213
*/
1314
class InvalidPhpDocTagValueRuleTest extends RuleTestCase
1415
{
1516

17+
private bool $checkAllInvalidPhpDocs;
18+
1619
protected function getRule(): Rule
1720
{
1821
return new InvalidPhpDocTagValueRule(
1922
self::getContainer()->getByType(Lexer::class),
2023
self::getContainer()->getByType(PhpDocParser::class),
24+
$this->checkAllInvalidPhpDocs,
2125
);
2226
}
2327

24-
public function testRule(): void
28+
public function dataRule(): iterable
2529
{
26-
$this->analyse([__DIR__ . '/data/invalid-phpdoc.php'], [
30+
$errors = [
2731
[
2832
'PHPDoc tag @param has invalid value (): Unexpected token "\n * ", expected type at offset 13',
2933
25,
@@ -84,16 +88,44 @@ public function testRule(): void
8488
'PHPDoc tag @var has invalid value ((Foo|Bar): Unexpected token "*/", expected \')\' at offset 18',
8589
81,
8690
],
87-
]);
91+
[
92+
'PHPDoc tag @var has invalid value ((Foo&): Unexpected token "*/", expected type at offset 15',
93+
89,
94+
],
95+
[
96+
'PHPDoc tag @var has invalid value ((Foo&): Unexpected token "*/", expected type at offset 15',
97+
92,
98+
],
99+
];
100+
101+
yield [false, $errors];
102+
yield [true, array_merge($errors, [
103+
[
104+
'PHPDoc tag @var has invalid value ((Foo&): Unexpected token "*/", expected type at offset 15',
105+
102,
106+
],
107+
])];
108+
}
109+
110+
/**
111+
* @dataProvider dataRule
112+
* @param list<array{0: string, 1: int, 2?: string}> $expectedErrors
113+
*/
114+
public function testRule(bool $checkAllInvalidPhpDocs, array $expectedErrors): void
115+
{
116+
$this->checkAllInvalidPhpDocs = $checkAllInvalidPhpDocs;
117+
$this->analyse([__DIR__ . '/data/invalid-phpdoc.php'], $expectedErrors);
88118
}
89119

90120
public function testBug4731(): void
91121
{
122+
$this->checkAllInvalidPhpDocs = true;
92123
$this->analyse([__DIR__ . '/data/bug-4731.php'], []);
93124
}
94125

95126
public function testBug4731WithoutFirstTag(): void
96127
{
128+
$this->checkAllInvalidPhpDocs = true;
97129
$this->analyse([__DIR__ . '/data/bug-4731-no-first-tag.php'], []);
98130
}
99131

tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php

+22
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,25 @@ class ClassConstant
8181
const FOO = 1;
8282

8383
}
84+
85+
class AboveProperty
86+
{
87+
88+
/** @var (Foo& */
89+
private $foo;
90+
91+
/** @var (Foo& */
92+
private const TEST = 1;
93+
94+
}
95+
96+
class AboveReturn
97+
{
98+
99+
public function doFoo(): string
100+
{
101+
/** @var (Foo& */
102+
return doFoo();
103+
}
104+
105+
}

tests/PHPStan/Rules/PhpDoc/data/invalid-phpstan-doc.php

+22
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,25 @@ function any()
3636

3737
}
3838
}
39+
40+
class AboveProperty
41+
{
42+
43+
/** @phpstan-varr 1 */
44+
private $foo;
45+
46+
/** @phpstan-varr 1 */
47+
private const TEST = 1;
48+
49+
}
50+
51+
class AboveReturn
52+
{
53+
54+
public function doFoo(): string
55+
{
56+
/** @phpstan-varr string */
57+
return doFoo();
58+
}
59+
60+
}

0 commit comments

Comments
 (0)