diff --git a/doc/grammars/phpdoc-param.peg b/doc/grammars/phpdoc-param.peg index 436c20fc..f16cb948 100644 --- a/doc/grammars/phpdoc-param.peg +++ b/doc/grammars/phpdoc-param.peg @@ -1,5 +1,5 @@ PhpDocParam - = AnnotationName Type IsReference? IsVariadic? ParameterName Description? + = AnnotationName Type? IsReference? IsVariadic? ParameterName Description? AnnotationName = '@param' diff --git a/src/Ast/PhpDoc/PhpDocNode.php b/src/Ast/PhpDoc/PhpDocNode.php index 7390b0c3..a0caef68 100644 --- a/src/Ast/PhpDoc/PhpDocNode.php +++ b/src/Ast/PhpDoc/PhpDocNode.php @@ -76,6 +76,20 @@ static function (PhpDocTagValueNode $value): bool { } + /** + * @return TypelessParamTagValueNode[] + */ + public function getTypelessParamTagValues(string $tagName = '@param'): array + { + return array_filter( + array_column($this->getTagsByName($tagName), 'value'), + static function (PhpDocTagValueNode $value): bool { + return $value instanceof TypelessParamTagValueNode; + } + ); + } + + /** * @return TemplateTagValueNode[] */ diff --git a/src/Ast/PhpDoc/TypelessParamTagValueNode.php b/src/Ast/PhpDoc/TypelessParamTagValueNode.php new file mode 100644 index 00000000..8b982954 --- /dev/null +++ b/src/Ast/PhpDoc/TypelessParamTagValueNode.php @@ -0,0 +1,41 @@ +isReference = $isReference; + $this->isVariadic = $isVariadic; + $this->parameterName = $parameterName; + $this->description = $description; + } + + + public function __toString(): string + { + $reference = $this->isReference ? '&' : ''; + $variadic = $this->isVariadic ? '...' : ''; + return trim("{$reference}{$variadic}{$this->parameterName} {$this->description}"); + } + +} diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index d0286d55..1e9abc05 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -230,14 +230,31 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph } - private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamTagValueNode + /** + * @return Ast\PhpDoc\ParamTagValueNode|Ast\PhpDoc\TypelessParamTagValueNode + */ + private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode { - $type = $this->typeParser->parse($tokens); + if ( + $tokens->isCurrentTokenType(Lexer::TOKEN_REFERENCE) + || $tokens->isCurrentTokenType(Lexer::TOKEN_VARIADIC) + || $tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE) + ) { + $type = null; + } else { + $type = $this->typeParser->parse($tokens); + } + $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); $parameterName = $this->parseRequiredVariableName($tokens); $description = $this->parseOptionalDescription($tokens); - return new Ast\PhpDoc\ParamTagValueNode($type, $isVariadic, $parameterName, $description, $isReference); + + if ($type !== null) { + return new Ast\PhpDoc\ParamTagValueNode($type, $isVariadic, $parameterName, $description, $isReference); + } + + return new Ast\PhpDoc\TypelessParamTagValueNode($isVariadic, $parameterName, $description, $isReference); } @@ -463,7 +480,6 @@ private function parseRequiredVariableName(TokenIterator $tokens): string return $parameterName; } - private function parseOptionalDescription(TokenIterator $tokens, bool $limitStartToken = false): string { if ($limitStartToken) { diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 31bb0247..e91a4042 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -27,6 +27,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; @@ -64,6 +65,7 @@ protected function setUp(): void /** * @dataProvider provideTagsWithNumbers * @dataProvider provideParamTagsData + * @dataProvider provideTypelessParamTagsData * @dataProvider provideVarTagsData * @dataProvider provideReturnTagsData * @dataProvider provideThrowsTagsData @@ -395,6 +397,86 @@ public function provideParamTagsData(): Iterator ]; } + public function provideTypelessParamTagsData(): Iterator + { + yield [ + 'OK', + '/** @param $foo description */', + new PhpDocNode([ + new PhpDocTagNode( + '@param', + new TypelessParamTagValueNode( + false, + '$foo', + 'description' + ) + ), + ]), + ]; + + yield [ + 'OK reference', + '/** @param &$foo description */', + new PhpDocNode([ + new PhpDocTagNode( + '@param', + new TypelessParamTagValueNode( + false, + '$foo', + 'description', + true + ) + ), + ]), + ]; + + yield [ + 'OK variadic', + '/** @param ...$foo description */', + new PhpDocNode([ + new PhpDocTagNode( + '@param', + new TypelessParamTagValueNode( + true, + '$foo', + 'description' + ) + ), + ]), + ]; + + yield [ + 'OK reference variadic', + '/** @param &...$foo description */', + new PhpDocNode([ + new PhpDocTagNode( + '@param', + new TypelessParamTagValueNode( + true, + '$foo', + 'description', + true + ) + ), + ]), + ]; + + yield [ + 'OK without type and description', + '/** @param $foo */', + new PhpDocNode([ + new PhpDocTagNode( + '@param', + new TypelessParamTagValueNode( + false, + '$foo', + '', + false + ) + ), + ]), + ]; + } public function provideVarTagsData(): Iterator {