Support @template tag

This commit is contained in:
Arnaud Le Blanc 2019-05-21 15:22:01 +02:00 committed by Ondřej Mirtes
parent 472d3161d2
commit 847540a54b
4 changed files with 171 additions and 0 deletions

View File

@ -70,6 +70,20 @@ class PhpDocNode implements Node
}
/**
* @return TemplateTagValueNode[]
*/
public function getTemplateTagValues(): array
{
return array_column(
array_filter($this->getTagsByName('@template'), static function (PhpDocTagNode $tag): bool {
return $tag->value instanceof TemplateTagValueNode;
}),
'value'
);
}
/**
* @return ReturnTagValueNode[]
*/

View File

@ -0,0 +1,32 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
class TemplateTagValueNode implements PhpDocTagValueNode
{
/** @var string */
public $name;
/** @var TypeNode */
public $bound;
/** @var string (may be empty) */
public $description;
public function __construct(string $name, TypeNode $bound, string $description)
{
$this->name = $name;
$this->bound = $bound;
$this->description = $description;
}
public function __toString(): string
{
return trim("{$this->name} of {$this->bound} {$this->description}");
}
}

View File

@ -3,6 +3,7 @@
namespace PHPStan\PhpDocParser\Parser;
use PHPStan\PhpDocParser\Ast;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
class PhpDocParser
@ -113,6 +114,10 @@ class PhpDocParser
$tagValue = $this->parseMethodTagValue($tokens);
break;
case '@template':
$tagValue = $this->parseTemplateTagValue($tokens);
break;
default:
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
break;
@ -243,6 +248,22 @@ class PhpDocParser
return new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue);
}
private function parseTemplateTagValue(TokenIterator $tokens): Ast\PhpDoc\TemplateTagValueNode
{
$name = $tokens->currentTokenValue();
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
if ($tokens->tryConsumeTokenValue('of')) {
$bound = $this->typeParser->parse($tokens);
} else {
$bound = new IdentifierTypeNode('mixed');
}
$description = $this->parseOptionalDescription($tokens);
return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, $description);
}
private function parseOptionalVariableName(TokenIterator $tokens): string
{

View File

@ -15,6 +15,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
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\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
@ -49,6 +50,7 @@ class PhpDocParserTest extends \PHPUnit\Framework\TestCase
* @dataProvider provideMethodTagsData
* @dataProvider provideSingleLinePhpDocData
* @dataProvider provideMultiLinePhpDocData
* @dataProvider provideTemplateTagsData
* @param string $label
* @param string $input
* @param PhpDocNode $expectedPhpDocNode
@ -2198,4 +2200,106 @@ class PhpDocParserTest extends \PHPUnit\Framework\TestCase
];
}
public function provideTemplateTagsData(): \Iterator
{
yield [
'OK without bound and description',
'/** @template T */',
new PhpDocNode([
new PhpDocTagNode(
'@template',
new TemplateTagValueNode(
'T',
new IdentifierTypeNode('mixed'),
''
)
),
]),
];
yield [
'OK without bound',
'/** @template T the value type*/',
new PhpDocNode([
new PhpDocTagNode(
'@template',
new TemplateTagValueNode(
'T',
new IdentifierTypeNode('mixed'),
'the value type'
)
),
]),
];
yield [
'OK without description',
'/** @template T of DateTime */',
new PhpDocNode([
new PhpDocTagNode(
'@template',
new TemplateTagValueNode(
'T',
new IdentifierTypeNode('DateTime'),
''
)
),
]),
];
yield [
'OK with bound and description',
'/** @template T of DateTime the value type */',
new PhpDocNode([
new PhpDocTagNode(
'@template',
new TemplateTagValueNode(
'T',
new IdentifierTypeNode('DateTime'),
'the value type'
)
),
]),
];
yield [
'invalid without bound and description',
'/** @template */',
new PhpDocNode([
new PhpDocTagNode(
'@template',
new InvalidTagValueNode(
'',
new \PHPStan\PhpDocParser\Parser\ParserException(
'*/',
Lexer::TOKEN_CLOSE_PHPDOC,
14,
Lexer::TOKEN_IDENTIFIER
)
)
),
]),
];
yield [
'invalid without bound and with description',
'/** @template #desc */',
new PhpDocNode([
new PhpDocTagNode(
'@template',
new InvalidTagValueNode(
'#desc',
new \PHPStan\PhpDocParser\Parser\ParserException(
'#desc',
Lexer::TOKEN_OTHER,
14,
Lexer::TOKEN_IDENTIFIER
)
)
),
]),
];
}
}