diff --git a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php index 7d96d314..7a5f8e2c 100644 --- a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php +++ b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php @@ -21,6 +21,7 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; +use RuntimeException; /** * Factory class creating tags using phpstan's parser @@ -54,15 +55,19 @@ public function create(string $tagLine, ?TypeContext $context = null): Tag $context = new TypeContext(''); } - foreach ($this->factories as $factory) { - if ($factory->supports($ast, $context)) { - return $factory->create($ast, $context); + try { + foreach ($this->factories as $factory) { + if ($factory->supports($ast, $context)) { + return $factory->create($ast, $context); + } } + } catch (RuntimeException $e) { + return InvalidTag::create((string) $ast->value, 'method')->withError($e); } return InvalidTag::create( - $ast->name, - (string) $ast->value + (string) $ast->value, + $ast->name ); } } diff --git a/tests/integration/TypedTagsTest.php b/tests/integration/TypedTagsTest.php new file mode 100644 index 00000000..b0f6d651 --- /dev/null +++ b/tests/integration/TypedTagsTest.php @@ -0,0 +1,194 @@ +create($docblock); + + $this->assertInstanceOf(Param::class, $phpdoc->getTags()[0]); + $this->assertEquals($expectedType, $phpdoc->getTags()[0]->getType()); + } + + /** @dataProvider typeProvider */ + public function testVarFormats(string $type, Type $expectedType): void + { + $docblock = <<create($docblock); + + $this->assertInstanceOf(Var_::class, $phpdoc->getTags()[0]); + $this->assertEquals($expectedType, $phpdoc->getTags()[0]->getType()); + } + + /** @dataProvider typeProvider */ + public function testMethodFormats(string $type, Type $expectedType): void + { + $docblock = <<create($docblock); + + $this->assertInstanceOf(Method::class, $phpdoc->getTags()[0]); + $this->assertEquals($expectedType, $phpdoc->getTags()[0]->getReturnType()); + $this->assertEquals($expectedType, $phpdoc->getTags()[0]->getParameters()[0]->getType()); + } + + /** @dataProvider invalidFormatsProvider */ + public function testInvalidParam(string $type): void + { + $docblock = <<create($docblock); + + $this->assertInstanceOf(InvalidTag::class, $phpdoc->getTags()[0]); + $this->assertEquals("$type \$param", (string) $phpdoc->getTags()[0]); + + } + + /** @dataProvider invalidFormatsProvider */ + public function testInvalidMethod(string $type): void + { + $docblock = <<create($docblock); + + $this->assertInstanceOf(InvalidTag::class, $phpdoc->getTags()[0]); + $this->assertEquals("$type setValue($type \$param)", (string) $phpdoc->getTags()[0]); + + } + + public static function typeProvider(): array + { + return [ + [ + 'int', + new Integer(), + ], + [ + 'integer', + new Integer(), + ], + [ + 'string', + new String_(), + ], + [ + 'T', + new Object_(new Fqsen('\\T')), + ], + [ + 'array', + new Array_(), + ], + [ + 'int[]', + new Array_(new Integer()), + ], + [ + 'int[][]', + new Array_(new Array_(new Integer())), + ], + [ + 'array', + new Array_(new String_(), new Integer()), + ], + [ + 'array{ + foo: int, + bar?: ?string + }', + new ArrayShape( + new ArrayShapeItem('foo', new Integer(), false), + new ArrayShapeItem('bar', new Nullable(new String_()), true), + + ) + ], + [ + 'Collection', + new Collection(new Fqsen('\\Collection'), new String_(), new Integer()), + ], + [ + 'Collection', + new Collection(new Fqsen('\\Collection'), new Object_(new Fqsen('\\Tvalue')), new Object_(new Fqsen('\\TKey'))), + ], + [ + 'Collection', + new Collection(new Fqsen('\\Collection'), new Object_(new Fqsen('\\Tvalue'))), + ], + [ + 'callable(int $foo, string $bar): void', + new Callable_([new CallableParameter(new Integer(), 'foo'), new CallableParameter(new String_(), 'bar')], new Void_()), + ], + ]; + } + + public static function invalidFormatsProvider(): \Generator + { + yield from [ + 'invalid collection' => ['self'], + ]; + + foreach (['<', '{', '('] as $badChar) { + yield "invalid format open $badChar" => ["array$badChar\\fooo"]; + } + + foreach (['>', '}', ')'] as $badChar) { + yield "invalid format close $badChar" => ["array\\fooo$badChar"]; + } + } +}