Skip to content

Commit 14346cb

Browse files
arnaud-lbondrejmirtes
authored andcommitted
1 parent 8c4ef2a commit 14346cb

File tree

7 files changed

+294
-1
lines changed

7 files changed

+294
-1
lines changed
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
4+
5+
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
6+
7+
class ExtendsTagValueNode implements PhpDocTagValueNode
8+
{
9+
10+
/** @var GenericTypeNode */
11+
public $type;
12+
13+
/** @var string (may be empty) */
14+
public $description;
15+
16+
public function __construct(GenericTypeNode $type, string $description)
17+
{
18+
$this->type = $type;
19+
$this->description = $description;
20+
}
21+
22+
23+
public function __toString(): string
24+
{
25+
return trim("{$this->type} {$this->description}");
26+
}
27+
28+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
4+
5+
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
6+
7+
class ImplementsTagValueNode implements PhpDocTagValueNode
8+
{
9+
10+
/** @var GenericTypeNode */
11+
public $type;
12+
13+
/** @var string (may be empty) */
14+
public $description;
15+
16+
public function __construct(GenericTypeNode $type, string $description)
17+
{
18+
$this->type = $type;
19+
$this->description = $description;
20+
}
21+
22+
23+
public function __toString(): string
24+
{
25+
return trim("{$this->type} {$this->description}");
26+
}
27+
28+
}

src/Ast/PhpDoc/PhpDocNode.php

+42
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,48 @@ public function getTemplateTagValues(): array
8484
}
8585

8686

87+
/**
88+
* @return ExtendsTagValueNode[]
89+
*/
90+
public function getExtendsTagValues(): array
91+
{
92+
return array_column(
93+
array_filter($this->getTagsByName('@extends'), static function (PhpDocTagNode $tag): bool {
94+
return $tag->value instanceof ExtendsTagValueNode;
95+
}),
96+
'value'
97+
);
98+
}
99+
100+
101+
/**
102+
* @return ImplementsTagValueNode[]
103+
*/
104+
public function getImplementsTagValues(): array
105+
{
106+
return array_column(
107+
array_filter($this->getTagsByName('@implements'), static function (PhpDocTagNode $tag): bool {
108+
return $tag->value instanceof ImplementsTagValueNode;
109+
}),
110+
'value'
111+
);
112+
}
113+
114+
115+
/**
116+
* @return UsesTagValueNode[]
117+
*/
118+
public function getUsesTagValues(): array
119+
{
120+
return array_column(
121+
array_filter($this->getTagsByName('@uses'), static function (PhpDocTagNode $tag): bool {
122+
return $tag->value instanceof UsesTagValueNode;
123+
}),
124+
'value'
125+
);
126+
}
127+
128+
87129
/**
88130
* @return ReturnTagValueNode[]
89131
*/

src/Ast/PhpDoc/UsesTagValueNode.php

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

src/Parser/PhpDocParser.php

+25
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
145145
$tagValue = $this->parseTemplateTagValue($tokens);
146146
break;
147147

148+
case '@extends':
149+
case '@implements':
150+
case '@uses':
151+
$tagValue = $this->parseExtendsTagValue($tag, $tokens);
152+
break;
153+
148154
default:
149155
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
150156
break;
@@ -292,6 +298,25 @@ private function parseTemplateTagValue(TokenIterator $tokens): Ast\PhpDoc\Templa
292298
return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, $description);
293299
}
294300

301+
private function parseExtendsTagValue(string $tagName, TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode
302+
{
303+
$baseType = new IdentifierTypeNode($tokens->currentTokenValue());
304+
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
305+
306+
$type = $this->typeParser->parseGeneric($tokens, $baseType);
307+
308+
$description = $this->parseOptionalDescription($tokens);
309+
310+
switch ($tagName) {
311+
case '@extends':
312+
return new Ast\PhpDoc\ExtendsTagValueNode($type, $description);
313+
case '@implements':
314+
return new Ast\PhpDoc\ImplementsTagValueNode($type, $description);
315+
case '@uses':
316+
return new Ast\PhpDoc\UsesTagValueNode($type, $description);
317+
}
318+
}
319+
295320
private function parseOptionalVariableName(TokenIterator $tokens): string
296321
{
297322
if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {

src/Parser/TypeParser.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private function parseNullable(TokenIterator $tokens): Ast\Type\TypeNode
105105
}
106106

107107

108-
private function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\TypeNode
108+
public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\GenericTypeNode
109109
{
110110
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
111111
$genericTypes[] = $this->parse($tokens);

tests/PHPStan/Parser/PhpDocParserTest.php

+142
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
66
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
77
use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode;
8+
use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode;
89
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
10+
use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode;
911
use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
1012
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
1113
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode;
@@ -17,8 +19,10 @@
1719
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
1820
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
1921
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
22+
use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode;
2023
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
2124
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
25+
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
2226
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
2327
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
2428
use PHPStan\PhpDocParser\Lexer\Lexer;
@@ -51,6 +55,7 @@ protected function setUp(): void
5155
* @dataProvider provideSingleLinePhpDocData
5256
* @dataProvider provideMultiLinePhpDocData
5357
* @dataProvider provideTemplateTagsData
58+
* @dataProvider provideExtendsTagsData
5459
* @dataProvider provideRealWorldExampleData
5560
* @param string $label
5661
* @param string $input
@@ -2368,6 +2373,143 @@ public function provideTemplateTagsData(): \Iterator
23682373
];
23692374
}
23702375

2376+
public function provideExtendsTagsData(): \Iterator
2377+
{
2378+
yield [
2379+
'OK with one argument',
2380+
'/** @extends Foo<A> */',
2381+
new PhpDocNode([
2382+
new PhpDocTagNode(
2383+
'@extends',
2384+
new ExtendsTagValueNode(
2385+
new GenericTypeNode(
2386+
new IdentifierTypeNode('Foo'),
2387+
[
2388+
new IdentifierTypeNode('A'),
2389+
]
2390+
),
2391+
''
2392+
)
2393+
),
2394+
]),
2395+
];
2396+
2397+
yield [
2398+
'OK with two arguments',
2399+
'/** @extends Foo<A,B> */',
2400+
new PhpDocNode([
2401+
new PhpDocTagNode(
2402+
'@extends',
2403+
new ExtendsTagValueNode(
2404+
new GenericTypeNode(
2405+
new IdentifierTypeNode('Foo'),
2406+
[
2407+
new IdentifierTypeNode('A'),
2408+
new IdentifierTypeNode('B'),
2409+
]
2410+
),
2411+
''
2412+
)
2413+
),
2414+
]),
2415+
];
2416+
2417+
yield [
2418+
'OK @implements',
2419+
'/** @implements Foo<A,B> */',
2420+
new PhpDocNode([
2421+
new PhpDocTagNode(
2422+
'@implements',
2423+
new ImplementsTagValueNode(
2424+
new GenericTypeNode(
2425+
new IdentifierTypeNode('Foo'),
2426+
[
2427+
new IdentifierTypeNode('A'),
2428+
new IdentifierTypeNode('B'),
2429+
]
2430+
),
2431+
''
2432+
)
2433+
),
2434+
]),
2435+
];
2436+
2437+
yield [
2438+
'OK @uses',
2439+
'/** @uses Foo<A,B> */',
2440+
new PhpDocNode([
2441+
new PhpDocTagNode(
2442+
'@uses',
2443+
new UsesTagValueNode(
2444+
new GenericTypeNode(
2445+
new IdentifierTypeNode('Foo'),
2446+
[
2447+
new IdentifierTypeNode('A'),
2448+
new IdentifierTypeNode('B'),
2449+
]
2450+
),
2451+
''
2452+
)
2453+
),
2454+
]),
2455+
];
2456+
2457+
yield [
2458+
'OK with description',
2459+
'/** @extends Foo<A> extends foo*/',
2460+
new PhpDocNode([
2461+
new PhpDocTagNode(
2462+
'@extends',
2463+
new ExtendsTagValueNode(
2464+
new GenericTypeNode(
2465+
new IdentifierTypeNode('Foo'),
2466+
[new IdentifierTypeNode('A')]
2467+
),
2468+
'extends foo'
2469+
)
2470+
),
2471+
]),
2472+
];
2473+
2474+
yield [
2475+
'invalid without type',
2476+
'/** @extends */',
2477+
new PhpDocNode([
2478+
new PhpDocTagNode(
2479+
'@extends',
2480+
new InvalidTagValueNode(
2481+
'',
2482+
new \PHPStan\PhpDocParser\Parser\ParserException(
2483+
'*/',
2484+
Lexer::TOKEN_CLOSE_PHPDOC,
2485+
13,
2486+
Lexer::TOKEN_IDENTIFIER
2487+
)
2488+
)
2489+
),
2490+
]),
2491+
];
2492+
2493+
yield [
2494+
'invalid without arguments',
2495+
'/** @extends Foo */',
2496+
new PhpDocNode([
2497+
new PhpDocTagNode(
2498+
'@extends',
2499+
new InvalidTagValueNode(
2500+
'Foo',
2501+
new \PHPStan\PhpDocParser\Parser\ParserException(
2502+
'*/',
2503+
Lexer::TOKEN_CLOSE_PHPDOC,
2504+
17,
2505+
Lexer::TOKEN_OPEN_ANGLE_BRACKET
2506+
)
2507+
)
2508+
),
2509+
]),
2510+
];
2511+
}
2512+
23712513
public function providerDebug(): \Iterator
23722514
{
23732515
$sample = '/**

0 commit comments

Comments
 (0)