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 bd84b62

Browse files
authored
Added support for @phpstan-require-extends and @phpstan-require-implements PHPDoc tags
1 parent 77db537 commit bd84b62

File tree

7 files changed

+270
-0
lines changed

7 files changed

+270
-0
lines changed

‎src/Ast/PhpDoc/PhpDocNode.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,31 @@ static function (PhpDocTagValueNode $value): bool {
187187
);
188188
}
189189

190+
/**
191+
* @return RequireExtendsTagValueNode[]
192+
*/
193+
public function getRequireExtendsTagValues(string $tagName = '@phpstan-require-extends'): array
194+
{
195+
return array_filter(
196+
array_column($this->getTagsByName($tagName), 'value'),
197+
static function (PhpDocTagValueNode $value): bool {
198+
return $value instanceof RequireExtendsTagValueNode;
199+
}
200+
);
201+
}
202+
203+
/**
204+
* @return RequireImplementsTagValueNode[]
205+
*/
206+
public function getRequireImplementsTagValues(string $tagName = '@phpstan-require-implements'): array
207+
{
208+
return array_filter(
209+
array_column($this->getTagsByName($tagName), 'value'),
210+
static function (PhpDocTagValueNode $value): bool {
211+
return $value instanceof RequireImplementsTagValueNode;
212+
}
213+
);
214+
}
190215

191216
/**
192217
* @return DeprecatedTagValueNode[]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
4+
5+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
6+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
7+
use function trim;
8+
9+
class RequireExtendsTagValueNode implements PhpDocTagValueNode
10+
{
11+
12+
use NodeAttributes;
13+
14+
/** @var TypeNode */
15+
public $type;
16+
17+
/** @var string (may be empty) */
18+
public $description;
19+
20+
public function __construct(TypeNode $type, string $description)
21+
{
22+
$this->type = $type;
23+
$this->description = $description;
24+
}
25+
26+
27+
public function __toString(): string
28+
{
29+
return trim("{$this->type}{$this->description}");
30+
}
31+
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
4+
5+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
6+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
7+
use function trim;
8+
9+
class RequireImplementsTagValueNode implements PhpDocTagValueNode
10+
{
11+
12+
use NodeAttributes;
13+
14+
/** @var TypeNode */
15+
public $type;
16+
17+
/** @var string (may be empty) */
18+
public $description;
19+
20+
public function __construct(TypeNode $type, string $description)
21+
{
22+
$this->type = $type;
23+
$this->description = $description;
24+
}
25+
26+
27+
public function __toString(): string
28+
{
29+
return trim("{$this->type}{$this->description}");
30+
}
31+
32+
}

‎src/Parser/PhpDocParser.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,16 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
408408
$tagValue = $this->parseMixinTagValue($tokens);
409409
break;
410410

411+
case '@psalm-require-extends':
412+
case '@phpstan-require-extends':
413+
$tagValue = $this->parseRequireExtendsTagValue($tokens);
414+
break;
415+
416+
case '@psalm-require-implements':
417+
case '@phpstan-require-implements':
418+
$tagValue = $this->parseRequireImplementsTagValue($tokens);
419+
break;
420+
411421
case '@deprecated':
412422
$tagValue = $this->parseDeprecatedTagValue($tokens);
413423
break;
@@ -877,6 +887,20 @@ private function parseMixinTagValue(TokenIterator $tokens): Ast\PhpDoc\MixinTagV
877887
return new Ast\PhpDoc\MixinTagValueNode($type, $description);
878888
}
879889

890+
private function parseRequireExtendsTagValue(TokenIterator $tokens): Ast\PhpDoc\RequireExtendsTagValueNode
891+
{
892+
$type = $this->typeParser->parse($tokens);
893+
$description = $this->parseOptionalDescription($tokens, true);
894+
return new Ast\PhpDoc\RequireExtendsTagValueNode($type, $description);
895+
}
896+
897+
private function parseRequireImplementsTagValue(TokenIterator $tokens): Ast\PhpDoc\RequireImplementsTagValueNode
898+
{
899+
$type = $this->typeParser->parse($tokens);
900+
$description = $this->parseOptionalDescription($tokens, true);
901+
return new Ast\PhpDoc\RequireImplementsTagValueNode($type, $description);
902+
}
903+
880904
private function parseDeprecatedTagValue(TokenIterator $tokens): Ast\PhpDoc\DeprecatedTagValueNode
881905
{
882906
$description = $this->parseOptionalDescription($tokens);

‎src/Printer/Printer.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
2929
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
3030
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
31+
use PHPStan\PhpDocParser\Ast\PhpDoc\RequireExtendsTagValueNode;
32+
use PHPStan\PhpDocParser\Ast\PhpDoc\RequireImplementsTagValueNode;
3133
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
3234
use PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode;
3335
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
@@ -283,6 +285,14 @@ private function printTagValue(PhpDocTagValueNode $node): string
283285
$type = $this->printType($node->type);
284286
return trim("{$type}{$node->description}");
285287
}
288+
if ($node instanceof RequireExtendsTagValueNode) {
289+
$type = $this->printType($node->type);
290+
return trim("{$type}{$node->description}");
291+
}
292+
if ($node instanceof RequireImplementsTagValueNode) {
293+
$type = $this->printType($node->type);
294+
return trim("{$type}{$node->description}");
295+
}
286296
if ($node instanceof ParamOutTagValueNode) {
287297
$type = $this->printType($node->type);
288298
return trim("{$type}{$node->parameterName}{$node->description}");

‎tests/PHPStan/Ast/ToString/PhpDocToStringTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
2929
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
3030
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
31+
use PHPStan\PhpDocParser\Ast\PhpDoc\RequireExtendsTagValueNode;
32+
use PHPStan\PhpDocParser\Ast\PhpDoc\RequireImplementsTagValueNode;
3133
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
3234
use PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode;
3335
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
@@ -213,6 +215,15 @@ public static function provideClassCases(): Generator
213215
['Foo\\Bar Baz', new MixinTagValueNode(new IdentifierTypeNode('Foo\\Bar'), 'Baz')],
214216
];
215217

218+
yield from [
219+
['PHPUnit\\TestCase', new RequireExtendsTagValueNode(new IdentifierTypeNode('PHPUnit\\TestCase'), '')],
220+
['Foo\\Bar Baz', new RequireExtendsTagValueNode(new IdentifierTypeNode('Foo\\Bar'), 'Baz')],
221+
];
222+
yield from [
223+
['PHPUnit\\TestCase', new RequireImplementsTagValueNode(new IdentifierTypeNode('PHPUnit\\TestCase'), '')],
224+
['Foo\\Bar Baz', new RequireImplementsTagValueNode(new IdentifierTypeNode('Foo\\Bar'), 'Baz')],
225+
];
226+
216227
yield from [
217228
['Foo array<string>', new TypeAliasTagValueNode('Foo', $arrayOfStrings)],
218229
['Test from Foo\Bar', new TypeAliasImportTagValueNode('Test', $bar, null)],

‎tests/PHPStan/Parser/PhpDocParserTest.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
3636
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
3737
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
38+
use PHPStan\PhpDocParser\Ast\PhpDoc\RequireExtendsTagValueNode;
39+
use PHPStan\PhpDocParser\Ast\PhpDoc\RequireImplementsTagValueNode;
3840
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
3941
use PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode;
4042
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
@@ -100,6 +102,8 @@ protected function setUp(): void
100102
* @dataProvider provideReturnTagsData
101103
* @dataProvider provideThrowsTagsData
102104
* @dataProvider provideMixinTagsData
105+
* @dataProvider provideRequireExtendsTagsData
106+
* @dataProvider provideRequireImplementsTagsData
103107
* @dataProvider provideDeprecatedTagsData
104108
* @dataProvider providePropertyTagsData
105109
* @dataProvider provideMethodTagsData
@@ -1908,6 +1912,138 @@ public function provideMixinTagsData(): Iterator
19081912
];
19091913
}
19101914

1915+
public function provideRequireExtendsTagsData(): Iterator
1916+
{
1917+
yield [
1918+
'OK without description',
1919+
'/** @phpstan-require-extends Foo */',
1920+
new PhpDocNode([
1921+
new PhpDocTagNode(
1922+
'@phpstan-require-extends',
1923+
new RequireExtendsTagValueNode(
1924+
new IdentifierTypeNode('Foo'),
1925+
''
1926+
)
1927+
),
1928+
]),
1929+
];
1930+
1931+
yield [
1932+
'OK with description',
1933+
'/** @phpstan-require-extends Foo optional description */',
1934+
new PhpDocNode([
1935+
new PhpDocTagNode(
1936+
'@phpstan-require-extends',
1937+
new RequireExtendsTagValueNode(
1938+
new IdentifierTypeNode('Foo'),
1939+
'optional description'
1940+
)
1941+
),
1942+
]),
1943+
];
1944+
1945+
yield [
1946+
'OK with psalm-prefix description',
1947+
'/** @psalm-require-extends Foo optional description */',
1948+
new PhpDocNode([
1949+
new PhpDocTagNode(
1950+
'@psalm-require-extends',
1951+
new RequireExtendsTagValueNode(
1952+
new IdentifierTypeNode('Foo'),
1953+
'optional description'
1954+
)
1955+
),
1956+
]),
1957+
];
1958+
1959+
yield [
1960+
'invalid without type and description',
1961+
'/** @phpstan-require-extends */',
1962+
new PhpDocNode([
1963+
new PhpDocTagNode(
1964+
'@phpstan-require-extends',
1965+
new InvalidTagValueNode(
1966+
'',
1967+
new ParserException(
1968+
'*/',
1969+
Lexer::TOKEN_CLOSE_PHPDOC,
1970+
29,
1971+
Lexer::TOKEN_IDENTIFIER,
1972+
null,
1973+
1
1974+
)
1975+
)
1976+
),
1977+
]),
1978+
];
1979+
}
1980+
1981+
public function provideRequireImplementsTagsData(): Iterator
1982+
{
1983+
yield [
1984+
'OK without description',
1985+
'/** @phpstan-require-implements Foo */',
1986+
new PhpDocNode([
1987+
new PhpDocTagNode(
1988+
'@phpstan-require-implements',
1989+
new RequireImplementsTagValueNode(
1990+
new IdentifierTypeNode('Foo'),
1991+
''
1992+
)
1993+
),
1994+
]),
1995+
];
1996+
1997+
yield [
1998+
'OK with description',
1999+
'/** @phpstan-require-implements Foo optional description */',
2000+
new PhpDocNode([
2001+
new PhpDocTagNode(
2002+
'@phpstan-require-implements',
2003+
new RequireImplementsTagValueNode(
2004+
new IdentifierTypeNode('Foo'),
2005+
'optional description'
2006+
)
2007+
),
2008+
]),
2009+
];
2010+
2011+
yield [
2012+
'OK with psalm-prefix description',
2013+
'/** @psalm-require-implements Foo optional description */',
2014+
new PhpDocNode([
2015+
new PhpDocTagNode(
2016+
'@psalm-require-implements',
2017+
new RequireImplementsTagValueNode(
2018+
new IdentifierTypeNode('Foo'),
2019+
'optional description'
2020+
)
2021+
),
2022+
]),
2023+
];
2024+
2025+
yield [
2026+
'invalid without type and description',
2027+
'/** @phpstan-require-implements */',
2028+
new PhpDocNode([
2029+
new PhpDocTagNode(
2030+
'@phpstan-require-implements',
2031+
new InvalidTagValueNode(
2032+
'',
2033+
new ParserException(
2034+
'*/',
2035+
Lexer::TOKEN_CLOSE_PHPDOC,
2036+
32,
2037+
Lexer::TOKEN_IDENTIFIER,
2038+
null,
2039+
1
2040+
)
2041+
)
2042+
),
2043+
]),
2044+
];
2045+
}
2046+
19112047
public function provideDeprecatedTagsData(): Iterator
19122048
{
19132049
yield [

0 commit comments

Comments
(0)

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