diff --git a/src/Ast/PhpDoc/ParamOutTagValueNode.php b/src/Ast/PhpDoc/ParamOutTagValueNode.php new file mode 100644 index 0000000..9f374bf --- /dev/null +++ b/src/Ast/PhpDoc/ParamOutTagValueNode.php @@ -0,0 +1,35 @@ +type = $type; + $this->parameterName = $parameterName; + $this->description = $description; + } + + public function __toString(): string + { + return trim("{$this->type} {$this->parameterName} {$this->description}"); + } + +} diff --git a/src/Ast/PhpDoc/PhpDocNode.php b/src/Ast/PhpDoc/PhpDocNode.php index 0c2f8f3..25f1939 100644 --- a/src/Ast/PhpDoc/PhpDocNode.php +++ b/src/Ast/PhpDoc/PhpDocNode.php @@ -341,6 +341,21 @@ class PhpDocNode implements Node ); } + + /** + * @return ParamOutTagValueNode[] + */ + public function getParamOutTypeTagValues(string $tagName = '@param-out'): array + { + return array_filter( + array_column($this->getTagsByName($tagName), 'value'), + static function (PhpDocTagValueNode $value): bool { + return $value instanceof ParamOutTagValueNode; + } + ); + } + + public function __toString(): string { $children = array_map( diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index 8038778..cca8e9b 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -229,6 +229,12 @@ class PhpDocParser $tagValue = $this->parseSelfOutTagValue($tokens); break; + case '@param-out': + case '@phpstan-param-out': + case '@psalm-param-out': + $tagValue = $this->parseParamOutTagValue($tokens); + break; + default: $tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens)); break; @@ -514,6 +520,15 @@ class PhpDocParser return new Ast\PhpDoc\SelfOutTagValueNode($type, $description); } + private function parseParamOutTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamOutTagValueNode + { + $type = $this->typeParser->parse($tokens); + $parameterName = $this->parseRequiredVariableName($tokens); + $description = $this->parseOptionalDescription($tokens); + + return new Ast\PhpDoc\ParamOutTagValueNode($type, $parameterName, $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 5010f0f..791e995 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -19,6 +19,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamOutTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; @@ -92,6 +93,7 @@ class PhpDocParserTest extends TestCase * @dataProvider provideDescriptionWithOrWithoutHtml * @dataProvider provideTagsWithBackslash * @dataProvider provideSelfOutTagsData + * @dataProvider provideParamOutTagsData */ public function testParse( string $label, @@ -4560,6 +4562,39 @@ Finder::findFiles('*.php') ]; } + public function provideParamOutTagsData(): Iterator + { + yield [ + 'OK param-out', + '/** @param-out string $s */', + new PhpDocNode([ + new PhpDocTagNode( + '@param-out', + new ParamOutTagValueNode( + new IdentifierTypeNode('string'), + '$s', + '' + ) + ), + ]), + ]; + + yield [ + 'OK param-out description', + '/** @param-out string $s description */', + new PhpDocNode([ + new PhpDocTagNode( + '@param-out', + new ParamOutTagValueNode( + new IdentifierTypeNode('string'), + '$s', + 'description' + ) + ), + ]), + ]; + } + /** * @dataProvider dataParseTagValue * @param PhpDocNode $expectedPhpDocNode