Skip to content

Commit 72e51f7

Browse files
authoredFeb 13, 2025··
TypeParser: Allow multiple newlines and also allow multiline union and intersection types for array shapes
·
2.2.02.0.1
1 parent 81de606 commit 72e51f7

File tree

3 files changed

+309
-42
lines changed

3 files changed

+309
-42
lines changed
 

‎src/Parser/TokenIterator.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,19 @@ public function tryConsumeTokenType(int $tokenType): bool
205205
}
206206

207207

208+
/** @phpstan-impure */
209+
public function skipNewLineTokens(): void
210+
{
211+
if (!$this->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
212+
return;
213+
}
214+
215+
do {
216+
$foundNewLine = $this->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
217+
} while ($foundNewLine === true);
218+
}
219+
220+
208221
private function detectNewline(): void
209222
{
210223
$value = $this->currentTokenValue();

‎src/Parser/TypeParser.php

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,44 @@ public function parse(TokenIterator $tokens): Ast\Type\TypeNode
4040
} else {
4141
$type = $this->parseAtomic($tokens);
4242

43-
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
44-
$type = $this->parseUnion($tokens, $type);
43+
$tokens->pushSavePoint();
44+
$tokens->skipNewLineTokens();
45+
46+
try {
47+
$enrichedType = $this->enrichTypeOnUnionOrIntersection($tokens, $type);
48+
49+
} catch (ParserException $parserException) {
50+
$enrichedType = null;
51+
}
4552

46-
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
47-
$type = $this->parseIntersection($tokens, $type);
53+
if ($enrichedType !== null) {
54+
$type = $enrichedType;
55+
$tokens->dropSavePoint();
56+
57+
} else {
58+
$tokens->rollback();
59+
$type = $this->enrichTypeOnUnionOrIntersection($tokens, $type) ?? $type;
4860
}
4961
}
5062

5163
return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
5264
}
5365

66+
/** @phpstan-impure */
67+
private function enrichTypeOnUnionOrIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): ?Ast\Type\TypeNode
68+
{
69+
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
70+
return $this->parseUnion($tokens, $type);
71+
72+
}
73+
74+
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
75+
return $this->parseIntersection($tokens, $type);
76+
}
77+
78+
return null;
79+
}
80+
5481
/**
5582
* @internal
5683
* @template T of Ast\Node
@@ -90,7 +117,7 @@ private function subParse(TokenIterator $tokens): Ast\Type\TypeNode
90117
if ($tokens->isCurrentTokenValue('is')) {
91118
$type = $this->parseConditional($tokens, $type);
92119
} else {
93-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
120+
$tokens->skipNewLineTokens();
94121

95122
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
96123
$type = $this->subParseUnion($tokens, $type);
@@ -112,9 +139,9 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
112139
$startIndex = $tokens->currentTokenIndex();
113140

114141
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
115-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
142+
$tokens->skipNewLineTokens();
116143
$type = $this->subParse($tokens);
117-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
144+
$tokens->skipNewLineTokens();
118145

119146
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
120147

@@ -256,9 +283,9 @@ private function subParseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type):
256283
$types = [$type];
257284

258285
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
259-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
286+
$tokens->skipNewLineTokens();
260287
$types[] = $this->parseAtomic($tokens);
261-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
288+
$tokens->skipNewLineTokens();
262289
}
263290

264291
return new Ast\Type\UnionTypeNode($types);
@@ -284,9 +311,9 @@ private function subParseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $
284311
$types = [$type];
285312

286313
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
287-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
314+
$tokens->skipNewLineTokens();
288315
$types[] = $this->parseAtomic($tokens);
289-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
316+
$tokens->skipNewLineTokens();
290317
}
291318

292319
return new Ast\Type\IntersectionTypeNode($types);
@@ -306,15 +333,15 @@ private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subj
306333

307334
$targetType = $this->parse($tokens);
308335

309-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
336+
$tokens->skipNewLineTokens();
310337
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
311-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
338+
$tokens->skipNewLineTokens();
312339

313340
$ifType = $this->parse($tokens);
314341

315-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
342+
$tokens->skipNewLineTokens();
316343
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
317-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
344+
$tokens->skipNewLineTokens();
318345

319346
$elseType = $this->subParse($tokens);
320347

@@ -335,15 +362,15 @@ private function parseConditionalForParameter(TokenIterator $tokens, string $par
335362

336363
$targetType = $this->parse($tokens);
337364

338-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
365+
$tokens->skipNewLineTokens();
339366
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
340-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
367+
$tokens->skipNewLineTokens();
341368

342369
$ifType = $this->parse($tokens);
343370

344-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
371+
$tokens->skipNewLineTokens();
345372
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
346-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
373+
$tokens->skipNewLineTokens();
347374

348375
$elseType = $this->subParse($tokens);
349376

@@ -409,8 +436,11 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
409436
$variances = [];
410437

411438
$isFirst = true;
412-
while ($isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
413-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
439+
while (
440+
$isFirst
441+
|| $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)
442+
) {
443+
$tokens->skipNewLineTokens();
414444

415445
// trailing comma case
416446
if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
@@ -419,7 +449,7 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
419449
$isFirst = false;
420450

421451
[$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens);
422-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
452+
$tokens->skipNewLineTokens();
423453
}
424454

425455
$type = new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances);
@@ -510,19 +540,19 @@ private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNod
510540
: [];
511541

512542
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
513-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
543+
$tokens->skipNewLineTokens();
514544

515545
$parameters = [];
516546
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
517547
$parameters[] = $this->parseCallableParameter($tokens);
518-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
548+
$tokens->skipNewLineTokens();
519549
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
520-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
550+
$tokens->skipNewLineTokens();
521551
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
522552
break;
523553
}
524554
$parameters[] = $this->parseCallableParameter($tokens);
525-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
555+
$tokens->skipNewLineTokens();
526556
}
527557
}
528558

@@ -550,7 +580,7 @@ private function parseCallableTemplates(TokenIterator $tokens): array
550580

551581
$isFirst = true;
552582
while ($isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
553-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
583+
$tokens->skipNewLineTokens();
554584

555585
// trailing comma case
556586
if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
@@ -559,7 +589,7 @@ private function parseCallableTemplates(TokenIterator $tokens): array
559589
$isFirst = false;
560590

561591
$templates[] = $this->parseCallableTemplateArgument($tokens);
562-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
592+
$tokens->skipNewLineTokens();
563593
}
564594

565595
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
@@ -830,7 +860,7 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
830860
$unsealedType = null;
831861

832862
do {
833-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
863+
$tokens->skipNewLineTokens();
834864

835865
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
836866
return Ast\Type\ArrayShapeNode::createSealed($items, $kind);
@@ -839,14 +869,14 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
839869
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) {
840870
$sealed = false;
841871

842-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
872+
$tokens->skipNewLineTokens();
843873
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
844874
if ($kind === Ast\Type\ArrayShapeNode::KIND_ARRAY) {
845875
$unsealedType = $this->parseArrayShapeUnsealedType($tokens);
846876
} else {
847877
$unsealedType = $this->parseListShapeUnsealedType($tokens);
848878
}
849-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
879+
$tokens->skipNewLineTokens();
850880
}
851881

852882
$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA);
@@ -855,10 +885,10 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
855885

856886
$items[] = $this->parseArrayShapeItem($tokens);
857887

858-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
888+
$tokens->skipNewLineTokens();
859889
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
860890

861-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
891+
$tokens->skipNewLineTokens();
862892
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
863893

864894
if ($sealed) {
@@ -945,18 +975,18 @@ private function parseArrayShapeUnsealedType(TokenIterator $tokens): Ast\Type\Ar
945975
$startIndex = $tokens->currentTokenIndex();
946976

947977
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
948-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
978+
$tokens->skipNewLineTokens();
949979

950980
$valueType = $this->parse($tokens);
951-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
981+
$tokens->skipNewLineTokens();
952982

953983
$keyType = null;
954984
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
955-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
985+
$tokens->skipNewLineTokens();
956986

957987
$keyType = $valueType;
958988
$valueType = $this->parse($tokens);
959-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
989+
$tokens->skipNewLineTokens();
960990
}
961991

962992
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
@@ -978,10 +1008,10 @@ private function parseListShapeUnsealedType(TokenIterator $tokens): Ast\Type\Arr
9781008
$startIndex = $tokens->currentTokenIndex();
9791009

9801010
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
981-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1011+
$tokens->skipNewLineTokens();
9821012

9831013
$valueType = $this->parse($tokens);
984-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1014+
$tokens->skipNewLineTokens();
9851015

9861016
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
9871017

@@ -1003,18 +1033,18 @@ private function parseObjectShape(TokenIterator $tokens): Ast\Type\ObjectShapeNo
10031033
$items = [];
10041034

10051035
do {
1006-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1036+
$tokens->skipNewLineTokens();
10071037

10081038
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
10091039
return new Ast\Type\ObjectShapeNode($items);
10101040
}
10111041

10121042
$items[] = $this->parseObjectShapeItem($tokens);
10131043

1014-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1044+
$tokens->skipNewLineTokens();
10151045
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
10161046

1017-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1047+
$tokens->skipNewLineTokens();
10181048
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
10191049

10201050
return new Ast\Type\ObjectShapeNode($items);

‎tests/PHPStan/Parser/PhpDocParserTest.php

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
6363
use PHPStan\PhpDocParser\Ast\Type\InvalidTypeNode;
6464
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
65+
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode;
66+
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode;
6567
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
6668
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
6769
use PHPStan\PhpDocParser\Lexer\Lexer;
@@ -4027,6 +4029,228 @@ public function provideMultiLinePhpDocData(): iterable
40274029
new PhpDocTextNode(''),
40284030
]),
40294031
];
4032+
4033+
yield [
4034+
'Multiline PHPDoc with new line across generic type declaration',
4035+
'/**' . PHP_EOL .
4036+
' * @param array<string, array{' . PHP_EOL .
4037+
' * foo: int,' . PHP_EOL .
4038+
' * bar?: array<string,' . PHP_EOL .
4039+
' * array{foo1: int, bar1?: true}' . PHP_EOL .
4040+
' * | array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
4041+
' * >,' . PHP_EOL .
4042+
' * }> $a' . PHP_EOL .
4043+
' */',
4044+
new PhpDocNode([
4045+
new PhpDocTagNode('@param', new ParamTagValueNode(
4046+
new GenericTypeNode(
4047+
new IdentifierTypeNode('array'),
4048+
[
4049+
new IdentifierTypeNode('string'),
4050+
ArrayShapeNode::createSealed([
4051+
new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
4052+
new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode(
4053+
new IdentifierTypeNode('array'),
4054+
[
4055+
new IdentifierTypeNode('string'),
4056+
new UnionTypeNode([
4057+
ArrayShapeNode::createSealed([
4058+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4059+
new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
4060+
]),
4061+
ArrayShapeNode::createSealed([
4062+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4063+
new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')),
4064+
new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
4065+
]),
4066+
]),
4067+
],
4068+
[
4069+
GenericTypeNode::VARIANCE_INVARIANT,
4070+
GenericTypeNode::VARIANCE_INVARIANT,
4071+
],
4072+
)),
4073+
]),
4074+
],
4075+
[
4076+
GenericTypeNode::VARIANCE_INVARIANT,
4077+
GenericTypeNode::VARIANCE_INVARIANT,
4078+
],
4079+
),
4080+
false,
4081+
'$a',
4082+
'',
4083+
false,
4084+
)),
4085+
]),
4086+
];
4087+
4088+
yield [
4089+
'Multiline PHPDoc with new line within type declaration',
4090+
'/**' . PHP_EOL .
4091+
' * @param array<string, array{' . PHP_EOL .
4092+
' * foo: int,' . PHP_EOL .
4093+
' * ' . PHP_EOL .
4094+
' * ' . PHP_EOL .
4095+
' * bar?: array<string,' . PHP_EOL .
4096+
' * ' . PHP_EOL .
4097+
' * array{foo1: int, bar1?: true}' . PHP_EOL .
4098+
' * ' . PHP_EOL .
4099+
' * | array{foo1: int, foo2: true, bar1?: true}' . PHP_EOL .
4100+
' * >,' . PHP_EOL .
4101+
' * }> $a' . PHP_EOL .
4102+
' */',
4103+
new PhpDocNode([
4104+
new PhpDocTagNode('@param', new ParamTagValueNode(
4105+
new GenericTypeNode(
4106+
new IdentifierTypeNode('array'),
4107+
[
4108+
new IdentifierTypeNode('string'),
4109+
ArrayShapeNode::createSealed([
4110+
new ArrayShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
4111+
new ArrayShapeItemNode(new IdentifierTypeNode('bar'), true, new GenericTypeNode(
4112+
new IdentifierTypeNode('array'),
4113+
[
4114+
new IdentifierTypeNode('string'),
4115+
new UnionTypeNode([
4116+
ArrayShapeNode::createSealed([
4117+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4118+
new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
4119+
]),
4120+
ArrayShapeNode::createSealed([
4121+
new ArrayShapeItemNode(new IdentifierTypeNode('foo1'), false, new IdentifierTypeNode('int')),
4122+
new ArrayShapeItemNode(new IdentifierTypeNode('foo2'), false, new IdentifierTypeNode('true')),
4123+
new ArrayShapeItemNode(new IdentifierTypeNode('bar1'), true, new IdentifierTypeNode('true')),
4124+
]),
4125+
]),
4126+
],
4127+
[
4128+
GenericTypeNode::VARIANCE_INVARIANT,
4129+
GenericTypeNode::VARIANCE_INVARIANT,
4130+
],
4131+
)),
4132+
]),
4133+
],
4134+
[
4135+
GenericTypeNode::VARIANCE_INVARIANT,
4136+
GenericTypeNode::VARIANCE_INVARIANT,
4137+
],
4138+
),
4139+
false,
4140+
'$a',
4141+
'',
4142+
false,
4143+
)),
4144+
]),
4145+
];
4146+
4147+
yield [
4148+
'Multiline PHPDoc with new line within type declaration including usage of braces',
4149+
'/**' . PHP_EOL .
4150+
' * @phpstan-type FactoriesConfigurationType = array<' . PHP_EOL .
4151+
' * string,' . PHP_EOL .
4152+
' * (class-string<Factory\FactoryInterface>|Factory\FactoryInterface)' . PHP_EOL .
4153+
' * |callable(ContainerInterface,?string,array<mixed>|null):object' . PHP_EOL .
4154+
' * >' . PHP_EOL .
4155+
' */',
4156+
new PhpDocNode([
4157+
new PhpDocTagNode('@phpstan-type', new TypeAliasTagValueNode(
4158+
'FactoriesConfigurationType',
4159+
new GenericTypeNode(
4160+
new IdentifierTypeNode('array'),
4161+
[
4162+
new IdentifierTypeNode('string'),
4163+
new UnionTypeNode([
4164+
new UnionTypeNode([
4165+
new GenericTypeNode(
4166+
new IdentifierTypeNode('class-string'),
4167+
[new IdentifierTypeNode('Factory\\FactoryInterface')],
4168+
[GenericTypeNode::VARIANCE_INVARIANT],
4169+
),
4170+
new IdentifierTypeNode('Factory\\FactoryInterface'),
4171+
]),
4172+
new CallableTypeNode(
4173+
new IdentifierTypeNode('callable'),
4174+
[
4175+
new CallableTypeParameterNode(new IdentifierTypeNode('ContainerInterface'), false, false, '', false),
4176+
new CallableTypeParameterNode(
4177+
new NullableTypeNode(
4178+
new IdentifierTypeNode('string'),
4179+
),
4180+
false,
4181+
false,
4182+
'',
4183+
false,
4184+
),
4185+
new CallableTypeParameterNode(
4186+
new UnionTypeNode([
4187+
new GenericTypeNode(
4188+
new IdentifierTypeNode('array'),
4189+
[new IdentifierTypeNode('mixed')],
4190+
[GenericTypeNode::VARIANCE_INVARIANT],
4191+
),
4192+
new IdentifierTypeNode('null'),
4193+
]),
4194+
false,
4195+
false,
4196+
'',
4197+
false,
4198+
),
4199+
],
4200+
new IdentifierTypeNode('object'),
4201+
[],
4202+
),
4203+
]),
4204+
],
4205+
[
4206+
GenericTypeNode::VARIANCE_INVARIANT,
4207+
GenericTypeNode::VARIANCE_INVARIANT,
4208+
],
4209+
),
4210+
)),
4211+
]),
4212+
];
4213+
4214+
/**
4215+
* @return object{
4216+
* a: int,
4217+
*
4218+
* b: int,
4219+
* }
4220+
*/
4221+
4222+
yield [
4223+
'Multiline PHPDoc with new line within object type declaration',
4224+
'/**' . PHP_EOL .
4225+
' * @return object{' . PHP_EOL .
4226+
' * a: int,' . PHP_EOL .
4227+
' *' . PHP_EOL .
4228+
' * b: int,' . PHP_EOL .
4229+
' * }' . PHP_EOL .
4230+
' */',
4231+
new PhpDocNode([
4232+
new PhpDocTagNode(
4233+
'@return',
4234+
new ReturnTagValueNode(
4235+
new ObjectShapeNode(
4236+
[
4237+
new ObjectShapeItemNode(
4238+
new IdentifierTypeNode('a'),
4239+
false,
4240+
new IdentifierTypeNode('int'),
4241+
),
4242+
new ObjectShapeItemNode(
4243+
new IdentifierTypeNode('b'),
4244+
false,
4245+
new IdentifierTypeNode('int'),
4246+
),
4247+
],
4248+
),
4249+
'',
4250+
),
4251+
),
4252+
]),
4253+
];
40304254
}
40314255

40324256
public function provideTemplateTagsData(): Iterator

0 commit comments

Comments
 (0)
Please sign in to comment.