type aliases: support @phpstan-import-type and @psalm-import-type tags

This commit is contained in:
Jiří Pudil 2021-02-27 14:47:23 +01:00 committed by Ondřej Mirtes
parent 2d862ef746
commit 2e17e4a907
4 changed files with 173 additions and 2 deletions

View File

@ -252,6 +252,20 @@ class PhpDocNode implements Node
}
/**
* @return TypeAliasImportTagValueNode[]
*/
public function getTypeAliasImportTagValues(string $tagName = '@phpstan-import-type'): array
{
return array_column(
array_filter($this->getTagsByName($tagName), static function (PhpDocTagNode $tag): bool {
return $tag->value instanceof TypeAliasImportTagValueNode;
}),
'value'
);
}
public function __toString(): string
{
return "/**\n * " . implode("\n * ", $this->children) . '*/';

View File

@ -0,0 +1,34 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
class TypeAliasImportTagValueNode implements PhpDocTagValueNode
{
/** @var string */
public $importedAlias;
/** @var IdentifierTypeNode */
public $importedFrom;
/** @var string|null */
public $importedAs;
public function __construct(string $importedAlias, IdentifierTypeNode $importedFrom, ?string $importedAs)
{
$this->importedAlias = $importedAlias;
$this->importedFrom = $importedFrom;
$this->importedAs = $importedAs;
}
public function __toString(): string
{
return trim(
"{$this->importedAlias} from {$this->importedFrom}"
. ($this->importedAs !== null ? " as {$this->importedAs}" : '')
);
}
}

View File

@ -194,6 +194,11 @@ class PhpDocParser
$tagValue = $this->parseTypeAliasTagValue($tokens);
break;
case '@phpstan-import-type':
case '@psalm-import-type':
$tagValue = $this->parseTypeAliasImportTagValue($tokens);
break;
default:
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
break;
@ -382,6 +387,32 @@ class PhpDocParser
return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type);
}
private function parseTypeAliasImportTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeAliasImportTagValueNode
{
$importedAlias = $tokens->currentTokenValue();
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
if (!$tokens->tryConsumeTokenValue('from')) {
throw new \PHPStan\PhpDocParser\Parser\ParserException(
$tokens->currentTokenValue(),
$tokens->currentTokenType(),
$tokens->currentTokenOffset(),
Lexer::TOKEN_IDENTIFIER
);
}
$importedFrom = $this->typeParser->parse($tokens);
assert($importedFrom instanceof IdentifierTypeNode);
$importedAs = null;
if ($tokens->tryConsumeTokenValue('as')) {
$importedAs = $tokens->currentTokenValue();
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
}
return new Ast\PhpDoc\TypeAliasImportTagValueNode($importedAlias, $importedFrom, $importedAs);
}
private function parseOptionalVariableName(TokenIterator $tokens): string
{
if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {

View File

@ -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\TypeAliasImportTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
@ -68,6 +69,7 @@ class PhpDocParserTest extends \PHPUnit\Framework\TestCase
* @dataProvider provideTemplateTagsData
* @dataProvider provideExtendsTagsData
* @dataProvider provideTypeAliasTagsData
* @dataProvider provideTypeAliasImportTagsData
* @dataProvider provideRealWorldExampleData
* @dataProvider provideDescriptionWithOrWithoutHtml
* @param string $label
@ -2904,7 +2906,7 @@ some text in the middle'
'@phpstan-type',
new InvalidTagValueNode(
'TypeAlias',
new ParserException(
new \PHPStan\PhpDocParser\Parser\ParserException(
'*/',
Lexer::TOKEN_CLOSE_PHPDOC,
28,
@ -2923,7 +2925,7 @@ some text in the middle'
'@phpstan-type',
new InvalidTagValueNode(
'',
new ParserException(
new \PHPStan\PhpDocParser\Parser\ParserException(
'*/',
Lexer::TOKEN_CLOSE_PHPDOC,
18,
@ -2935,6 +2937,96 @@ some text in the middle'
];
}
public function provideTypeAliasImportTagsData(): \Iterator
{
yield [
'OK',
'/** @phpstan-import-type TypeAlias from AnotherClass */',
new PhpDocNode([
new PhpDocTagNode(
'@phpstan-import-type',
new TypeAliasImportTagValueNode(
'TypeAlias',
new IdentifierTypeNode('AnotherClass'),
null
)
),
]),
];
yield [
'OK with alias',
'/** @phpstan-import-type TypeAlias from AnotherClass as DifferentAlias */',
new PhpDocNode([
new PhpDocTagNode(
'@phpstan-import-type',
new TypeAliasImportTagValueNode(
'TypeAlias',
new IdentifierTypeNode('AnotherClass'),
'DifferentAlias'
)
),
]),
];
yield [
'invalid missing from',
'/** @phpstan-import-type TypeAlias */',
new PhpDocNode([
new PhpDocTagNode(
'@phpstan-import-type',
new InvalidTagValueNode(
'TypeAlias',
new \PHPStan\PhpDocParser\Parser\ParserException(
'*/',
Lexer::TOKEN_CLOSE_PHPDOC,
35,
Lexer::TOKEN_IDENTIFIER
)
)
),
]),
];
yield [
'invalid missing from with alias',
'/** @phpstan-import-type TypeAlias as DifferentAlias */',
new PhpDocNode([
new PhpDocTagNode(
'@phpstan-import-type',
new InvalidTagValueNode(
'TypeAlias as DifferentAlias',
new \PHPStan\PhpDocParser\Parser\ParserException(
'as',
Lexer::TOKEN_IDENTIFIER,
35,
Lexer::TOKEN_IDENTIFIER
)
)
),
]),
];
yield [
'invalid empty',
'/** @phpstan-import-type */',
new PhpDocNode([
new PhpDocTagNode(
'@phpstan-import-type',
new InvalidTagValueNode(
'',
new \PHPStan\PhpDocParser\Parser\ParserException(
'*/',
Lexer::TOKEN_CLOSE_PHPDOC,
25,
Lexer::TOKEN_IDENTIFIER
)
)
),
]),
];
}
public function providerDebug(): \Iterator
{
$sample = '/**