Skip to content

Commit 2e17e4a

Browse files
jiripudilondrejmirtes
authored andcommittedFeb 28, 2021
type aliases: support @phpstan-import-type and @psalm-import-type tags
1 parent 2d862ef commit 2e17e4a

File tree

4 files changed

+173
-2
lines changed

4 files changed

+173
-2
lines changed
 

‎src/Ast/PhpDoc/PhpDocNode.php

+14
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,20 @@ public function getTypeAliasTagValues(string $tagName = '@phpstan-type'): array
252252
}
253253

254254

255+
/**
256+
* @return TypeAliasImportTagValueNode[]
257+
*/
258+
public function getTypeAliasImportTagValues(string $tagName = '@phpstan-import-type'): array
259+
{
260+
return array_column(
261+
array_filter($this->getTagsByName($tagName), static function (PhpDocTagNode $tag): bool {
262+
return $tag->value instanceof TypeAliasImportTagValueNode;
263+
}),
264+
'value'
265+
);
266+
}
267+
268+
255269
public function __toString(): string
256270
{
257271
return "/**\n * " . implode("\n * ", $this->children) . '*/';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
4+
5+
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
6+
7+
class TypeAliasImportTagValueNode implements PhpDocTagValueNode
8+
{
9+
10+
/** @var string */
11+
public $importedAlias;
12+
13+
/** @var IdentifierTypeNode */
14+
public $importedFrom;
15+
16+
/** @var string|null */
17+
public $importedAs;
18+
19+
public function __construct(string $importedAlias, IdentifierTypeNode $importedFrom, ?string $importedAs)
20+
{
21+
$this->importedAlias = $importedAlias;
22+
$this->importedFrom = $importedFrom;
23+
$this->importedAs = $importedAs;
24+
}
25+
26+
public function __toString(): string
27+
{
28+
return trim(
29+
"{$this->importedAlias} from {$this->importedFrom}"
30+
. ($this->importedAs !== null ? " as {$this->importedAs}" : '')
31+
);
32+
}
33+
34+
}

‎src/Parser/PhpDocParser.php

+31
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
194194
$tagValue = $this->parseTypeAliasTagValue($tokens);
195195
break;
196196

197+
case '@phpstan-import-type':
198+
case '@psalm-import-type':
199+
$tagValue = $this->parseTypeAliasImportTagValue($tokens);
200+
break;
201+
197202
default:
198203
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
199204
break;
@@ -382,6 +387,32 @@ private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeA
382387
return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type);
383388
}
384389

390+
private function parseTypeAliasImportTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasImportTagValueNode
391+
{
392+
$importedAlias = $tokens->currentTokenValue();
393+
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
394+
395+
if (!$tokens->tryConsumeTokenValue('from')) {
396+
throw new \PHPStan\PhpDocParser\Parser\ParserException(
397+
$tokens->currentTokenValue(),
398+
$tokens->currentTokenType(),
399+
$tokens->currentTokenOffset(),
400+
Lexer::TOKEN_IDENTIFIER
401+
);
402+
}
403+
404+
$importedFrom = $this->typeParser->parse($tokens);
405+
assert($importedFrom instanceof IdentifierTypeNode);
406+
407+
$importedAs = null;
408+
if ($tokens->tryConsumeTokenValue('as')) {
409+
$importedAs = $tokens->currentTokenValue();
410+
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
411+
}
412+
413+
return new Ast\PhpDoc\TypeAliasImportTagValueNode($importedAlias, $importedFrom, $importedAs);
414+
}
415+
385416
private function parseOptionalVariableName(TokenIterator $tokens): string
386417
{
387418
if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {

‎tests/PHPStan/Parser/PhpDocParserTest.php

+94-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
2424
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
2525
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
26+
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode;
2627
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode;
2728
use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode;
2829
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
@@ -68,6 +69,7 @@ protected function setUp(): void
6869
* @dataProvider provideTemplateTagsData
6970
* @dataProvider provideExtendsTagsData
7071
* @dataProvider provideTypeAliasTagsData
72+
* @dataProvider provideTypeAliasImportTagsData
7173
* @dataProvider provideRealWorldExampleData
7274
* @dataProvider provideDescriptionWithOrWithoutHtml
7375
* @param string $label
@@ -2904,7 +2906,7 @@ public function provideTypeAliasTagsData(): \Iterator
29042906
'@phpstan-type',
29052907
new InvalidTagValueNode(
29062908
'TypeAlias',
2907-
new ParserException(
2909+
new \PHPStan\PhpDocParser\Parser\ParserException(
29082910
'*/',
29092911
Lexer::TOKEN_CLOSE_PHPDOC,
29102912
28,
@@ -2923,7 +2925,7 @@ public function provideTypeAliasTagsData(): \Iterator
29232925
'@phpstan-type',
29242926
new InvalidTagValueNode(
29252927
'',
2926-
new ParserException(
2928+
new \PHPStan\PhpDocParser\Parser\ParserException(
29272929
'*/',
29282930
Lexer::TOKEN_CLOSE_PHPDOC,
29292931
18,
@@ -2935,6 +2937,96 @@ public function provideTypeAliasTagsData(): \Iterator
29352937
];
29362938
}
29372939

2940+
public function provideTypeAliasImportTagsData(): \Iterator
2941+
{
2942+
yield [
2943+
'OK',
2944+
'/** @phpstan-import-type TypeAlias from AnotherClass */',
2945+
new PhpDocNode([
2946+
new PhpDocTagNode(
2947+
'@phpstan-import-type',
2948+
new TypeAliasImportTagValueNode(
2949+
'TypeAlias',
2950+
new IdentifierTypeNode('AnotherClass'),
2951+
null
2952+
)
2953+
),
2954+
]),
2955+
];
2956+
2957+
yield [
2958+
'OK with alias',
2959+
'/** @phpstan-import-type TypeAlias from AnotherClass as DifferentAlias */',
2960+
new PhpDocNode([
2961+
new PhpDocTagNode(
2962+
'@phpstan-import-type',
2963+
new TypeAliasImportTagValueNode(
2964+
'TypeAlias',
2965+
new IdentifierTypeNode('AnotherClass'),
2966+
'DifferentAlias'
2967+
)
2968+
),
2969+
]),
2970+
];
2971+
2972+
yield [
2973+
'invalid missing from',
2974+
'/** @phpstan-import-type TypeAlias */',
2975+
new PhpDocNode([
2976+
new PhpDocTagNode(
2977+
'@phpstan-import-type',
2978+
new InvalidTagValueNode(
2979+
'TypeAlias',
2980+
new \PHPStan\PhpDocParser\Parser\ParserException(
2981+
'*/',
2982+
Lexer::TOKEN_CLOSE_PHPDOC,
2983+
35,
2984+
Lexer::TOKEN_IDENTIFIER
2985+
)
2986+
)
2987+
),
2988+
]),
2989+
];
2990+
2991+
yield [
2992+
'invalid missing from with alias',
2993+
'/** @phpstan-import-type TypeAlias as DifferentAlias */',
2994+
new PhpDocNode([
2995+
new PhpDocTagNode(
2996+
'@phpstan-import-type',
2997+
new InvalidTagValueNode(
2998+
'TypeAlias as DifferentAlias',
2999+
new \PHPStan\PhpDocParser\Parser\ParserException(
3000+
'as',
3001+
Lexer::TOKEN_IDENTIFIER,
3002+
35,
3003+
Lexer::TOKEN_IDENTIFIER
3004+
)
3005+
)
3006+
),
3007+
]),
3008+
];
3009+
3010+
yield [
3011+
'invalid empty',
3012+
'/** @phpstan-import-type */',
3013+
new PhpDocNode([
3014+
new PhpDocTagNode(
3015+
'@phpstan-import-type',
3016+
new InvalidTagValueNode(
3017+
'',
3018+
new \PHPStan\PhpDocParser\Parser\ParserException(
3019+
'*/',
3020+
Lexer::TOKEN_CLOSE_PHPDOC,
3021+
25,
3022+
Lexer::TOKEN_IDENTIFIER
3023+
)
3024+
)
3025+
),
3026+
]),
3027+
];
3028+
}
3029+
29383030
public function providerDebug(): \Iterator
29393031
{
29403032
$sample = '/**

0 commit comments

Comments
 (0)
Please sign in to comment.