Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/2.0.x' into 2.1.x
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 17, 2025
2 parents dc7c469 + 51087f8 commit 12c4e9f
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 0 deletions.
16 changes: 16 additions & 0 deletions src/Parser/TypeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
172 changes: 172 additions & 0 deletions tests/PHPStan/Parser/PhpDocParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -4211,6 +4213,176 @@ public function provideMultiLinePhpDocData(): iterable
]),
];

yield [
'Multiline PHPDoc with multiple new line within union type declaration',
'/**' . PHP_EOL .
' * @param array<string, array{' . PHP_EOL .
' * foo: int,' . PHP_EOL .
' * bar?: array<string,' . PHP_EOL .
' * array{foo1: int, bar1?: true}' . PHP_EOL .
' * | array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
' * | array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
' * | array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
' * >,' . 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<string, array{' . PHP_EOL .
' * foo: int,' . PHP_EOL .
' * bar?: array<string,' . PHP_EOL .
' * array{foo1: int, bar1?: true}' . PHP_EOL .
' * & array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
' * & array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
' * & array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
' * >,' . 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<string, array{' . PHP_EOL .
' * foo: int,' . PHP_EOL .
' * bar?: array<string,' . PHP_EOL .
' * array{foo1: int, bar1?: true}' . PHP_EOL .
' * & array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
' * | array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
' * & array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
' * >,' . PHP_EOL .
' * }> $a' . PHP_EOL .
' */',
new PhpDocNode([
new PhpDocTagNode('@param', new InvalidTagValueNode(
'array<string, array{' . PHP_EOL .
' foo: int,' . PHP_EOL .
' bar?: array<string,' . PHP_EOL .
' array{foo1: int, bar1?: true}' . PHP_EOL .
' & array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
' | array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
' & array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
' >,' . PHP_EOL .
'}> $a',
new ParserException(
'?',
Lexer::TOKEN_NULLABLE,
DIRECTORY_SEPARATOR === '\\' ? 65 : 62,
Lexer::TOKEN_CLOSE_CURLY_BRACKET,
null,
4,
),
)),
]),
];

/**
* @return object{
* a: int,
Expand Down

0 comments on commit 12c4e9f

Please sign in to comment.