From 22a7b24168733b5bdb7eb70427530df572f77ded Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 6 Oct 2022 09:23:59 +0200 Subject: [PATCH] Implement this-out/self-out syntax --- src/Ast/PhpDoc/PhpDocNode.php | 13 ++++ src/Ast/PhpDoc/SelfOutTagValueNode.php | 32 ++++++++++ src/Parser/PhpDocParser.php | 15 +++++ tests/PHPStan/Parser/PhpDocParserTest.php | 75 +++++++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 src/Ast/PhpDoc/SelfOutTagValueNode.php diff --git a/src/Ast/PhpDoc/PhpDocNode.php b/src/Ast/PhpDoc/PhpDocNode.php index ac06e19f..0c2f8f3b 100644 --- a/src/Ast/PhpDoc/PhpDocNode.php +++ b/src/Ast/PhpDoc/PhpDocNode.php @@ -328,6 +328,19 @@ static function (PhpDocTagValueNode $value): bool { } + /** + * @return SelfOutTagValueNode[] + */ + public function getSelfOutTypeTagValues(string $tagName = '@phpstan-this-out'): array + { + return array_filter( + array_column($this->getTagsByName($tagName), 'value'), + static function (PhpDocTagValueNode $value): bool { + return $value instanceof SelfOutTagValueNode; + } + ); + } + public function __toString(): string { $children = array_map( diff --git a/src/Ast/PhpDoc/SelfOutTagValueNode.php b/src/Ast/PhpDoc/SelfOutTagValueNode.php new file mode 100644 index 00000000..83169aff --- /dev/null +++ b/src/Ast/PhpDoc/SelfOutTagValueNode.php @@ -0,0 +1,32 @@ +type = $type; + $this->description = $description; + } + + + public function __toString(): string + { + return trim($this->type . ' ' . $this->description); + } + +} diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index 7c5284d1..8038778f 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -222,6 +222,13 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph $tagValue = $this->parseAssertTagValue($tokens); break; + case '@phpstan-this-out': + case '@phpstan-self-out': + case '@psalm-this-out': + case '@psalm-self-out': + $tagValue = $this->parseSelfOutTagValue($tokens); + break; + default: $tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens)); break; @@ -499,6 +506,14 @@ private function parseAssertParameter(TokenIterator $tokens): array return ['parameter' => $parameter]; } + private function parseSelfOutTagValue(TokenIterator $tokens): Ast\PhpDoc\SelfOutTagValueNode + { + $type = $this->typeParser->parse($tokens); + $description = $this->parseOptionalDescription($tokens); + + return new Ast\PhpDoc\SelfOutTagValueNode($type, $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 71bb423b..5010f0f5 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -25,6 +25,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\SelfOutTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode; @@ -90,6 +91,7 @@ protected function setUp(): void * @dataProvider provideRealWorldExampleData * @dataProvider provideDescriptionWithOrWithoutHtml * @dataProvider provideTagsWithBackslash + * @dataProvider provideSelfOutTagsData */ public function testParse( string $label, @@ -4485,6 +4487,79 @@ public function provideTagsWithBackslash(): Iterator ]; } + public function provideSelfOutTagsData(): Iterator + { + yield [ + 'OK phpstan-self-out', + '/** @phpstan-self-out self */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-self-out', + new SelfOutTagValueNode( + new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')]), + '' + ) + ), + ]), + ]; + + yield [ + 'OK phpstan-this-out', + '/** @phpstan-this-out self */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-this-out', + new SelfOutTagValueNode( + new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')]), + '' + ) + ), + ]), + ]; + + yield [ + 'OK psalm-self-out', + '/** @psalm-self-out self */', + new PhpDocNode([ + new PhpDocTagNode( + '@psalm-self-out', + new SelfOutTagValueNode( + new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')]), + '' + ) + ), + ]), + ]; + + yield [ + 'OK psalm-this-out', + '/** @psalm-this-out self */', + new PhpDocNode([ + new PhpDocTagNode( + '@psalm-this-out', + new SelfOutTagValueNode( + new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')]), + '' + ) + ), + ]), + ]; + + yield [ + 'OK with description', + '/** @phpstan-self-out self description */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-self-out', + new SelfOutTagValueNode( + new GenericTypeNode(new IdentifierTypeNode('self'), [new IdentifierTypeNode('T')]), + 'description' + ) + ), + ]), + ]; + } + /** * @dataProvider dataParseTagValue * @param PhpDocNode $expectedPhpDocNode