diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index 8a52a6f9..ec14b806 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -165,13 +165,24 @@ private function parseNullable(TokenIterator $tokens): Ast\Type\TypeNode public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\GenericTypeNode { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); + $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $genericTypes = [$this->parse($tokens)]; + $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); + while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { + $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); + if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) { + // trailing comma case + return new Ast\Type\GenericTypeNode($baseType, $genericTypes); + } $genericTypes[] = $this->parse($tokens); + $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); } + $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); + return new Ast\Type\GenericTypeNode($baseType, $genericTypes); } diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index e834ae82..6efd5da2 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -3054,6 +3054,80 @@ public function provideRealWorldExampleData(): \Iterator new PhpDocTagNode('@param', new InvalidTagValueNode('Foo::** $a', new \PHPStan\PhpDocParser\Parser\ParserException('*', Lexer::TOKEN_WILDCARD, 17, Lexer::TOKEN_VARIABLE))), ]), ]; + + yield [ + 'multiline generic types', + '/**' . PHP_EOL . + ' * @implements Foo<' . PHP_EOL . + ' * A, B' . PHP_EOL . + ' * >' . PHP_EOL . + ' */', + new PhpDocNode([ + new PhpDocTagNode( + '@implements', + new ImplementsTagValueNode( + new GenericTypeNode( + new IdentifierTypeNode('Foo'), + [ + new IdentifierTypeNode('A'), + new IdentifierTypeNode('B'), + ] + ), + '' + ) + ), + ]), + ]; + + yield [ + 'multiline generic types - leading comma', + '/**' . PHP_EOL . + ' * @implements Foo<' . PHP_EOL . + ' * A' . PHP_EOL . + ' * , B' . PHP_EOL . + ' * >' . PHP_EOL . + ' */', + new PhpDocNode([ + new PhpDocTagNode( + '@implements', + new ImplementsTagValueNode( + new GenericTypeNode( + new IdentifierTypeNode('Foo'), + [ + new IdentifierTypeNode('A'), + new IdentifierTypeNode('B'), + ] + ), + '' + ) + ), + ]), + ]; + + yield [ + 'multiline generic types - traling comma', + '/**' . PHP_EOL . + ' * @implements Foo<' . PHP_EOL . + ' * A,' . PHP_EOL . + ' * B,' . PHP_EOL . + ' * >' . PHP_EOL . + ' */', + new PhpDocNode([ + new PhpDocTagNode( + '@implements', + new ImplementsTagValueNode( + new GenericTypeNode( + new IdentifierTypeNode('Foo'), + [ + new IdentifierTypeNode('A'), + new IdentifierTypeNode('B'), + ] + ), + '' + ) + ), + ]), + ]; } public function dataParseTagValue(): array diff --git a/tests/PHPStan/Parser/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php index 4809d656..d807b3af 100644 --- a/tests/PHPStan/Parser/TypeParserTest.php +++ b/tests/PHPStan/Parser/TypeParserTest.php @@ -899,6 +899,82 @@ public function provideParseData(): array new ConstTypeNode(new ConstFetchNode('QueueAttributeName', '*')), ]), ], + [ + 'array<' . PHP_EOL . + ' Foo' . PHP_EOL . + '>', + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('Foo'), + ] + ), + ], + [ + 'array<' . PHP_EOL . + ' Foo,' . PHP_EOL . + ' Bar' . PHP_EOL . + '>', + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('Foo'), + new IdentifierTypeNode('Bar'), + ] + ), + ], + [ + 'array<' . PHP_EOL . + ' Foo, Bar' . PHP_EOL . + '>', + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('Foo'), + new IdentifierTypeNode('Bar'), + ] + ), + ], + [ + 'array<' . PHP_EOL . + ' Foo,' . PHP_EOL . + ' array<' . PHP_EOL . + ' Bar' . PHP_EOL . + ' >' . PHP_EOL . + '>', + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('Foo'), + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('Bar'), + ] + ), + ] + ), + ], + [ + 'array<' . PHP_EOL . + ' Foo,' . PHP_EOL . + ' array<' . PHP_EOL . + ' Bar,' . PHP_EOL . + ' >' . PHP_EOL . + '>', + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('Foo'), + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('Bar'), + ] + ), + ] + ), + ], ]; }