Skip to content

Commit 3a918f8

Browse files
DaDeatherondrejmirtes
authored andcommitted
Allow multiple new lines in union and intersection definition
1 parent 19ecfcb commit 3a918f8

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

src/Parser/TypeParser.php

+16
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,14 @@ private function parseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast
271271

272272
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
273273
$types[] = $this->parseAtomic($tokens);
274+
$tokens->pushSavePoint();
275+
$tokens->skipNewLineTokens();
276+
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
277+
$tokens->rollback();
278+
break;
279+
}
280+
281+
$tokens->dropSavePoint();
274282
}
275283

276284
return new Ast\Type\UnionTypeNode($types);
@@ -299,6 +307,14 @@ private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $typ
299307

300308
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
301309
$types[] = $this->parseAtomic($tokens);
310+
$tokens->pushSavePoint();
311+
$tokens->skipNewLineTokens();
312+
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
313+
$tokens->rollback();
314+
break;
315+
}
316+
317+
$tokens->dropSavePoint();
302318
}
303319

304320
return new Ast\Type\IntersectionTypeNode($types);

tests/PHPStan/Parser/PhpDocParserTest.php

+172
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
6161
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
6262
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
63+
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
6364
use PHPStan\PhpDocParser\Ast\Type\InvalidTypeNode;
6465
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
6566
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode;
@@ -71,6 +72,7 @@
7172
use PHPUnit\Framework\TestCase;
7273
use function count;
7374
use function sprintf;
75+
use const DIRECTORY_SEPARATOR;
7476
use const PHP_EOL;
7577

7678
class PhpDocParserTest extends TestCase
@@ -4211,6 +4213,176 @@ public function provideMultiLinePhpDocData(): iterable
42114213
]),
42124214
];
42134215

4216+
yield [
4217+
'Multiline PHPDoc with multiple new line within union type declaration',
4218+
'/**' . PHP_EOL .
4219+
' * @param array<string, array{' . PHP_EOL .
4220+
' * foo: int,' . PHP_EOL .
4221+
' * bar?: array<string,' . PHP_EOL .
4222+
' * array{foo1: int, bar1?: true}' . PHP_EOL .
4223+
' * | array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
4224+
' * | array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
4225+
' * | array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
4226+
' * >,' . PHP_EOL .
4227+
' * }> $a' . PHP_EOL .
4228+
' */',
4229+
new PhpDocNode([
4230+
new PhpDocTagNode('@param', new ParamTagValueNode(
4231+
new GenericTypeNode(
4232+
new IdentifierTypeNode('array'),
4233+
[
4234+
new IdentifierTypeNode('string'),
4235+
ArrayShapeNode::createSealed([
4236+
new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
4237+
new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode(
4238+
new IdentifierTypeNode('array'),
4239+
[
4240+
new IdentifierTypeNode('string'),
4241+
new UnionTypeNode([
4242+
ArrayShapeNode::createSealed([
4243+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4244+
new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
4245+
]),
4246+
ArrayShapeNode::createSealed([
4247+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4248+
new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')),
4249+
new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
4250+
]),
4251+
ArrayShapeNode::createSealed([
4252+
new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('int')),
4253+
new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('bool')),
4254+
new ArrayShapeItemNode(new IdentifierTypeNode('bar2'), true, new IdentifierTypeNode('true')),
4255+
]),
4256+
ArrayShapeNode::createSealed([
4257+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4258+
new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('true')),
4259+
new ArrayShapeItemNode(new IdentifierTypeNode('bar3'), true, new IdentifierTypeNode('false')),
4260+
]),
4261+
]),
4262+
],
4263+
[
4264+
GenericTypeNode::VARIANCE_INVARIANT,
4265+
GenericTypeNode::VARIANCE_INVARIANT,
4266+
],
4267+
)),
4268+
]),
4269+
],
4270+
[
4271+
GenericTypeNode::VARIANCE_INVARIANT,
4272+
GenericTypeNode::VARIANCE_INVARIANT,
4273+
],
4274+
),
4275+
false,
4276+
'$a',
4277+
'',
4278+
false,
4279+
)),
4280+
]),
4281+
];
4282+
4283+
yield [
4284+
'Multiline PHPDoc with multiple new line within intersection type declaration',
4285+
'/**' . PHP_EOL .
4286+
' * @param array<string, array{' . PHP_EOL .
4287+
' * foo: int,' . PHP_EOL .
4288+
' * bar?: array<string,' . PHP_EOL .
4289+
' * array{foo1: int, bar1?: true}' . PHP_EOL .
4290+
' * & array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
4291+
' * & array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
4292+
' * & array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
4293+
' * >,' . PHP_EOL .
4294+
' * }> $a' . PHP_EOL .
4295+
' */',
4296+
new PhpDocNode([
4297+
new PhpDocTagNode('@param', new ParamTagValueNode(
4298+
new GenericTypeNode(
4299+
new IdentifierTypeNode('array'),
4300+
[
4301+
new IdentifierTypeNode('string'),
4302+
ArrayShapeNode::createSealed([
4303+
new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
4304+
new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode(
4305+
new IdentifierTypeNode('array'),
4306+
[
4307+
new IdentifierTypeNode('string'),
4308+
new IntersectionTypeNode([
4309+
ArrayShapeNode::createSealed([
4310+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4311+
new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
4312+
]),
4313+
ArrayShapeNode::createSealed([
4314+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4315+
new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')),
4316+
new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
4317+
]),
4318+
ArrayShapeNode::createSealed([
4319+
new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('int')),
4320+
new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('bool')),
4321+
new ArrayShapeItemNode(new IdentifierTypeNode('bar2'), true, new IdentifierTypeNode('true')),
4322+
]),
4323+
ArrayShapeNode::createSealed([
4324+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4325+
new ArrayShapeItemNode(new IdentifierTypeNode('foo3'), false, new IdentifierTypeNode('true')),
4326+
new ArrayShapeItemNode(new IdentifierTypeNode('bar3'), true, new IdentifierTypeNode('false')),
4327+
]),
4328+
]),
4329+
],
4330+
[
4331+
GenericTypeNode::VARIANCE_INVARIANT,
4332+
GenericTypeNode::VARIANCE_INVARIANT,
4333+
],
4334+
)),
4335+
]),
4336+
],
4337+
[
4338+
GenericTypeNode::VARIANCE_INVARIANT,
4339+
GenericTypeNode::VARIANCE_INVARIANT,
4340+
],
4341+
),
4342+
false,
4343+
'$a',
4344+
'',
4345+
false,
4346+
)),
4347+
]),
4348+
];
4349+
4350+
yield [
4351+
'Multiline PHPDoc with multiple new line being invalid due to union and intersection type declaration',
4352+
'/**' . PHP_EOL .
4353+
' * @param array<string, array{' . PHP_EOL .
4354+
' * foo: int,' . PHP_EOL .
4355+
' * bar?: array<string,' . PHP_EOL .
4356+
' * array{foo1: int, bar1?: true}' . PHP_EOL .
4357+
' * & array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
4358+
' * | array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
4359+
' * & array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
4360+
' * >,' . PHP_EOL .
4361+
' * }> $a' . PHP_EOL .
4362+
' */',
4363+
new PhpDocNode([
4364+
new PhpDocTagNode('@param', new InvalidTagValueNode(
4365+
'array<string, array{' . PHP_EOL .
4366+
' foo: int,' . PHP_EOL .
4367+
' bar?: array<string,' . PHP_EOL .
4368+
' array{foo1: int, bar1?: true}' . PHP_EOL .
4369+
' & array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
4370+
' | array{foo2: int, foo3: bool, bar2?: true}' . PHP_EOL .
4371+
' & array{foo1: int, foo3: true, bar3?: false}' . PHP_EOL .
4372+
' >,' . PHP_EOL .
4373+
'}> $a',
4374+
new ParserException(
4375+
'?',
4376+
Lexer::TOKEN_NULLABLE,
4377+
DIRECTORY_SEPARATOR === '\\' ? 65 : 62,
4378+
Lexer::TOKEN_CLOSE_CURLY_BRACKET,
4379+
null,
4380+
4,
4381+
),
4382+
)),
4383+
]),
4384+
];
4385+
42144386
/**
42154387
* @return object{
42164388
* a: int,

0 commit comments

Comments
 (0)