Skip to content

Commit 2d862ef

Browse files
jiripudilondrejmirtes
authored andcommitted
type aliases: support @phpstan-type and @psalm-type tags
1 parent 2ce4c66 commit 2d862ef

File tree

4 files changed

+137
-0
lines changed

4 files changed

+137
-0
lines changed

Diff for: src/Ast/PhpDoc/PhpDocNode.php

+14
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,20 @@ public function getMethodTagValues(string $tagName = '@method'): array
238238
}
239239

240240

241+
/**
242+
* @return TypeAliasTagValueNode[]
243+
*/
244+
public function getTypeAliasTagValues(string $tagName = '@phpstan-type'): array
245+
{
246+
return array_column(
247+
array_filter($this->getTagsByName($tagName), static function (PhpDocTagNode $tag): bool {
248+
return $tag->value instanceof TypeAliasTagValueNode;
249+
}),
250+
'value'
251+
);
252+
}
253+
254+
241255
public function __toString(): string
242256
{
243257
return "/**\n * " . implode("\n * ", $this->children) . '*/';

Diff for: src/Ast/PhpDoc/TypeAliasTagValueNode.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\TypeNode;
6+
7+
class TypeAliasTagValueNode implements PhpDocTagValueNode
8+
{
9+
10+
/** @var string */
11+
public $alias;
12+
13+
/** @var TypeNode */
14+
public $type;
15+
16+
public function __construct(string $alias, TypeNode $type)
17+
{
18+
$this->alias = $alias;
19+
$this->type = $type;
20+
}
21+
22+
23+
public function __toString(): string
24+
{
25+
return trim("{$this->alias} {$this->type}");
26+
}
27+
28+
}

Diff for: src/Parser/PhpDocParser.php

+18
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
189189
$tagValue = $this->parseExtendsTagValue('@use', $tokens);
190190
break;
191191

192+
case '@phpstan-type':
193+
case '@psalm-type':
194+
$tagValue = $this->parseTypeAliasTagValue($tokens);
195+
break;
196+
192197
default:
193198
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
194199
break;
@@ -364,6 +369,19 @@ private function parseExtendsTagValue(string $tagName, TokenIterator $tokens): A
364369
throw new \PHPStan\ShouldNotHappenException();
365370
}
366371

372+
private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasTagValueNode
373+
{
374+
$alias = $tokens->currentTokenValue();
375+
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
376+
377+
// support psalm-type syntax
378+
$tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL);
379+
380+
$type = $this->typeParser->parse($tokens);
381+
382+
return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type);
383+
}
384+
367385
private function parseOptionalVariableName(TokenIterator $tokens): string
368386
{
369387
if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {

Diff for: tests/PHPStan/Parser/PhpDocParserTest.php

+77
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\TypeAliasTagValueNode;
2627
use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode;
2728
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
2829
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
@@ -66,6 +67,7 @@ protected function setUp(): void
6667
* @dataProvider provideMultiLinePhpDocData
6768
* @dataProvider provideTemplateTagsData
6869
* @dataProvider provideExtendsTagsData
70+
* @dataProvider provideTypeAliasTagsData
6971
* @dataProvider provideRealWorldExampleData
7072
* @dataProvider provideDescriptionWithOrWithoutHtml
7173
* @param string $label
@@ -2858,6 +2860,81 @@ public function provideExtendsTagsData(): \Iterator
28582860
];
28592861
}
28602862

2863+
public function provideTypeAliasTagsData(): \Iterator
2864+
{
2865+
yield [
2866+
'OK',
2867+
'/** @phpstan-type TypeAlias string|int */',
2868+
new PhpDocNode([
2869+
new PhpDocTagNode(
2870+
'@phpstan-type',
2871+
new TypeAliasTagValueNode(
2872+
'TypeAlias',
2873+
new UnionTypeNode([
2874+
new IdentifierTypeNode('string'),
2875+
new IdentifierTypeNode('int'),
2876+
])
2877+
)
2878+
),
2879+
]),
2880+
];
2881+
2882+
yield [
2883+
'OK with psalm syntax',
2884+
'/** @psalm-type TypeAlias=string|int */',
2885+
new PhpDocNode([
2886+
new PhpDocTagNode(
2887+
'@psalm-type',
2888+
new TypeAliasTagValueNode(
2889+
'TypeAlias',
2890+
new UnionTypeNode([
2891+
new IdentifierTypeNode('string'),
2892+
new IdentifierTypeNode('int'),
2893+
])
2894+
)
2895+
),
2896+
]),
2897+
];
2898+
2899+
yield [
2900+
'invalid without type',
2901+
'/** @phpstan-type TypeAlias */',
2902+
new PhpDocNode([
2903+
new PhpDocTagNode(
2904+
'@phpstan-type',
2905+
new InvalidTagValueNode(
2906+
'TypeAlias',
2907+
new ParserException(
2908+
'*/',
2909+
Lexer::TOKEN_CLOSE_PHPDOC,
2910+
28,
2911+
Lexer::TOKEN_IDENTIFIER
2912+
)
2913+
)
2914+
),
2915+
]),
2916+
];
2917+
2918+
yield [
2919+
'invalid empty',
2920+
'/** @phpstan-type */',
2921+
new PhpDocNode([
2922+
new PhpDocTagNode(
2923+
'@phpstan-type',
2924+
new InvalidTagValueNode(
2925+
'',
2926+
new ParserException(
2927+
'*/',
2928+
Lexer::TOKEN_CLOSE_PHPDOC,
2929+
18,
2930+
Lexer::TOKEN_IDENTIFIER
2931+
)
2932+
)
2933+
),
2934+
]),
2935+
];
2936+
}
2937+
28612938
public function providerDebug(): \Iterator
28622939
{
28632940
$sample = '/**

0 commit comments

Comments
 (0)