From d6c050a5330fc2e6d7ff34cf1170a77f9ff872c7 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 14:05:33 +0200 Subject: [PATCH] WIP --- composer.lock | 4 +- src/DocBlock/StandardTagFactory.php | 30 +- .../Tags/Factory/AbstractPHPStanFactory.php | 79 +++++ src/DocBlock/Tags/Factory/PHPStanFactory.php | 16 ++ src/DocBlock/Tags/Factory/ParamFactory.php | 49 ++++ src/DocBlock/Tags/Factory/TypeFactory.php | 169 +++++++++++ src/DocBlockFactory.php | 27 +- src/PhpStan/TagFactory.php | 33 --- src/PhpStanDocblockFactory.php | 152 ---------- src/PseudoTypes/ArrayShape.php | 4 +- src/PseudoTypes/ArrayShapeItem.php | 8 +- .../integration/InterpretingDocBlocksTest.php | 40 ++- .../PhpStanDocblockFactoryTest.php | 61 ---- tests/unit/Assets/CustomTagFactory.php | 47 +++ .../unit/DocBlock/StandardTagFactoryTest.php | 17 ++ .../DocBlock/Tags/Factory/TypeFactoryTest.php | 270 ++++++++++++++++++ 16 files changed, 734 insertions(+), 272 deletions(-) create mode 100644 src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php create mode 100644 src/DocBlock/Tags/Factory/PHPStanFactory.php create mode 100644 src/DocBlock/Tags/Factory/ParamFactory.php create mode 100644 src/DocBlock/Tags/Factory/TypeFactory.php delete mode 100644 src/PhpStan/TagFactory.php delete mode 100644 src/PhpStanDocblockFactory.php delete mode 100644 tests/integration/PhpStanDocblockFactoryTest.php create mode 100644 tests/unit/Assets/CustomTagFactory.php create mode 100644 tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php diff --git a/composer.lock b/composer.lock index 852e5c4..b5912bb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5ca8bb643a11d17932ef43044dc2f12c", + "content-hash": "99af646bb3e1fa0d77d855f0f5995c9f", "packages": [ { "name": "phpdocumentor/reflection-common", @@ -4053,7 +4053,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "ext-filter": "*" }, "platform-dev": [], diff --git a/src/DocBlock/StandardTagFactory.php b/src/DocBlock/StandardTagFactory.php index 8d76595..f77e399 100644 --- a/src/DocBlock/StandardTagFactory.php +++ b/src/DocBlock/StandardTagFactory.php @@ -45,6 +45,7 @@ use function array_slice; use function call_user_func_array; use function count; use function get_class; +use function is_object; use function preg_match; use function strpos; use function trim; @@ -162,18 +163,23 @@ final class StandardTagFactory implements TagFactory $this->serviceLocator[$alias ?: get_class($service)] = $service; } - public function registerTagHandler(string $tagName, string $handler): void + public function registerTagHandler(string $tagName, $handler): void { Assert::stringNotEmpty($tagName); - Assert::classExists($handler); - Assert::implementsInterface($handler, Tag::class); - if (strpos($tagName, '\\') && $tagName[0] !== '\\') { throw new InvalidArgumentException( 'A namespaced tag must have a leading backslash as it must be fully qualified' ); } + if (is_object($handler)) { + Assert::implementsInterface($handler, TagFactory::class); + $this->tagHandlerMappings[$tagName] = $handler; + return; + } + + Assert::classExists($handler); + Assert::implementsInterface($handler, Tag::class); $this->tagHandlerMappings[$tagName] = $handler; } @@ -210,6 +216,8 @@ final class StandardTagFactory implements TagFactory $this->getServiceLocatorWithDynamicParameters($context, $name, $body) ); + $arguments['tagLine'] = sprintf('@%s %s', $name, $body); + try { $callable = [$handlerClassName, 'create']; Assert::isCallable($callable); @@ -225,9 +233,9 @@ final class StandardTagFactory implements TagFactory /** * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). * - * @return class-string + * @return class-string|TagFactory */ - private function findHandlerClassName(string $tagName, TypeContext $context): string + private function findHandlerClassName(string $tagName, TypeContext $context) { $handlerClassName = Generic::class; if (isset($this->tagHandlerMappings[$tagName])) { @@ -275,11 +283,11 @@ final class StandardTagFactory implements TagFactory $parameterName = $parameter->getName(); if (isset($locator[$parameterName])) { - $arguments[] = $locator[$parameterName]; + $arguments[$parameterName] = $locator[$parameterName]; continue; } - $arguments[] = null; + $arguments[$parameterName] = null; } return $arguments; @@ -289,12 +297,14 @@ final class StandardTagFactory implements TagFactory * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given * tag handler class name. * - * @param class-string $handlerClassName + * @param class-string|TagFactory $handler * * @return ReflectionParameter[] */ - private function fetchParametersForHandlerFactoryMethod(string $handlerClassName): array + private function fetchParametersForHandlerFactoryMethod($handler): array { + $handlerClassName = is_object($handler) ? get_class($handler) : $handler; + if (!isset($this->tagHandlerParameterCache[$handlerClassName])) { $methodReflection = new ReflectionMethod($handlerClassName, 'create'); $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); diff --git a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php new file mode 100644 index 0000000..4e4ae3c --- /dev/null +++ b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php @@ -0,0 +1,79 @@ +lexer = new Lexer(); + $constParser = new ConstExprParser(); + $this->parser = new PhpDocParser(new TypeParser($constParser), $constParser); + $this->factories = $factories; + } + + public function addParameter(string $name, $value): void + { + // TODO: Implement addParameter() method. + } + + public function create(string $tagLine, ?TypeContext $context = null): Tag + { + $tokens = $this->lexer->tokenize($tagLine); + $ast = $this->parser->parseTag(new TokenIterator($tokens)); + + foreach ($this->factories as $factory) { + if ($factory->supports($ast, $context)) { + return $factory->create($ast, $context); + } + } + + return InvalidTag::create( + $ast->name, + (string) $ast->value + ); + } + + public function addService(object $service): void + { + // TODO: Implement addService() method. + } + + public function registerTagHandler(string $tagName, string $handler): void + { + // TODO: Implement registerTagHandler() method. + } +} diff --git a/src/DocBlock/Tags/Factory/PHPStanFactory.php b/src/DocBlock/Tags/Factory/PHPStanFactory.php new file mode 100644 index 0000000..4e9f77e --- /dev/null +++ b/src/DocBlock/Tags/Factory/PHPStanFactory.php @@ -0,0 +1,16 @@ +typeFactory = $typeFactory; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ParamTagValueNode::class); + + return new Param( + trim($tagValue->parameterName, '$'), + $this->typeFactory->createType($tagValue->type, $context), + $tagValue->isVariadic, + $this->descriptionFactory->create($tagValue->description, $context), + $tagValue->isReference + ); + } + + public function supports(PhpDocTagNode $node, ?Context $context): bool + { + return $node->value instanceof ParamTagValueNode; + } +} diff --git a/src/DocBlock/Tags/Factory/TypeFactory.php b/src/DocBlock/Tags/Factory/TypeFactory.php new file mode 100644 index 0000000..6c41c1e --- /dev/null +++ b/src/DocBlock/Tags/Factory/TypeFactory.php @@ -0,0 +1,169 @@ +resolver = $resolver; + } + + public function createType(TypeNode $type, Context $context): ?Type + { + switch (get_class($type)) { + case ArrayTypeNode::class: + return new Array_( + $this->createType($type->type, $context) + ); + + case ArrayShapeNode::class: + return new ArrayShape( + ...array_map( + fn (ArrayShapeItemNode $item) => new ArrayShapeItem( + (string) $item->keyName, + $this->createType($item->valueType, $context), + $item->optional + ), + $type->items + ) + ); + + case CallableTypeNode::class: + return $this->createFromCallable($type, $context); + + case ConstTypeNode::class: + case GenericTypeNode::class: + return $this->createFromGeneric($type, $context); + + case IdentifierTypeNode::class: + return $this->resolver->resolve($type->name, $context); + + case IntersectionTypeNode::class: + return new Intersection( + array_map( + fn (TypeNode $nestedType) => $this->createType($nestedType, $context), + $type->types + ) + ); + + case NullableTypeNode::class: + return new Nullable( + $this->createType($type->type, $context) + ); + + case UnionTypeNode::class: + return new Compound( + array_map( + fn (TypeNode $nestedType) => $this->createType($nestedType, $context), + $type->types + ) + ); + + case ThisTypeNode::class: + return new This(); + + case ConditionalTypeNode::class: + case ConditionalTypeForParameterNode::class: + case OffsetAccessTypeNode::class: + default: + return null; + } + } + + private function createFromGeneric(GenericTypeNode $type, Context $context): Type + { + switch (strtolower($type->type->name)) { + case 'array': + return new Array_( + ...array_reverse( + array_map( + fn (TypeNode $genericType) => $this->createType($genericType, $context), + $type->genericTypes + ) + ) + ); + + case 'class-string': + return new ClassString( + $this->createType($type->genericTypes[0], $context)->getFqsen() + ); + + case 'interface-string': + return new InterfaceString( + $this->createType($type->genericTypes[0], $context)->getFqsen() + ); + + case 'list': + return new List_( + $this->createType($type->genericTypes[0], $context) + ); + + case 'int': + return new IntegerRange( + (string) $type->genericTypes[0], + (string) ($type->genericTypes[1] ?? ''), + ); + + default: + return new Collection( + $this->createType($type->type, $context)->getFqsen(), + ...array_reverse( + array_map( + fn (TypeNode $genericType) => $this->createType($genericType, $context), + $type->genericTypes + ) + ) + ); + } + } + + private function createFromCallable(CallableTypeNode $type, Context $context): Callable_ + { + return new Callable_(); + } +} diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index 37f72dd..2a05ac7 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -19,6 +19,9 @@ use phpDocumentor\Reflection\DocBlock\DescriptionFactory; use phpDocumentor\Reflection\DocBlock\StandardTagFactory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\TagFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\AbstractPHPStanFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory; use Webmozart\Assert\Assert; use function array_shift; @@ -47,22 +50,29 @@ final class DocBlockFactory implements DocBlockFactoryInterface public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory) { $this->descriptionFactory = $descriptionFactory; - $this->tagFactory = $tagFactory; + $this->tagFactory = $tagFactory; } /** * Factory method for easy instantiation. * - * @param array> $additionalTags + * @param array|TagFactory> $additionalTags */ public static function createInstance(array $additionalTags = []): self { - $fqsenResolver = new FqsenResolver(); - $tagFactory = new StandardTagFactory($fqsenResolver); + $fqsenResolver = new FqsenResolver(); + $tagFactory = new StandardTagFactory($fqsenResolver); $descriptionFactory = new DescriptionFactory($tagFactory); + $typeResolver = new TypeResolver($fqsenResolver); + $typeFactory = new TypeFactory($typeResolver); + + $phpstanTagFactory = new AbstractPHPStanFactory( + new ParamFactory($typeFactory, $descriptionFactory) + ); $tagFactory->addService($descriptionFactory); - $tagFactory->addService(new TypeResolver($fqsenResolver)); + $tagFactory->addService($typeResolver); + $tagFactory->registerTagHandler('param', $phpstanTagFactory); $docBlockFactory = new self($descriptionFactory, $tagFactory); foreach ($additionalTags as $tagName => $tagHandler) { @@ -138,6 +148,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface } // phpcs:disable + /** * Splits the DocBlock into a template marker, summary, description and block of tags. * @@ -149,7 +160,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface * * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split. */ - private function splitDocBlock(string $comment) : array + private function splitDocBlock(string $comment): array { // phpcs:enable // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This @@ -227,7 +238,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface /** * Creates the tag objects. * - * @param string $tags Tag block to parse. + * @param string $tags Tag block to parse. * @param Types\Context $context Context of the parsed Tag * * @return DocBlock\Tag[] @@ -240,7 +251,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface } $result = []; - $lines = $this->splitTagBlockIntoTagLines($tags); + $lines = $this->splitTagBlockIntoTagLines($tags); foreach ($lines as $key => $tagLine) { $result[$key] = $this->tagFactory->create(trim($tagLine), $context); } diff --git a/src/PhpStan/TagFactory.php b/src/PhpStan/TagFactory.php deleted file mode 100644 index 4215a2a..0000000 --- a/src/PhpStan/TagFactory.php +++ /dev/null @@ -1,33 +0,0 @@ -lexer = new Lexer(); - $self->parser = new PhpDocParser( - new TypeParser($constExprParser), - $constExprParser - ); - - $self->descriptionFactory = new DescriptionFactory(new TagFactory()); - $self->typeResolver = new TypeResolver($fqsenResolver); - - return $self; - } - - public function create($docblock, ?Types\Context $context = null, ?Location $location = null): DocBlock - { - if (is_object($docblock)) { - if (!method_exists($docblock, 'getDocComment')) { - $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method'; - - throw new InvalidArgumentException($exceptionMessage); - } - - $docblock = $docblock->getDocComment(); - Assert::string($docblock); - } - - Assert::stringNotEmpty($docblock); - - $tokens = $this->lexer->tokenize($docblock); - $ast = $this->parser->parse(new TokenIterator($tokens)); - - $textNodes = []; - - foreach ($ast->children as $child) { - if ($child instanceof PhpDocTextNode) { - $textNodes[] = $child->text; - continue; - } - - //If node is not a text node this is the end of description; - break; - } - - $tags = []; - foreach ($ast->getTags() as $node) { - switch ($node->name) { - case '@param': - $tag = $node->value; - $tags[] = new Param( - ltrim($tag->parameterName, '$'), - $this->createType($tag->type, $context), - $tag->isVariadic, - $this->descriptionFactory->create($tag->description), - $tag->isReference - ); - break; - case '@return': - $tag = $node->value; - $tags[] = new Return_( - $this->createType($tag->type, $context), - $this->descriptionFactory->create($tag->description) - ); - } - } - - return new DocBlock( - '', - $this->descriptionFactory->create( - implode("\n", $textNodes) - ), - $tags, - null, - $location - ); - } - - private function createType(TypeNode $type, Context $context) - { - switch (get_class($type)) { - case IdentifierTypeNode::class: - return $this->typeResolver->resolve($type->name, $context); - case ArrayShapeNode::class: - return new ArrayShape( - ... array_map( - fn(ArrayShapeItemNode $item) => new ArrayShapeItem( - (string) $item->keyName, - $this->createType($item->valueType, $context), - $item->optional - ), - $type->items - ) - ); - default: - return null; - } - } -} diff --git a/src/PseudoTypes/ArrayShape.php b/src/PseudoTypes/ArrayShape.php index e1cf57c..93380a3 100644 --- a/src/PseudoTypes/ArrayShape.php +++ b/src/PseudoTypes/ArrayShape.php @@ -10,6 +10,8 @@ use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\ArrayKey; use phpDocumentor\Reflection\Types\Mixed_; +use function implode; + class ArrayShape implements PseudoType { /** @var ArrayShapeItem[] */ @@ -20,7 +22,7 @@ class ArrayShape implements PseudoType $this->items = $items; } - public function underlyingType() : Type + public function underlyingType(): Type { return new Array_(new Mixed_(), new ArrayKey()); } diff --git a/src/PseudoTypes/ArrayShapeItem.php b/src/PseudoTypes/ArrayShapeItem.php index 282b45a..2480f3b 100644 --- a/src/PseudoTypes/ArrayShapeItem.php +++ b/src/PseudoTypes/ArrayShapeItem.php @@ -6,6 +6,8 @@ namespace phpDocumentor\Reflection\PseudoTypes; use phpDocumentor\Reflection\Type; +use function sprintf; + final class ArrayShapeItem { private ?string $key; @@ -19,17 +21,17 @@ final class ArrayShapeItem $this->optional = $optional; } - public function getKey() : ?string + public function getKey(): ?string { return $this->key; } - public function getValue() : Type + public function getValue(): Type { return $this->value; } - public function isOptional() : bool + public function isOptional(): bool { return $this->optional; } diff --git a/tests/integration/InterpretingDocBlocksTest.php b/tests/integration/InterpretingDocBlocksTest.php index 2c8358c..5ebc29c 100644 --- a/tests/integration/InterpretingDocBlocksTest.php +++ b/tests/integration/InterpretingDocBlocksTest.php @@ -17,7 +17,11 @@ use Mockery as m; use phpDocumentor\Reflection\DocBlock\Description; use phpDocumentor\Reflection\DocBlock\StandardTagFactory; use phpDocumentor\Reflection\DocBlock\Tag; +use phpDocumentor\Reflection\DocBlock\Tags\Param; use phpDocumentor\Reflection\DocBlock\Tags\See; +use phpDocumentor\Reflection\Types\Array_; +use phpDocumentor\Reflection\Types\Integer; +use phpDocumentor\Reflection\Types\String_; use PHPUnit\Framework\TestCase; /** @@ -83,7 +87,7 @@ DESCRIPTION; str_replace( PHP_EOL, "\n", - $descriptionText + $descriptionText ), $description->render() ); @@ -134,7 +138,7 @@ DESCRIPTION; str_replace( PHP_EOL, "\n", - <<<'DESCRIPTION' + <<<'DESCRIPTION' You can escape the @-sign by surrounding it with braces, for example: @. And escape a closing brace within an inline tag by adding an opening brace in front of it like this: }. @@ -149,4 +153,36 @@ DESCRIPTION $foundDescription ); } + + public function testMultilineTags(): void + { + $docCommment = << \$store + */ +DOC; + $factory = DocBlockFactory::createInstance(); + $docblock = $factory->create($docCommment); + + self::assertEquals( + [ + new Param( + 'store', + new Array_( + new String_(), + new Integer() + ), + false, + new Description(''), + ), + ], + $docblock->getTags() + ); + + } } diff --git a/tests/integration/PhpStanDocblockFactoryTest.php b/tests/integration/PhpStanDocblockFactoryTest.php deleted file mode 100644 index a6aa80a..0000000 --- a/tests/integration/PhpStanDocblockFactoryTest.php +++ /dev/null @@ -1,61 +0,0 @@ -create( - $string, - new Context('/') - ); - - self::assertEquals($docblock, $actual); - } -} diff --git a/tests/unit/Assets/CustomTagFactory.php b/tests/unit/Assets/CustomTagFactory.php new file mode 100644 index 0000000..ad054f9 --- /dev/null +++ b/tests/unit/Assets/CustomTagFactory.php @@ -0,0 +1,47 @@ +class = $class; + + return new Generic('custom'); + } + + public function addService(object $service): void + { + // TODO: Implement addService() method. + } + + public function registerTagHandler(string $tagName, string $handler): void + { + // TODO: Implement registerTagHandler() method. + } +} diff --git a/tests/unit/DocBlock/StandardTagFactoryTest.php b/tests/unit/DocBlock/StandardTagFactoryTest.php index 2e380d9..893c3b6 100644 --- a/tests/unit/DocBlock/StandardTagFactoryTest.php +++ b/tests/unit/DocBlock/StandardTagFactoryTest.php @@ -18,6 +18,7 @@ use Mockery as m; use phpDocumentor\Reflection\Assets\CustomParam; use phpDocumentor\Reflection\Assets\CustomServiceClass; use phpDocumentor\Reflection\Assets\CustomServiceInterface; +use phpDocumentor\Reflection\Assets\CustomTagFactory; use phpDocumentor\Reflection\DocBlock\Tags\Author; use phpDocumentor\Reflection\DocBlock\Tags\Formatter; use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter; @@ -225,6 +226,22 @@ class StandardTagFactoryTest extends TestCase $this->assertSame('author', $tag->getName()); } + public function testTagWithHandlerObject(): void + { + $fqsenResolver = new FqsenResolver(); + + $customFactory = new CustomTagFactory(); + $injectedClass = new CustomServiceClass(); + + $tagFactory = new StandardTagFactory($fqsenResolver); + $tagFactory->addService($injectedClass); + $tagFactory->registerTagHandler('param', $customFactory); + $tag = $tagFactory->create('@param foo'); + + self::assertSame('custom', $tag->getName()); + self::assertSame($injectedClass, $customFactory->class); + } + /** * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService * @uses \phpDocumentor\Reflection\DocBlock\Tags\Author diff --git a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php new file mode 100644 index 0000000..8958389 --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php @@ -0,0 +1,270 @@ +tokenize($type); + $constParser = new ConstExprParser(); + $parser = new TypeParser($constParser); + $ast = $parser->parse(new TokenIterator($tokens)); + + $factory = new TypeFactory(new TypeResolver(new FqsenResolver())); + $actual = $factory->createType($ast, new Context('phpDocumentor')); + + self::assertEquals($expected, $actual); + } + + public function typeProvider(): array + { + return [ + [ + 'string', + new String_(), + ], + [ + '( string )', + new String_(), + ], + [ + '\\Foo\Bar\\Baz', + new Object_(new Fqsen('\\Foo\Bar\\Baz')), + ], + [ + 'string|int', + new Compound( + [ + new String_(), + new Integer(), + ] + ), + ], + [ + 'string&int', + new Intersection( + [ + new String_(), + new Integer(), + ] + ), + ], + [ + 'string & (int | float)', + new Intersection( + [ + new String_(), + new Compound( + [ + new Integer(), + new Float_(), + ] + ), + ] + ), + ], + [ + 'string[]', + new Array_( + new String_() + ), + ], + [ + '$this', + new This(), + ], + [ + '?int', + new Nullable( + new Integer() + ), + ], + [ + 'self', + new Self_(), + ], + ]; + } + + public function genericsProvider(): array + { + return [ + [ + 'array', + new Array_( + new Object_(new Fqsen('\\phpDocumentor\\Foo\\Bar')), + new Integer() + ), + ], + [ + 'Collection[]', + new Array_( + new Collection( + new Fqsen('\\phpDocumentor\\Collection'), + new Integer(), + new ArrayKey() + ) + ), + ], + [ + 'class-string', + new ClassString(null), + ], + [ + 'class-string', + new ClassString(new Fqsen('\\phpDocumentor\\Foo')), + ], + [ + 'interface-string', + new InterfaceString(new Fqsen('\\phpDocumentor\\Foo')), + ], + [ + 'List', + new List_(new Object_(new Fqsen('\\phpDocumentor\\Foo'))), + ], + [ + 'int<1, 100>', + new IntegerRange('1', '100'), + ], + ]; + } + + public function callableProvider(): array + { + return [ + [ + 'callable', + new Callable_(), + ], + [ + 'callable()', + new Callable_(), + ], + [ + 'callable(): Foo', + new Callable_(), + ], + [ + 'callable(): (Foo&Bar)', + new Callable_(), + ], + [ + 'callable(A&...$a=, B&...=, C): Foo', + new Callable_(), + ], + ]; + } + + public function constExpressions(): array + { + return [ + ['Foo::FOO_CONSTANT'], + [ + '123', + //new ConstTypeNode(new ConstExprIntegerNode('123')), + ], + [ + '123.2', + //new ConstTypeNode(new ConstExprFloatNode('123.2')), + ], + [ + '"bar"', + //new ConstTypeNode(new ConstExprStringNode('bar')), + ], + [ + 'Foo::FOO_*', + //new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*')), + ], + [ + 'Foo::FOO_*BAR', + //new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*BAR')), + ], + [ + 'Foo::*FOO*', + //new ConstTypeNode(new ConstFetchNode('Foo', '*FOO*')), + ], + [ + 'Foo::A*B*C', + //new ConstTypeNode(new ConstFetchNode('Foo', 'A*B*C')), + ], + [ + 'self::*BAR', + //new ConstTypeNode(new ConstFetchNode('self', '*BAR')), + ], + [ + 'Foo::*', + //new ConstTypeNode(new ConstFetchNode('Foo', '*')), + ], + [ + 'Foo::**', + //new ConstTypeNode(new ConstFetchNode('Foo', '*')), // fails later in PhpDocParser + //Lexer::TOKEN_WILDCARD, + ], + [ + 'Foo::*a', + //new ConstTypeNode(new ConstFetchNode('Foo', '*a')), + ], + [ + '( "foo" | Foo::FOO_* )', +// new UnionTypeNode([ +// new ConstTypeNode(new ConstExprStringNode('foo')), +// new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*')), +// ]), + ], + [ + 'DateTimeImmutable::*|DateTime::*', +// new UnionTypeNode([ +// new ConstTypeNode(new ConstFetchNode('DateTimeImmutable', '*')), +// new ConstTypeNode(new ConstFetchNode('DateTime', '*')), +// ]), + ], + [ + 'ParameterTier::*|null', +// new UnionTypeNode([ +// new ConstTypeNode(new ConstFetchNode('ParameterTier', '*')), +// new IdentifierTypeNode('null'), +// ]), + ], + ]; + } +}