From 51087f87dcce2663e1fed4dfd4e56eccd580297e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ismail=20=C3=96zg=C3=BCn=20Turan?= Date: Sun, 16 Feb 2025 14:48:34 +0100 Subject: [PATCH] Allow multiple new lines in union and intersection definition --- src/Parser/TypeParser.php | 16 ++ tests/PHPStan/Parser/PhpDocParserTest.php | 172 ++++++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index d5f9217c..b24561b6 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -271,6 +271,14 @@ private function parseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) { $types[] = $this->parseAtomic($tokens); + $tokens->pushSavePoint(); + $tokens->skipNewLineTokens(); + if (!$tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) { + $tokens->rollback(); + break; + } + + $tokens->dropSavePoint(); } return new Ast\Type\UnionTypeNode($types); @@ -299,6 +307,14 @@ private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $typ while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) { $types[] = $this->parseAtomic($tokens); + $tokens->pushSavePoint(); + $tokens->skipNewLineTokens(); + if (!$tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) { + $tokens->rollback(); + break; + } + + $tokens->dropSavePoint(); } return new Ast\Type\IntersectionTypeNode($types); diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 2cbde3c6..49ae1873 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -60,6 +60,7 @@ use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; use PHPStan\PhpDocParser\Ast\Type\InvalidTypeNode; use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode; @@ -71,6 +72,7 @@ use PHPUnit\Framework\TestCase; use function count; use function sprintf; +use const DIRECTORY_SEPARATOR; use const PHP_EOL; class PhpDocParserTest extends TestCase @@ -4211,6 +4213,176 @@ public function provideMultiLinePhpDocData(): iterable ]), ]; + yield [ + 'Multiline PHPDoc with multiple new line within union type declaration', + '/**' . PHP_EOL . + ' * @param array,' . PHP_EOL . + ' * }> $a' . PHP_EOL . + ' */', + new PhpDocNode([ + new PhpDocTagNode('@param', new ParamTagValueNode( + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('string'), + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('string'), + new UnionTypeNode([ + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')), + ]), + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')), + ]), + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('bool')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar2'), true, new IdentifierTypeNode('true')), + ]), + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('true')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar3'), true, new IdentifierTypeNode('false')), + ]), + ]), + ], + [ + GenericTypeNode::VARIANCE_INVARIANT, + GenericTypeNode::VARIANCE_INVARIANT, + ], + )), + ]), + ], + [ + GenericTypeNode::VARIANCE_INVARIANT, + GenericTypeNode::VARIANCE_INVARIANT, + ], + ), + false, + '$a', + '', + false, + )), + ]), + ]; + + yield [ + 'Multiline PHPDoc with multiple new line within intersection type declaration', + '/**' . PHP_EOL . + ' * @param array,' . PHP_EOL . + ' * }> $a' . PHP_EOL . + ' */', + new PhpDocNode([ + new PhpDocTagNode('@param', new ParamTagValueNode( + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('string'), + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('string'), + new IntersectionTypeNode([ + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')), + ]), + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')), + ]), + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('bool')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar2'), true, new IdentifierTypeNode('true')), + ]), + ArrayShapeNode::createSealed([ + new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')), + new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('true')), + new ArrayShapeItemNode(new IdentifierTypeNode('bar3'), true, new IdentifierTypeNode('false')), + ]), + ]), + ], + [ + GenericTypeNode::VARIANCE_INVARIANT, + GenericTypeNode::VARIANCE_INVARIANT, + ], + )), + ]), + ], + [ + GenericTypeNode::VARIANCE_INVARIANT, + GenericTypeNode::VARIANCE_INVARIANT, + ], + ), + false, + '$a', + '', + false, + )), + ]), + ]; + + yield [ + 'Multiline PHPDoc with multiple new line being invalid due to union and intersection type declaration', + '/**' . PHP_EOL . + ' * @param array,' . PHP_EOL . + ' * }> $a' . PHP_EOL . + ' */', + new PhpDocNode([ + new PhpDocTagNode('@param', new InvalidTagValueNode( + 'array,' . PHP_EOL . + '}> $a', + new ParserException( + '?', + Lexer::TOKEN_NULLABLE, + DIRECTORY_SEPARATOR === '\\' ? 65 : 62, + Lexer::TOKEN_CLOSE_CURLY_BRACKET, + null, + 4, + ), + )), + ]), + ]; + /** * @return object{ * a: int,