diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index 185069b..610d590 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -190,15 +190,14 @@ class PhpDocParser } $parameters = []; - if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { - if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { + $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); + if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { + $parameters[] = $this->parseMethodTagValueParameter($tokens); + while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { $parameters[] = $this->parseMethodTagValueParameter($tokens); - while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { - $parameters[] = $this->parseMethodTagValueParameter($tokens); - } - $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); } } + $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); $description = $this->parseOptionalDescription($tokens); return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description); diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index f46bc7e..5bb2639 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -3,6 +3,7 @@ namespace PHPStan\PhpDocParser\Parser; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; @@ -955,7 +956,41 @@ class PhpDocParserTest extends \PHPUnit\Framework\TestCase public function provideMethodTagsData(): iterable { yield [ - 'OK', + 'OK non-static, without return type', + '/** @method foo() */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + null, + 'foo', + [], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type', + '/** @method Foo foo() */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return static type', '/** @method static foo() */', new PhpDocNode([ new PhpDocTagNode( @@ -970,6 +1005,328 @@ class PhpDocParserTest extends \PHPUnit\Framework\TestCase ), ]), ]; + + yield [ + 'OK static, with return type', + '/** @method static Foo foo() */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + true, + new IdentifierTypeNode('Foo'), + 'foo', + [], + '' + ) + ), + ]), + ]; + + yield [ + 'OK static, with return static type', + '/** @method static static foo() */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + true, + new IdentifierTypeNode('static'), + 'foo', + [], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type and description', + '/** @method Foo foo() optional description */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [], + 'optional description' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type and single parameter without type', + '/** @method Foo foo($a) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [ + new MethodTagValueParameterNode( + null, + false, + false, + '$a', + null + ), + ], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type and single parameter with type', + '/** @method Foo foo(A $a) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [ + new MethodTagValueParameterNode( + new IdentifierTypeNode('A'), + false, + false, + '$a', + null + ), + ], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type and single parameter with type that is passed by reference', + '/** @method Foo foo(A &$a) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [ + new MethodTagValueParameterNode( + new IdentifierTypeNode('A'), + true, + false, + '$a', + null + ), + ], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type and single variadic parameter with type', + '/** @method Foo foo(A ...$a) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [ + new MethodTagValueParameterNode( + new IdentifierTypeNode('A'), + false, + true, + '$a', + null + ), + ], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type and single variadic parameter with type that is passed by reference', + '/** @method Foo foo(A &...$a) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [ + new MethodTagValueParameterNode( + new IdentifierTypeNode('A'), + true, + true, + '$a', + null + ), + ], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type and single parameter with default value', + '/** @method Foo foo($a = 123) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [ + new MethodTagValueParameterNode( + null, + false, + false, + '$a', + new ConstExprIntegerNode('123') + ), + ], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type and single variadic parameter with type that is passed by reference and default value', + '/** @method Foo foo(A &...$a = 123) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [ + new MethodTagValueParameterNode( + new IdentifierTypeNode('A'), + true, + true, + '$a', + new ConstExprIntegerNode('123') + ), + ], + '' + ) + ), + ]), + ]; + + yield [ + 'OK non-static, with return type and multiple parameters without type', + '/** @method Foo foo($a, $b, $c) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + false, + new IdentifierTypeNode('Foo'), + 'foo', + [ + new MethodTagValueParameterNode( + null, + false, + false, + '$a', + null + ), + new MethodTagValueParameterNode( + null, + false, + false, + '$b', + null + ), + new MethodTagValueParameterNode( + null, + false, + false, + '$c', + null + ), + ], + '' + ) + ), + ]), + ]; + + yield [ + 'invalid non-static method without parentheses', + '/** @method a b */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new InvalidTagValueNode( + 'a b', + new \PHPStan\PhpDocParser\Parser\ParserException( + '*/', + Lexer::TOKEN_CLOSE_PHPDOC, + 16, + Lexer::TOKEN_OPEN_PARENTHESES + ) + ) + ), + ]), + ]; + + yield [ + 'invalid static method without parentheses', + '/** @method static a b */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new InvalidTagValueNode( + 'static a b', + new \PHPStan\PhpDocParser\Parser\ParserException( + '*/', + Lexer::TOKEN_CLOSE_PHPDOC, + 23, + Lexer::TOKEN_OPEN_PARENTHESES + ) + ) + ), + ]), + ]; + + yield [ + 'invalid non-static method without parameter name', + '/** @method a b(x) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new InvalidTagValueNode( + 'a b(x)', + new \PHPStan\PhpDocParser\Parser\ParserException( + ')', + Lexer::TOKEN_CLOSE_PARENTHESES, + 17, + Lexer::TOKEN_VARIABLE + ) + ) + ), + ]), + ]; } @@ -1214,22 +1571,22 @@ class PhpDocParserTest extends \PHPUnit\Framework\TestCase * @method static methodWithNoReturnTypeStaticallyWithDescription() Do something with a description statically, but what, who knows! * @method static bool aStaticMethodThatHasAUniqueReturnTypeInThisClass() * @method static string aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription() A Description. - * @method int getIntegerNoParams - * @method void doSomethingNoParams - * @method self|Bar getFooOrBarNoParams - * @method methodWithNoReturnTypeNoParams - * @method static int getIntegerStaticallyNoParams - * @method static void doSomethingStaticallyNoParams - * @method static self|Bar getFooOrBarStaticallyNoParams - * @method static methodWithNoReturnTypeStaticallyNoParams - * @method int getIntegerWithDescriptionNoParams Get an integer with a description. - * @method void doSomethingWithDescriptionNoParams Do something with a description. - * @method self|Bar getFooOrBarWithDescriptionNoParams Get a Foo or a Bar with a description. - * @method static int getIntegerStaticallyWithDescriptionNoParams Get an integer with a description statically. - * @method static void doSomethingStaticallyWithDescriptionNoParams Do something with a description statically. - * @method static self|Bar getFooOrBarStaticallyWithDescriptionNoParams Get a Foo or a Bar with a description statically. - * @method static bool|string aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams - * @method static string|float aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams A Description. + * @method int getIntegerNoParams() + * @method void doSomethingNoParams() + * @method self|Bar getFooOrBarNoParams() + * @method methodWithNoReturnTypeNoParams() + * @method static int getIntegerStaticallyNoParams() + * @method static void doSomethingStaticallyNoParams() + * @method static self|Bar getFooOrBarStaticallyNoParams() + * @method static methodWithNoReturnTypeStaticallyNoParams() + * @method int getIntegerWithDescriptionNoParams() Get an integer with a description. + * @method void doSomethingWithDescriptionNoParams() Do something with a description. + * @method self|Bar getFooOrBarWithDescriptionNoParams() Get a Foo or a Bar with a description. + * @method static int getIntegerStaticallyWithDescriptionNoParams() Get an integer with a description statically. + * @method static void doSomethingStaticallyWithDescriptionNoParams() Do something with a description statically. + * @method static self|Bar getFooOrBarStaticallyWithDescriptionNoParams() Get a Foo or a Bar with a description statically. + * @method static bool|string aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams() + * @method static string|float aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams() A Description. * @method \Aws\Result publish(array $args) * @method Image rotate(float & ... $angle = array(), $backgroundColor) * @method Foo overridenMethod()