diff --git a/src/Ast/PhpDoc/PhpDocNode.php b/src/Ast/PhpDoc/PhpDocNode.php index 64a57af..d0f2829 100644 --- a/src/Ast/PhpDoc/PhpDocNode.php +++ b/src/Ast/PhpDoc/PhpDocNode.php @@ -238,6 +238,20 @@ class PhpDocNode implements Node } + /** + * @return TypeAliasTagValueNode[] + */ + public function getTypeAliasTagValues(string $tagName = '@phpstan-type'): array + { + return array_column( + array_filter($this->getTagsByName($tagName), static function (PhpDocTagNode $tag): bool { + return $tag->value instanceof TypeAliasTagValueNode; + }), + 'value' + ); + } + + public function __toString(): string { return "/**\n * " . implode("\n * ", $this->children) . '*/'; diff --git a/src/Ast/PhpDoc/TypeAliasTagValueNode.php b/src/Ast/PhpDoc/TypeAliasTagValueNode.php new file mode 100644 index 0000000..4a4ac61 --- /dev/null +++ b/src/Ast/PhpDoc/TypeAliasTagValueNode.php @@ -0,0 +1,28 @@ +alias = $alias; + $this->type = $type; + } + + + public function __toString(): string + { + return trim("{$this->alias} {$this->type}"); + } + +} diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index a148da6..158b633 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -189,6 +189,11 @@ class PhpDocParser $tagValue = $this->parseExtendsTagValue('@use', $tokens); break; + case '@phpstan-type': + case '@psalm-type': + $tagValue = $this->parseTypeAliasTagValue($tokens); + break; + default: $tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens)); break; @@ -364,6 +369,19 @@ class PhpDocParser throw new \PHPStan\ShouldNotHappenException(); } + private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasTagValueNode + { + $alias = $tokens->currentTokenValue(); + $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); + + // support psalm-type syntax + $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); + + $type = $this->typeParser->parse($tokens); + + return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type); + } + private function parseOptionalVariableName(TokenIterator $tokens): string { if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 79b55e4..61c498a 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -23,6 +23,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; @@ -66,6 +67,7 @@ class PhpDocParserTest extends \PHPUnit\Framework\TestCase * @dataProvider provideMultiLinePhpDocData * @dataProvider provideTemplateTagsData * @dataProvider provideExtendsTagsData + * @dataProvider provideTypeAliasTagsData * @dataProvider provideRealWorldExampleData * @dataProvider provideDescriptionWithOrWithoutHtml * @param string $label @@ -2858,6 +2860,81 @@ some text in the middle' ]; } + public function provideTypeAliasTagsData(): \Iterator + { + yield [ + 'OK', + '/** @phpstan-type TypeAlias string|int */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new TypeAliasTagValueNode( + 'TypeAlias', + new UnionTypeNode([ + new IdentifierTypeNode('string'), + new IdentifierTypeNode('int'), + ]) + ) + ), + ]), + ]; + + yield [ + 'OK with psalm syntax', + '/** @psalm-type TypeAlias=string|int */', + new PhpDocNode([ + new PhpDocTagNode( + '@psalm-type', + new TypeAliasTagValueNode( + 'TypeAlias', + new UnionTypeNode([ + new IdentifierTypeNode('string'), + new IdentifierTypeNode('int'), + ]) + ) + ), + ]), + ]; + + yield [ + 'invalid without type', + '/** @phpstan-type TypeAlias */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new InvalidTagValueNode( + 'TypeAlias', + new ParserException( + '*/', + Lexer::TOKEN_CLOSE_PHPDOC, + 28, + Lexer::TOKEN_IDENTIFIER + ) + ) + ), + ]), + ]; + + yield [ + 'invalid empty', + '/** @phpstan-type */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new InvalidTagValueNode( + '', + new ParserException( + '*/', + Lexer::TOKEN_CLOSE_PHPDOC, + 18, + Lexer::TOKEN_IDENTIFIER + ) + ) + ), + ]), + ]; + } + public function providerDebug(): \Iterator { $sample = '/**