From 73d91aa44621fa3d68a6fc0a537bbdf5e600f8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Fr=C3=B6mer?= Date: Mon, 4 May 2020 15:13:26 +0200 Subject: [PATCH 1/5] Add multiline generic support This solves #42 --- src/Parser/TypeParser.php | 7 +++- tests/PHPStan/Parser/TypeParserTest.php | 56 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index 8a52a6f9..4ff29936 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -164,14 +164,19 @@ 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->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); + $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $genericTypes = [$this->parse($tokens)]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { + $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $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/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php index 4809d656..57b2e4de 100644 --- a/tests/PHPStan/Parser/TypeParserTest.php +++ b/tests/PHPStan/Parser/TypeParserTest.php @@ -899,6 +899,62 @@ 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') + ] + ) + ] + ) + ], ]; } From c17c3bc8729d68dbbdbcd8c9cfdba87b0e5d0de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Fr=C3=B6mer?= Date: Mon, 4 May 2020 15:15:33 +0200 Subject: [PATCH 2/5] Update Codestyle issues --- src/Parser/TypeParser.php | 8 +- tests/PHPStan/Parser/TypeParserTest.php | 110 ++++++++++++------------ 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index 4ff29936..410359dc 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -164,17 +164,17 @@ 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->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $genericTypes = [$this->parse($tokens)]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { - $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); + $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $genericTypes[] = $this->parse($tokens); - $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); + $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); } - $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/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php index 57b2e4de..b15988fb 100644 --- a/tests/PHPStan/Parser/TypeParserTest.php +++ b/tests/PHPStan/Parser/TypeParserTest.php @@ -900,61 +900,61 @@ public function provideParseData(): array ]), ], [ - '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 . + '>', + 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'), + ] + ), + ] + ), + ], ]; } From ed755ef28428c8f315dfaa2e63cf09135ddcfd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Fr=C3=B6mer?= Date: Mon, 4 May 2020 15:56:40 +0200 Subject: [PATCH 3/5] Add test for docblock parsing --- tests/PHPStan/Parser/PhpDocParserTest.php | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index e834ae82..11f22b0a 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -3054,6 +3054,30 @@ 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'), + ] + ), + '' + ) + ), + ]), + ]; } public function dataParseTagValue(): array From b2030f6b2a023db3ebb5daa927f0b6571a0eb116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Fr=C3=B6mer?= Date: Tue, 5 May 2020 09:56:14 +0200 Subject: [PATCH 4/5] Add possibility for leading comma --- src/Parser/TypeParser.php | 1 + tests/PHPStan/Parser/PhpDocParserTest.php | 48 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index 410359dc..1b80a977 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -167,6 +167,7 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $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); diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 11f22b0a..f5c22335 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -3078,6 +3078,54 @@ public function provideRealWorldExampleData(): \Iterator ), ]), ]; + +// yield [ +// 'multiline generic types - trailing comma', +// '/**' . PHP_EOL . +// ' * @implements Foo<' . PHP_EOL . +// ' * A,' . PHP_EOL . +// ' * >' . PHP_EOL . +// ' */', +// new PhpDocNode([ +// new PhpDocTagNode( +// '@implements', +// new ImplementsTagValueNode( +// new GenericTypeNode( +// new IdentifierTypeNode('Foo'), +// [ +// new IdentifierTypeNode('A'), +// ] +// ), +// '' +// ) +// ), +// ]), +// ]; + + 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'), + ] + ), + '' + ) + ), + ]), + ]; } public function dataParseTagValue(): array From 183925f27ff64275a389abe0778840881564ea5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Fr=C3=B6mer?= Date: Tue, 5 May 2020 10:25:00 +0200 Subject: [PATCH 5/5] Add support for trailing comma --- src/Parser/TypeParser.php | 7 +++- tests/PHPStan/Parser/PhpDocParserTest.php | 48 ++++++++++++----------- tests/PHPStan/Parser/TypeParserTest.php | 22 ++++++++++- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index 1b80a977..ec14b806 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -167,10 +167,15 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $genericTypes = [$this->parse($tokens)]; - $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); + + $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); } diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index f5c22335..6efd5da2 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -3079,29 +3079,6 @@ public function provideRealWorldExampleData(): \Iterator ]), ]; -// yield [ -// 'multiline generic types - trailing comma', -// '/**' . PHP_EOL . -// ' * @implements Foo<' . PHP_EOL . -// ' * A,' . PHP_EOL . -// ' * >' . PHP_EOL . -// ' */', -// new PhpDocNode([ -// new PhpDocTagNode( -// '@implements', -// new ImplementsTagValueNode( -// new GenericTypeNode( -// new IdentifierTypeNode('Foo'), -// [ -// new IdentifierTypeNode('A'), -// ] -// ), -// '' -// ) -// ), -// ]), -// ]; - yield [ 'multiline generic types - leading comma', '/**' . PHP_EOL . @@ -3126,6 +3103,31 @@ public function provideRealWorldExampleData(): \Iterator ), ]), ]; + + 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 b15988fb..d807b3af 100644 --- a/tests/PHPStan/Parser/TypeParserTest.php +++ b/tests/PHPStan/Parser/TypeParserTest.php @@ -899,7 +899,7 @@ public function provideParseData(): array new ConstTypeNode(new ConstFetchNode('QueueAttributeName', '*')), ]), ], - [ + [ 'array<' . PHP_EOL . ' Foo' . PHP_EOL . '>', @@ -955,6 +955,26 @@ public function provideParseData(): array ] ), ], + [ + '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'), + ] + ), + ] + ), + ], ]; }