From 93e908833161b57fa3d652b659f8263d3406325f Mon Sep 17 00:00:00 2001 From: Tomohito YABU Date: Wed, 2 Nov 2022 00:46:13 +0900 Subject: [PATCH 1/7] Add generic support to `@method` definitions --- src/Ast/PhpDoc/MethodTagValueGenericNode.php | 33 ++++ src/Ast/PhpDoc/MethodTagValueNode.php | 9 +- src/Parser/PhpDocParser.php | 23 ++- tests/PHPStan/Parser/PhpDocParserTest.php | 181 +++++++++++++++++++ 4 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 src/Ast/PhpDoc/MethodTagValueGenericNode.php diff --git a/src/Ast/PhpDoc/MethodTagValueGenericNode.php b/src/Ast/PhpDoc/MethodTagValueGenericNode.php new file mode 100644 index 00000000..23e84019 --- /dev/null +++ b/src/Ast/PhpDoc/MethodTagValueGenericNode.php @@ -0,0 +1,33 @@ +name = $name; + $this->bound = $bound; + } + + + public function __toString(): string + { + $bound = $this->bound !== null ? " of {$this->bound}" : ''; + return trim("{$this->name}{$bound}"); + } + +} diff --git a/src/Ast/PhpDoc/MethodTagValueNode.php b/src/Ast/PhpDoc/MethodTagValueNode.php index 155897bb..b2b47bdc 100644 --- a/src/Ast/PhpDoc/MethodTagValueNode.php +++ b/src/Ast/PhpDoc/MethodTagValueNode.php @@ -20,17 +20,21 @@ class MethodTagValueNode implements PhpDocTagValueNode /** @var string */ public $methodName; + /** @var MethodTagValueGenericNode[] */ + public $generics; + /** @var MethodTagValueParameterNode[] */ public $parameters; /** @var string (may be empty) */ public $description; - public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description) + public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $generics, array $parameters, string $description) { $this->isStatic = $isStatic; $this->returnType = $returnType; $this->methodName = $methodName; + $this->generics = $generics; $this->parameters = $parameters; $this->description = $description; } @@ -40,9 +44,10 @@ public function __toString(): string { $static = $this->isStatic ? 'static ' : ''; $returnType = $this->returnType !== null ? "{$this->returnType} " : ''; + $generics = count($this->generics) > 0 ? '<' . implode(', ', $this->generics) . '>' : ''; $parameters = implode(', ', $this->parameters); $description = $this->description !== '' ? " {$this->description}" : ''; - return "{$static}{$returnType}{$this->methodName}({$parameters}){$description}"; + return "{$static}{$returnType}{$this->methodName}{$generics}({$parameters}){$description}"; } } diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index 9badbe61..cc718572 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -346,6 +346,14 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa exit; } + $generics = []; + if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { + do { + $generics[] = $this->parseMethodTagValueGeneric($tokens); + } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); + $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); + } + $parameters = []; $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { @@ -357,9 +365,22 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); $description = $this->parseOptionalDescription($tokens); - return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description); + return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $generics, $parameters, $description); } + private function parseMethodTagValueGeneric(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueGenericNode + { + $name = $tokens->currentTokenValue(); + $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); + + if ($tokens->tryConsumeTokenValue('of') || $tokens->tryConsumeTokenValue('as')) { + $bound = $this->typeParser->parse($tokens); + } else { + $bound = null; + } + + return new Ast\PhpDoc\MethodTagValueGenericNode($name, $bound); + } private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode { diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index c81a3a4c..c023761f 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -3,6 +3,7 @@ namespace PHPStan\PhpDocParser\Parser; use Iterator; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayItemNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; @@ -16,6 +17,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueGenericNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode; @@ -44,6 +46,7 @@ use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; use PHPStan\PhpDocParser\Lexer\Lexer; @@ -1834,6 +1837,7 @@ public function provideMethodTagsData(): Iterator null, 'foo', [], + [], '' ) ), @@ -1851,6 +1855,7 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('Foo'), 'foo', [], + [], '' ) ), @@ -1868,6 +1873,7 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('static'), 'foo', [], + [], '' ) ), @@ -1885,6 +1891,7 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('Foo'), 'foo', [], + [], '' ) ), @@ -1902,6 +1909,7 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('static'), 'foo', [], + [], '' ) ), @@ -1919,6 +1927,7 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('Foo'), 'foo', [], + [], 'optional description' ) ), @@ -1935,6 +1944,7 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', + [], [ new MethodTagValueParameterNode( null, @@ -1960,6 +1970,7 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -1985,6 +1996,7 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -2010,6 +2022,7 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -2035,6 +2048,7 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -2060,6 +2074,7 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', + [], [ new MethodTagValueParameterNode( null, @@ -2085,6 +2100,7 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -2110,6 +2126,7 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', + [], [ new MethodTagValueParameterNode( null, @@ -2195,6 +2212,93 @@ public function provideMethodTagsData(): Iterator ), ]), ]; + + yield [ + 'OK non-static, with return type and parameter with generic type', + '/** @method ?T randomElement(array $array = [\'a\', \'b\']) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new NullableTypeNode(new IdentifierTypeNode('T')), + 'randomElement', + [ + new MethodTagValueGenericNode('T', null), + ], + [ + new MethodTagValueParameterNode( + new GenericTypeNode( + new IdentifierTypeNode('array'), + [ + new IdentifierTypeNode('array-key'), + new IdentifierTypeNode('T'), + ] + ), + false, + false, + '$array', + new ConstExprArrayNode([ + new ConstExprArrayItemNode( + null, + new ConstExprStringNode('\'a\'') + ), + new ConstExprArrayItemNode( + null, + new ConstExprStringNode('\'b\'') + ), + ]), + ), + ], + '' + ) + ) + ]) + ]; + + yield [ + 'OK static, with return type and multiple parameters with generic type', + '/** @method static bool compare(T1 $t1, T2 $t2, T3 $t3) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + true, + new IdentifierTypeNode('bool'), + 'compare', + [ + new MethodTagValueGenericNode('T1', null), + new MethodTagValueGenericNode('T2', new IdentifierTypeNode('Bar')), + new MethodTagValueGenericNode('T3', new IdentifierTypeNode('Baz')), + ], + [ + new MethodTagValueParameterNode( + new IdentifierTypeNode('T1'), + false, + false, + '$t1', + null + ), + new MethodTagValueParameterNode( + new IdentifierTypeNode('T2'), + false, + false, + '$t2', + null + ), + new MethodTagValueParameterNode( + new IdentifierTypeNode('T3'), + false, + false, + '$t3', + null + ), + ], + '' + ) + ) + ]) + ]; } @@ -2533,6 +2637,7 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('int'), 'getInteger', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2558,6 +2663,7 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('void'), 'doSomething', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2587,6 +2693,7 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBar', [], + [], '' ) ), @@ -2597,6 +2704,7 @@ public function provideMultiLinePhpDocData(): array null, 'methodWithNoReturnType', [], + [], '' ) ), @@ -2606,6 +2714,7 @@ public function provideMultiLinePhpDocData(): array true, new IdentifierTypeNode('int'), 'getIntegerStatically', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2631,6 +2740,7 @@ public function provideMultiLinePhpDocData(): array true, new IdentifierTypeNode('void'), 'doSomethingStatically', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2660,6 +2770,7 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarStatically', [], + [], '' ) ), @@ -2670,6 +2781,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('static'), 'methodWithNoReturnTypeStatically', [], + [], '' ) ), @@ -2679,6 +2791,7 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('int'), 'getIntegerWithDescription', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2704,6 +2817,7 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('void'), 'doSomethingWithDescription', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2733,6 +2847,7 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarWithDescription', [], + [], 'Get a Foo or a Bar with a description.' ) ), @@ -2743,6 +2858,7 @@ public function provideMultiLinePhpDocData(): array null, 'methodWithNoReturnTypeWithDescription', [], + [], 'Do something with a description but what, who knows!' ) ), @@ -2752,6 +2868,7 @@ public function provideMultiLinePhpDocData(): array true, new IdentifierTypeNode('int'), 'getIntegerStaticallyWithDescription', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2777,6 +2894,7 @@ public function provideMultiLinePhpDocData(): array true, new IdentifierTypeNode('void'), 'doSomethingStaticallyWithDescription', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2806,6 +2924,7 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarStaticallyWithDescription', [], + [], 'Get a Foo or a Bar with a description statically.' ) ), @@ -2816,6 +2935,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('static'), 'methodWithNoReturnTypeStaticallyWithDescription', [], + [], 'Do something with a description statically, but what, who knows!' ) ), @@ -2826,6 +2946,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('bool'), 'aStaticMethodThatHasAUniqueReturnTypeInThisClass', [], + [], '' ) ), @@ -2836,6 +2957,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('string'), 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription', [], + [], 'A Description.' ) ), @@ -2846,6 +2968,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('int'), 'getIntegerNoParams', [], + [], '' ) ), @@ -2856,6 +2979,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('void'), 'doSomethingNoParams', [], + [], '' ) ), @@ -2869,6 +2993,7 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarNoParams', [], + [], '' ) ), @@ -2879,6 +3004,7 @@ public function provideMultiLinePhpDocData(): array null, 'methodWithNoReturnTypeNoParams', [], + [], '' ) ), @@ -2889,6 +3015,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('int'), 'getIntegerStaticallyNoParams', [], + [], '' ) ), @@ -2899,6 +3026,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('void'), 'doSomethingStaticallyNoParams', [], + [], '' ) ), @@ -2912,6 +3040,7 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarStaticallyNoParams', [], + [], '' ) ), @@ -2922,6 +3051,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('static'), 'methodWithNoReturnTypeStaticallyNoParams', [], + [], '' ) ), @@ -2932,6 +3062,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('int'), 'getIntegerWithDescriptionNoParams', [], + [], 'Get an integer with a description.' ) ), @@ -2942,6 +3073,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('void'), 'doSomethingWithDescriptionNoParams', [], + [], 'Do something with a description.' ) ), @@ -2955,6 +3087,7 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarWithDescriptionNoParams', [], + [], 'Get a Foo or a Bar with a description.' ) ), @@ -2965,6 +3098,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('int'), 'getIntegerStaticallyWithDescriptionNoParams', [], + [], 'Get an integer with a description statically.' ) ), @@ -2975,6 +3109,7 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('void'), 'doSomethingStaticallyWithDescriptionNoParams', [], + [], 'Do something with a description statically.' ) ), @@ -2988,6 +3123,7 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarStaticallyWithDescriptionNoParams', [], + [], 'Get a Foo or a Bar with a description statically.' ) ), @@ -3001,6 +3137,7 @@ public function provideMultiLinePhpDocData(): array ]), 'aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams', [], + [], '' ) ), @@ -3014,6 +3151,7 @@ public function provideMultiLinePhpDocData(): array ]), 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams', [], + [], 'A Description.' ) ), @@ -3023,6 +3161,7 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('\\Aws\\Result'), 'publish', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('array'), @@ -3041,6 +3180,7 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('Image'), 'rotate', + [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('float'), @@ -3067,11 +3207,52 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('Foo'), 'overridenMethod', [], + [], '' ) ), ]), ], + [ + 'OK with template method', + '/** + * @template TKey as array-key + * @template TValue + * @method TKey|null find(TValue $v) find index of $v + */', + new PhpDocNode([ + new PhpDocTagNode( + '@template', + new TemplateTagValueNode('TKey', new IdentifierTypeNode('array-key'), '') + ), + new PhpDocTagNode( + '@template', + new TemplateTagValueNode('TValue', null, '') + ), + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new UnionTypeNode([ + new IdentifierTypeNode('TKey'), + new IdentifierTypeNode('null'), + ]), + 'find', + [], + [ + new MethodTagValueParameterNode( + new IdentifierTypeNode('TValue'), + false, + false, + '$v', + null + ), + ], + 'find index of $v', + ) + ), + ]), + ], [ 'OK with multiline conditional return type', '/** From 6e9c7760d3518e489bc5f80211709fb27e6c6e37 Mon Sep 17 00:00:00 2001 From: Tomohito YABU Date: Wed, 2 Nov 2022 02:12:09 +0900 Subject: [PATCH 2/7] Avoid BC break and reuse TemplateTagValueNode --- src/Ast/PhpDoc/MethodTagValueGenericNode.php | 33 -------- src/Ast/PhpDoc/MethodTagValueNode.php | 12 +-- src/Parser/PhpDocParser.php | 16 ++-- tests/PHPStan/Parser/PhpDocParserTest.php | 80 ++++---------------- 4 files changed, 33 insertions(+), 108 deletions(-) delete mode 100644 src/Ast/PhpDoc/MethodTagValueGenericNode.php diff --git a/src/Ast/PhpDoc/MethodTagValueGenericNode.php b/src/Ast/PhpDoc/MethodTagValueGenericNode.php deleted file mode 100644 index 23e84019..00000000 --- a/src/Ast/PhpDoc/MethodTagValueGenericNode.php +++ /dev/null @@ -1,33 +0,0 @@ -name = $name; - $this->bound = $bound; - } - - - public function __toString(): string - { - $bound = $this->bound !== null ? " of {$this->bound}" : ''; - return trim("{$this->name}{$bound}"); - } - -} diff --git a/src/Ast/PhpDoc/MethodTagValueNode.php b/src/Ast/PhpDoc/MethodTagValueNode.php index b2b47bdc..14859cda 100644 --- a/src/Ast/PhpDoc/MethodTagValueNode.php +++ b/src/Ast/PhpDoc/MethodTagValueNode.php @@ -20,8 +20,8 @@ class MethodTagValueNode implements PhpDocTagValueNode /** @var string */ public $methodName; - /** @var MethodTagValueGenericNode[] */ - public $generics; + /** @var TemplateTagValueNode[] */ + public $templateTypes; /** @var MethodTagValueParameterNode[] */ public $parameters; @@ -29,14 +29,14 @@ class MethodTagValueNode implements PhpDocTagValueNode /** @var string (may be empty) */ public $description; - public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $generics, array $parameters, string $description) + public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description, array $templateTypes = []) { $this->isStatic = $isStatic; $this->returnType = $returnType; $this->methodName = $methodName; - $this->generics = $generics; $this->parameters = $parameters; $this->description = $description; + $this->templateTypes = $templateTypes; } @@ -44,10 +44,10 @@ public function __toString(): string { $static = $this->isStatic ? 'static ' : ''; $returnType = $this->returnType !== null ? "{$this->returnType} " : ''; - $generics = count($this->generics) > 0 ? '<' . implode(', ', $this->generics) . '>' : ''; $parameters = implode(', ', $this->parameters); $description = $this->description !== '' ? " {$this->description}" : ''; - return "{$static}{$returnType}{$this->methodName}{$generics}({$parameters}){$description}"; + $templateTypes = count($this->templateTypes) > 0 ? '<' . implode(', ', $this->templateTypes) . '>' : ''; + return "{$static}{$returnType}{$this->methodName}{$templateTypes}({$parameters}){$description}"; } } diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index cc718572..965d9509 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -346,10 +346,10 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa exit; } - $generics = []; + $templateTypes = []; if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { do { - $generics[] = $this->parseMethodTagValueGeneric($tokens); + $templateTypes[] = $this->parseMethodTagValueTemplateType($tokens); } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); } @@ -365,10 +365,10 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); $description = $this->parseOptionalDescription($tokens); - return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $generics, $parameters, $description); + return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes); } - private function parseMethodTagValueGeneric(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueGenericNode + private function parseMethodTagValueTemplateType(TokenIterator $tokens): Ast\PhpDoc\TemplateTagValueNode { $name = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); @@ -379,7 +379,13 @@ private function parseMethodTagValueGeneric(TokenIterator $tokens): Ast\PhpDoc\M $bound = null; } - return new Ast\PhpDoc\MethodTagValueGenericNode($name, $bound); + if ($tokens->tryConsumeTokenValue('=')) { + $default = $this->typeParser->parse($tokens); + } else { + $default = null; + } + + return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, '', $default); } private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index c023761f..5c9fa567 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -17,7 +17,6 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; -use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueGenericNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode; @@ -1837,7 +1836,6 @@ public function provideMethodTagsData(): Iterator null, 'foo', [], - [], '' ) ), @@ -1855,7 +1853,6 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('Foo'), 'foo', [], - [], '' ) ), @@ -1873,7 +1870,6 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('static'), 'foo', [], - [], '' ) ), @@ -1891,7 +1887,6 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('Foo'), 'foo', [], - [], '' ) ), @@ -1909,7 +1904,6 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('static'), 'foo', [], - [], '' ) ), @@ -1927,7 +1921,6 @@ public function provideMethodTagsData(): Iterator new IdentifierTypeNode('Foo'), 'foo', [], - [], 'optional description' ) ), @@ -1944,7 +1937,6 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', - [], [ new MethodTagValueParameterNode( null, @@ -1970,7 +1962,6 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -1996,7 +1987,6 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -2022,7 +2012,6 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -2048,7 +2037,6 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -2074,7 +2062,6 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', - [], [ new MethodTagValueParameterNode( null, @@ -2100,7 +2087,6 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('A'), @@ -2126,7 +2112,6 @@ public function provideMethodTagsData(): Iterator false, new IdentifierTypeNode('Foo'), 'foo', - [], [ new MethodTagValueParameterNode( null, @@ -2215,7 +2200,7 @@ public function provideMethodTagsData(): Iterator yield [ 'OK non-static, with return type and parameter with generic type', - '/** @method ?T randomElement(array $array = [\'a\', \'b\']) */', + '/** @method ?T randomElement(array $array = [\'a\', \'b\']) */', new PhpDocNode([ new PhpDocTagNode( '@method', @@ -2223,9 +2208,6 @@ public function provideMethodTagsData(): Iterator false, new NullableTypeNode(new IdentifierTypeNode('T')), 'randomElement', - [ - new MethodTagValueGenericNode('T', null), - ], [ new MethodTagValueParameterNode( new GenericTypeNode( @@ -2250,7 +2232,15 @@ public function provideMethodTagsData(): Iterator ]), ), ], - '' + '', + [ + new TemplateTagValueNode( + 'T', + null, + '', + new IdentifierTypeNode('string'), + ), + ], ) ) ]) @@ -2266,11 +2256,6 @@ public function provideMethodTagsData(): Iterator true, new IdentifierTypeNode('bool'), 'compare', - [ - new MethodTagValueGenericNode('T1', null), - new MethodTagValueGenericNode('T2', new IdentifierTypeNode('Bar')), - new MethodTagValueGenericNode('T3', new IdentifierTypeNode('Baz')), - ], [ new MethodTagValueParameterNode( new IdentifierTypeNode('T1'), @@ -2294,7 +2279,12 @@ public function provideMethodTagsData(): Iterator null ), ], - '' + '', + [ + new TemplateTagValueNode('T1', null, ''), + new TemplateTagValueNode('T2', new IdentifierTypeNode('Bar'), ''), + new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''), + ], ) ) ]) @@ -2637,7 +2627,6 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('int'), 'getInteger', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2663,7 +2652,6 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('void'), 'doSomething', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2693,7 +2681,6 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBar', [], - [], '' ) ), @@ -2704,7 +2691,6 @@ public function provideMultiLinePhpDocData(): array null, 'methodWithNoReturnType', [], - [], '' ) ), @@ -2714,7 +2700,6 @@ public function provideMultiLinePhpDocData(): array true, new IdentifierTypeNode('int'), 'getIntegerStatically', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2740,7 +2725,6 @@ public function provideMultiLinePhpDocData(): array true, new IdentifierTypeNode('void'), 'doSomethingStatically', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2770,7 +2754,6 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarStatically', [], - [], '' ) ), @@ -2781,7 +2764,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('static'), 'methodWithNoReturnTypeStatically', [], - [], '' ) ), @@ -2791,7 +2773,6 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('int'), 'getIntegerWithDescription', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2817,7 +2798,6 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('void'), 'doSomethingWithDescription', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2847,7 +2827,6 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarWithDescription', [], - [], 'Get a Foo or a Bar with a description.' ) ), @@ -2858,7 +2837,6 @@ public function provideMultiLinePhpDocData(): array null, 'methodWithNoReturnTypeWithDescription', [], - [], 'Do something with a description but what, who knows!' ) ), @@ -2868,7 +2846,6 @@ public function provideMultiLinePhpDocData(): array true, new IdentifierTypeNode('int'), 'getIntegerStaticallyWithDescription', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2894,7 +2871,6 @@ public function provideMultiLinePhpDocData(): array true, new IdentifierTypeNode('void'), 'doSomethingStaticallyWithDescription', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('int'), @@ -2924,7 +2900,6 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarStaticallyWithDescription', [], - [], 'Get a Foo or a Bar with a description statically.' ) ), @@ -2935,7 +2910,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('static'), 'methodWithNoReturnTypeStaticallyWithDescription', [], - [], 'Do something with a description statically, but what, who knows!' ) ), @@ -2946,7 +2920,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('bool'), 'aStaticMethodThatHasAUniqueReturnTypeInThisClass', [], - [], '' ) ), @@ -2957,7 +2930,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('string'), 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription', [], - [], 'A Description.' ) ), @@ -2968,7 +2940,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('int'), 'getIntegerNoParams', [], - [], '' ) ), @@ -2979,7 +2950,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('void'), 'doSomethingNoParams', [], - [], '' ) ), @@ -2993,7 +2963,6 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarNoParams', [], - [], '' ) ), @@ -3004,7 +2973,6 @@ public function provideMultiLinePhpDocData(): array null, 'methodWithNoReturnTypeNoParams', [], - [], '' ) ), @@ -3015,7 +2983,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('int'), 'getIntegerStaticallyNoParams', [], - [], '' ) ), @@ -3026,7 +2993,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('void'), 'doSomethingStaticallyNoParams', [], - [], '' ) ), @@ -3040,7 +3006,6 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarStaticallyNoParams', [], - [], '' ) ), @@ -3051,7 +3016,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('static'), 'methodWithNoReturnTypeStaticallyNoParams', [], - [], '' ) ), @@ -3062,7 +3026,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('int'), 'getIntegerWithDescriptionNoParams', [], - [], 'Get an integer with a description.' ) ), @@ -3073,7 +3036,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('void'), 'doSomethingWithDescriptionNoParams', [], - [], 'Do something with a description.' ) ), @@ -3087,7 +3049,6 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarWithDescriptionNoParams', [], - [], 'Get a Foo or a Bar with a description.' ) ), @@ -3098,7 +3059,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('int'), 'getIntegerStaticallyWithDescriptionNoParams', [], - [], 'Get an integer with a description statically.' ) ), @@ -3109,7 +3069,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('void'), 'doSomethingStaticallyWithDescriptionNoParams', [], - [], 'Do something with a description statically.' ) ), @@ -3123,7 +3082,6 @@ public function provideMultiLinePhpDocData(): array ]), 'getFooOrBarStaticallyWithDescriptionNoParams', [], - [], 'Get a Foo or a Bar with a description statically.' ) ), @@ -3137,7 +3095,6 @@ public function provideMultiLinePhpDocData(): array ]), 'aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams', [], - [], '' ) ), @@ -3151,7 +3108,6 @@ public function provideMultiLinePhpDocData(): array ]), 'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams', [], - [], 'A Description.' ) ), @@ -3161,7 +3117,6 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('\\Aws\\Result'), 'publish', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('array'), @@ -3180,7 +3135,6 @@ public function provideMultiLinePhpDocData(): array false, new IdentifierTypeNode('Image'), 'rotate', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('float'), @@ -3207,7 +3161,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('Foo'), 'overridenMethod', [], - [], '' ) ), @@ -3238,7 +3191,6 @@ public function provideMultiLinePhpDocData(): array new IdentifierTypeNode('null'), ]), 'find', - [], [ new MethodTagValueParameterNode( new IdentifierTypeNode('TValue'), From e661949ecda58fd0b57e5264c8c940bd5e272cf4 Mon Sep 17 00:00:00 2001 From: Tomohito YABU Date: Wed, 2 Nov 2022 02:22:09 +0900 Subject: [PATCH 3/7] Fix PHP 7.2 lint --- tests/PHPStan/Parser/PhpDocParserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 5c9fa567..061bf027 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -2230,7 +2230,7 @@ public function provideMethodTagsData(): Iterator new ConstExprStringNode('\'b\'') ), ]), - ), + ) ], '', [ From ba315abeed57c5b681967dc353288250b7beb6a3 Mon Sep 17 00:00:00 2001 From: Tomohito YABU Date: Wed, 2 Nov 2022 02:26:11 +0900 Subject: [PATCH 4/7] Fix PHP 7.2 lint again --- tests/PHPStan/Parser/PhpDocParserTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 061bf027..26b1d383 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -2229,8 +2229,8 @@ public function provideMethodTagsData(): Iterator null, new ConstExprStringNode('\'b\'') ), - ]), - ) + ]) + ), ], '', [ @@ -2238,7 +2238,7 @@ public function provideMethodTagsData(): Iterator 'T', null, '', - new IdentifierTypeNode('string'), + new IdentifierTypeNode('string') ), ], ) @@ -2284,7 +2284,7 @@ public function provideMethodTagsData(): Iterator new TemplateTagValueNode('T1', null, ''), new TemplateTagValueNode('T2', new IdentifierTypeNode('Bar'), ''), new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''), - ], + ] ) ) ]) From cbcd76b49bad0fc0e5a30eb2bec6a3333bfdcdd1 Mon Sep 17 00:00:00 2001 From: Tomohito YABU Date: Wed, 2 Nov 2022 02:31:19 +0900 Subject: [PATCH 5/7] Fix PHP 7.2 lint again 2 --- tests/PHPStan/Parser/PhpDocParserTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 26b1d383..7980c67f 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -2240,7 +2240,7 @@ public function provideMethodTagsData(): Iterator '', new IdentifierTypeNode('string') ), - ], + ] ) ) ]) @@ -3200,7 +3200,7 @@ public function provideMultiLinePhpDocData(): array null ), ], - 'find index of $v', + 'find index of $v' ) ), ]), From 6838eed6c9d2b8107c313163a27b871147cc5d0c Mon Sep 17 00:00:00 2001 From: Tomohito YABU Date: Wed, 2 Nov 2022 02:33:55 +0900 Subject: [PATCH 6/7] Fix coding standard errors --- src/Ast/PhpDoc/MethodTagValueNode.php | 1 + tests/PHPStan/Parser/PhpDocParserTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Ast/PhpDoc/MethodTagValueNode.php b/src/Ast/PhpDoc/MethodTagValueNode.php index 14859cda..075cec04 100644 --- a/src/Ast/PhpDoc/MethodTagValueNode.php +++ b/src/Ast/PhpDoc/MethodTagValueNode.php @@ -4,6 +4,7 @@ use PHPStan\PhpDocParser\Ast\NodeAttributes; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use function count; use function implode; class MethodTagValueNode implements PhpDocTagValueNode diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 7980c67f..6a2657b9 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -2242,8 +2242,8 @@ public function provideMethodTagsData(): Iterator ), ] ) - ) - ]) + ), + ]), ]; yield [ @@ -2286,8 +2286,8 @@ public function provideMethodTagsData(): Iterator new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''), ] ) - ) - ]) + ), + ]), ]; } From 96d3a2f7440df759bcdef851ccc51109eb1d040f Mon Sep 17 00:00:00 2001 From: Tomohito YABU Date: Wed, 2 Nov 2022 08:11:46 +0900 Subject: [PATCH 7/7] Reuse parseTemplateTagValue --- src/Parser/PhpDocParser.php | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index 965d9509..d9942b3d 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -182,7 +182,7 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph case '@template-contravariant': case '@phpstan-template-contravariant': case '@psalm-template-contravariant': - $tagValue = $this->parseTemplateTagValue($tokens); + $tagValue = $this->parseTemplateTagValue($tokens, true); break; case '@extends': @@ -349,7 +349,7 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa $templateTypes = []; if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { do { - $templateTypes[] = $this->parseMethodTagValueTemplateType($tokens); + $templateTypes[] = $this->parseTemplateTagValue($tokens, false); } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); } @@ -368,26 +368,6 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes); } - private function parseMethodTagValueTemplateType(TokenIterator $tokens): Ast\PhpDoc\TemplateTagValueNode - { - $name = $tokens->currentTokenValue(); - $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); - - if ($tokens->tryConsumeTokenValue('of') || $tokens->tryConsumeTokenValue('as')) { - $bound = $this->typeParser->parse($tokens); - } else { - $bound = null; - } - - if ($tokens->tryConsumeTokenValue('=')) { - $default = $this->typeParser->parse($tokens); - } else { - $default = null; - } - - return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, '', $default); - } - private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode { switch ($tokens->currentTokenType()) { @@ -417,7 +397,7 @@ private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc return new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue); } - private function parseTemplateTagValue(TokenIterator $tokens): Ast\PhpDoc\TemplateTagValueNode + private function parseTemplateTagValue(TokenIterator $tokens, bool $parseDescription): Ast\PhpDoc\TemplateTagValueNode { $name = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); @@ -435,7 +415,11 @@ private function parseTemplateTagValue(TokenIterator $tokens): Ast\PhpDoc\Templa $default = null; } - $description = $this->parseOptionalDescription($tokens); + if ($parseDescription) { + $description = $this->parseOptionalDescription($tokens); + } else { + $description = ''; + } return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, $description, $default); }