From 8f703ba7242306555b8624290b15be0a67e58565 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Fri, 29 Apr 2022 14:18:43 +0200 Subject: [PATCH] Add assert syntax --- src/Ast/PhpDoc/AssertTagValueNode.php | 36 ++++++++++ src/Parser/PhpDocParser.php | 13 ++++ tests/PHPStan/Parser/PhpDocParserTest.php | 87 +++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 src/Ast/PhpDoc/AssertTagValueNode.php diff --git a/src/Ast/PhpDoc/AssertTagValueNode.php b/src/Ast/PhpDoc/AssertTagValueNode.php new file mode 100644 index 00000000..1fec8619 --- /dev/null +++ b/src/Ast/PhpDoc/AssertTagValueNode.php @@ -0,0 +1,36 @@ +type = $type; + $this->parameter = $parameter; + $this->description = $description; + } + + + public function __toString(): string + { + return trim("{$this->type} {$this->parameter} {$this->description}"); + } + +} diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index 8d4573a2..dd9dde29 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -205,6 +205,11 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph $tagValue = $this->parseTypeAliasImportTagValue($tokens); break; + case '@phpstan-assert': + case '@psalm-assert': + $tagValue = $this->parseAssertTagValue($tokens); + break; + default: $tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens)); break; @@ -420,6 +425,14 @@ private function parseTypeAliasImportTagValue(TokenIterator $tokens): Ast\PhpDoc return new Ast\PhpDoc\TypeAliasImportTagValueNode($importedAlias, new IdentifierTypeNode($importedFrom), $importedAs); } + private function parseAssertTagValue(TokenIterator $tokens): Ast\PhpDoc\AssertTagValueNode + { + $type = $this->typeParser->parse($tokens); + $parameter = $this->parseRequiredVariableName($tokens); + $description = $this->parseOptionalDescription($tokens); + return new Ast\PhpDoc\AssertTagValueNode($type, $parameter, $description); + } + 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 b8a3fa42..60622698 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -8,6 +8,7 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\Node; +use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; @@ -75,6 +76,7 @@ protected function setUp(): void * @dataProvider provideExtendsTagsData * @dataProvider provideTypeAliasTagsData * @dataProvider provideTypeAliasImportTagsData + * @dataProvider provideAssertTagsData * @dataProvider provideRealWorldExampleData * @dataProvider provideDescriptionWithOrWithoutHtml */ @@ -3480,6 +3482,91 @@ public function provideTypeAliasImportTagsData(): Iterator ]; } + public function provideAssertTagsData(): Iterator + { + yield [ + 'OK', + '/** @phpstan-assert Type $var */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-assert', + new AssertTagValueNode( + new IdentifierTypeNode('Type'), + '$var', + '' + ) + ), + ]), + ]; + + yield [ + 'OK with psalm syntax', + '/** @psalm-assert Type $var */', + new PhpDocNode([ + new PhpDocTagNode( + '@psalm-assert', + new AssertTagValueNode( + new IdentifierTypeNode('Type'), + '$var', + '' + ) + ), + ]), + ]; + + yield [ + 'OK with description', + '/** @phpstan-assert Type $var assert Type to $var */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-assert', + new AssertTagValueNode( + new IdentifierTypeNode('Type'), + '$var', + 'assert Type to $var' + ) + ), + ]), + ]; + + yield [ + 'OK with union type', + '/** @phpstan-assert Type|Other $var */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-assert', + new AssertTagValueNode( + new UnionTypeNode([ + new IdentifierTypeNode('Type'), + new IdentifierTypeNode('Other'), + ]), + '$var', + '' + ) + ), + ]), + ]; + + yield [ + 'invalid $this->method()', + '/** @phpstan-assert Type $this->method() */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-assert', + new InvalidTagValueNode( + 'Type $this->method()', + new ParserException( + '$this', + Lexer::TOKEN_THIS_VARIABLE, + 25, + Lexer::TOKEN_VARIABLE + ) + ) + ), + ]), + ]; + } + public function providerDebug(): Iterator { $sample = '/**