From 7192e67cfa536867249d390a555692efa6db4807 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 14 Oct 2022 15:26:54 +0200 Subject: [PATCH 01/16] First POC steps --- composer.json | 5 +- composer.lock | 47 +++++- src/DocBlockFactoryInterface.php | 2 +- src/PhpStan/TagFactory.php | 33 ++++ src/PhpStanDocblockFactory.php | 152 ++++++++++++++++++ src/PseudoTypes/ArrayShape.php | 32 ++++ src/PseudoTypes/ArrayShapeItem.php | 50 ++++++ .../PhpStanDocblockFactoryTest.php | 61 +++++++ 8 files changed, 378 insertions(+), 4 deletions(-) create mode 100644 src/PhpStan/TagFactory.php create mode 100644 src/PhpStanDocblockFactory.php create mode 100644 src/PseudoTypes/ArrayShape.php create mode 100644 src/PseudoTypes/ArrayShapeItem.php create mode 100644 tests/integration/PhpStanDocblockFactoryTest.php diff --git a/composer.json b/composer.json index 7d76f16e..8a91cb29 100644 --- a/composer.json +++ b/composer.json @@ -14,11 +14,12 @@ } ], "require": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/type-resolver": "^1.3", "webmozart/assert": "^1.9.1", "phpdocumentor/reflection-common": "^2.2", - "ext-filter": "*" + "ext-filter": "*", + "phpstan/phpdoc-parser": "^1.7" }, "require-dev": { "mockery/mockery": "~1.3.5", diff --git a/composer.lock b/composer.lock index c4be8e91..852e5c4c 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": "cbf52dda9a68fb6e5d4da2511d6b4c0d", + "content-hash": "5ca8bb643a11d17932ef43044dc2f12c", "packages": [ { "name": "phpdocumentor/reflection-common", @@ -109,6 +109,51 @@ }, "time": "2022-03-15T21:29:03+00:00" }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "367a8d9d5f7da2a0136422d27ce8840583926955" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/367a8d9d5f7da2a0136422d27ce8840583926955", + "reference": "367a8d9d5f7da2a0136422d27ce8840583926955", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.7.0" + }, + "time": "2022-08-09T12:23:23+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/src/DocBlockFactoryInterface.php b/src/DocBlockFactoryInterface.php index 9995c0c0..cacc382e 100644 --- a/src/DocBlockFactoryInterface.php +++ b/src/DocBlockFactoryInterface.php @@ -14,7 +14,7 @@ interface DocBlockFactoryInterface * * @param array> $additionalTags */ - public static function createInstance(array $additionalTags = []): DocBlockFactory; + public static function createInstance(array $additionalTags = []): self; /** * @param string|object $docblock diff --git a/src/PhpStan/TagFactory.php b/src/PhpStan/TagFactory.php new file mode 100644 index 00000000..4215a2a0 --- /dev/null +++ b/src/PhpStan/TagFactory.php @@ -0,0 +1,33 @@ +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 new file mode 100644 index 00000000..e1cf57cf --- /dev/null +++ b/src/PseudoTypes/ArrayShape.php @@ -0,0 +1,32 @@ +items = $items; + } + + public function underlyingType() : Type + { + return new Array_(new Mixed_(), new ArrayKey()); + } + + public function __toString(): string + { + return 'array{' . implode(', ', $this->items) . '}'; + } +} diff --git a/src/PseudoTypes/ArrayShapeItem.php b/src/PseudoTypes/ArrayShapeItem.php new file mode 100644 index 00000000..282b45a2 --- /dev/null +++ b/src/PseudoTypes/ArrayShapeItem.php @@ -0,0 +1,50 @@ +key = $key; + $this->value = $value; + $this->optional = $optional; + } + + public function getKey() : ?string + { + return $this->key; + } + + public function getValue() : Type + { + return $this->value; + } + + public function isOptional() : bool + { + return $this->optional; + } + + public function __toString() + { + if ($this->key !== null) { + return sprintf( + '%s%s: %s', + $this->key, + $this->optional ? '?' : '', + $this->value + ); + } + + return (string) $this->value; + } +} diff --git a/tests/integration/PhpStanDocblockFactoryTest.php b/tests/integration/PhpStanDocblockFactoryTest.php new file mode 100644 index 00000000..a6aa80aa --- /dev/null +++ b/tests/integration/PhpStanDocblockFactoryTest.php @@ -0,0 +1,61 @@ +create( + $string, + new Context('/') + ); + + self::assertEquals($docblock, $actual); + } +} From d6c050a5330fc2e6d7ff34cf1170a77f9ff872c7 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 14:05:33 +0200 Subject: [PATCH 02/16] 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 852e5c4c..b5912bb2 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 8d765951..f77e3999 100644 --- a/src/DocBlock/StandardTagFactory.php +++ b/src/DocBlock/StandardTagFactory.php @@ -45,6 +45,7 @@ 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 @@ public function addService(object $service, ?string $alias = null): void $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 @@ private function createTag(string $body, string $name, TypeContext $context): Ta $this->getServiceLocatorWithDynamicParameters($context, $name, $body) ); + $arguments['tagLine'] = sprintf('@%s %s', $name, $body); + try { $callable = [$handlerClassName, 'create']; Assert::isCallable($callable); @@ -225,9 +233,9 @@ private function createTag(string $body, string $name, TypeContext $context): Ta /** * 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 @@ private function getArgumentsForParametersFromWiring(array $parameters, array $l $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 @@ private function getArgumentsForParametersFromWiring(array $parameters, array $l * 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 00000000..4e4ae3c8 --- /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 00000000..4e9f77e4 --- /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 00000000..6c41c1eb --- /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 37f72dd2..2a05ac78 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -19,6 +19,9 @@ 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 @@ private function stripDocComment(string $comment): string } // phpcs:disable + /** * Splits the DocBlock into a template marker, summary, description and block of tags. * @@ -149,7 +160,7 @@ private function stripDocComment(string $comment): string * * @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 @@ private function splitDocBlock(string $comment) : array /** * 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 @@ private function parseTagBlock(string $tags, Types\Context $context): array } $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 4215a2a0..00000000 --- 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 e1cf57cf..93380a3a 100644 --- a/src/PseudoTypes/ArrayShape.php +++ b/src/PseudoTypes/ArrayShape.php @@ -10,6 +10,8 @@ use phpDocumentor\Reflection\Types\ArrayKey; use phpDocumentor\Reflection\Types\Mixed_; +use function implode; + class ArrayShape implements PseudoType { /** @var ArrayShapeItem[] */ @@ -20,7 +22,7 @@ public function __construct(ArrayShapeItem ...$items) $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 282b45a2..2480f3ba 100644 --- a/src/PseudoTypes/ArrayShapeItem.php +++ b/src/PseudoTypes/ArrayShapeItem.php @@ -6,6 +6,8 @@ use phpDocumentor\Reflection\Type; +use function sprintf; + final class ArrayShapeItem { private ?string $key; @@ -19,17 +21,17 @@ public function __construct(?string $key, Type $value, bool $optional) $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 2c8358cf..5ebc29c6 100644 --- a/tests/integration/InterpretingDocBlocksTest.php +++ b/tests/integration/InterpretingDocBlocksTest.php @@ -17,7 +17,11 @@ 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 @@ public function testInterpretingASimpleDocBlock(): void str_replace( PHP_EOL, "\n", - $descriptionText + $descriptionText ), $description->render() ); @@ -134,7 +138,7 @@ public function testDescriptionsCanEscapeAtSignsAndClosingBraces(): void 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 @@ public function testDescriptionsCanEscapeAtSignsAndClosingBraces(): void $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 a6aa80aa..00000000 --- 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 00000000..ad054f9a --- /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 2e380d91..893c3b6a 100644 --- a/tests/unit/DocBlock/StandardTagFactoryTest.php +++ b/tests/unit/DocBlock/StandardTagFactoryTest.php @@ -18,6 +18,7 @@ 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 @@ public function testPassingYourOwnSetOfTagHandlersWithoutComment(): void $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 00000000..8958389e --- /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'), +// ]), + ], + ]; + } +} From ece4f5ab18c85dea17c674718a4e51984cde899a Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 14:37:01 +0200 Subject: [PATCH 03/16] Introduce a Simple implementation of the TagFactory interface Less complex interface to implement makes it easier to implement tag factories as they are part of the new behavior of the StandardTagFactory. --- src/DocBlock/DescriptionFactory.php | 4 +-- src/DocBlock/SimpleTagFactory.php | 32 +++++++++++++++++++ src/DocBlock/StandardTagFactory.php | 4 +-- src/DocBlock/TagFactory.php | 13 +------- .../Tags/Factory/AbstractPHPStanFactory.php | 18 ++--------- src/DocBlockFactory.php | 5 +-- 6 files changed, 42 insertions(+), 34 deletions(-) create mode 100644 src/DocBlock/SimpleTagFactory.php diff --git a/src/DocBlock/DescriptionFactory.php b/src/DocBlock/DescriptionFactory.php index 1a519ec4..96357096 100644 --- a/src/DocBlock/DescriptionFactory.php +++ b/src/DocBlock/DescriptionFactory.php @@ -47,13 +47,13 @@ */ class DescriptionFactory { - /** @var TagFactory */ + /** @var SimpleTagFactory */ private $tagFactory; /** * Initializes this factory with the means to construct (inline) tags. */ - public function __construct(TagFactory $tagFactory) + public function __construct(SimpleTagFactory $tagFactory) { $this->tagFactory = $tagFactory; } diff --git a/src/DocBlock/SimpleTagFactory.php b/src/DocBlock/SimpleTagFactory.php new file mode 100644 index 00000000..4f5c42fc --- /dev/null +++ b/src/DocBlock/SimpleTagFactory.php @@ -0,0 +1,32 @@ +|TagFactory + * @return class-string|SimpleTagFactory */ private function findHandlerClassName(string $tagName, TypeContext $context) { @@ -297,7 +297,7 @@ private function getArgumentsForParametersFromWiring(array $parameters, array $l * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given * tag handler class name. * - * @param class-string|TagFactory $handler + * @param class-string|SimpleTagFactory $handler * * @return ReflectionParameter[] */ diff --git a/src/DocBlock/TagFactory.php b/src/DocBlock/TagFactory.php index c0868dcb..f394a8a9 100644 --- a/src/DocBlock/TagFactory.php +++ b/src/DocBlock/TagFactory.php @@ -16,7 +16,7 @@ use InvalidArgumentException; use phpDocumentor\Reflection\Types\Context as TypeContext; -interface TagFactory +interface TagFactory extends SimpleTagFactory { /** * Adds a parameter to the service locator that can be injected in a tag's factory method. @@ -40,17 +40,6 @@ interface TagFactory */ public function addParameter(string $name, $value): void; - /** - * Factory method responsible for instantiating the correct sub type. - * - * @param string $tagLine The text for this tag, including description. - * - * @return Tag A new tag object. - * - * @throws InvalidArgumentException If an invalid tag line was presented. - */ - public function create(string $tagLine, ?TypeContext $context = null): Tag; - /** * Registers a service with the Service Locator using the FQCN of the class or the alias, if provided. * diff --git a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php index 4e4ae3c8..d30df1c0 100644 --- a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php +++ b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php @@ -13,6 +13,7 @@ namespace phpDocumentor\Reflection\DocBlock\Tags\Factory; +use phpDocumentor\Reflection\DocBlock\SimpleTagFactory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\TagFactory; use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; @@ -31,7 +32,7 @@ * * @internal This class is not part of the BC promise of this library. */ -class AbstractPHPStanFactory implements TagFactory +class AbstractPHPStanFactory implements SimpleTagFactory { private PhpDocParser $parser; private Lexer $lexer; @@ -45,11 +46,6 @@ public function __construct(PHPStanFactory ...$factories) $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); @@ -66,14 +62,4 @@ public function create(string $tagLine, ?TypeContext $context = null): Tag (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/DocBlockFactory.php b/src/DocBlockFactory.php index 2a05ac78..84dca511 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -16,6 +16,7 @@ use InvalidArgumentException; use LogicException; use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\DocBlock\SimpleTagFactory; use phpDocumentor\Reflection\DocBlock\StandardTagFactory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\TagFactory; @@ -41,13 +42,13 @@ final class DocBlockFactory implements DocBlockFactoryInterface /** @var DocBlock\DescriptionFactory */ private $descriptionFactory; - /** @var DocBlock\TagFactory */ + /** @var DocBlock\SimpleTagFactory */ private $tagFactory; /** * Initializes this factory with the required subcontractors. */ - public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory) + public function __construct(DescriptionFactory $descriptionFactory, SimpleTagFactory $tagFactory) { $this->descriptionFactory = $descriptionFactory; $this->tagFactory = $tagFactory; From c6f6e2d52328b0c8f5979c6ca5bc4df4d463bd2c Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 14:39:55 +0200 Subject: [PATCH 04/16] Improve naming for tag factory --- src/DocBlock/DescriptionFactory.php | 5 +++-- src/DocBlock/StandardTagFactory.php | 5 +++-- src/DocBlock/TagFactory.php | 3 ++- .../Tags/Factory/AbstractPHPStanFactory.php | 4 ++-- .../Factory/Factory.php} | 14 ++++++++++++-- src/DocBlockFactory.php | 6 +++--- 6 files changed, 25 insertions(+), 12 deletions(-) rename src/DocBlock/{SimpleTagFactory.php => Tags/Factory/Factory.php} (68%) diff --git a/src/DocBlock/DescriptionFactory.php b/src/DocBlock/DescriptionFactory.php index 96357096..d0180184 100644 --- a/src/DocBlock/DescriptionFactory.php +++ b/src/DocBlock/DescriptionFactory.php @@ -13,6 +13,7 @@ namespace phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; use phpDocumentor\Reflection\Types\Context as TypeContext; use phpDocumentor\Reflection\Utils; @@ -47,13 +48,13 @@ */ class DescriptionFactory { - /** @var SimpleTagFactory */ + /** @var Factory */ private $tagFactory; /** * Initializes this factory with the means to construct (inline) tags. */ - public function __construct(SimpleTagFactory $tagFactory) + public function __construct(Factory $tagFactory) { $this->tagFactory = $tagFactory; } diff --git a/src/DocBlock/StandardTagFactory.php b/src/DocBlock/StandardTagFactory.php index 5bb0ac9c..b728d58f 100644 --- a/src/DocBlock/StandardTagFactory.php +++ b/src/DocBlock/StandardTagFactory.php @@ -17,6 +17,7 @@ use phpDocumentor\Reflection\DocBlock\Tags\Author; use phpDocumentor\Reflection\DocBlock\Tags\Covers; use phpDocumentor\Reflection\DocBlock\Tags\Deprecated; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; use phpDocumentor\Reflection\DocBlock\Tags\Generic; use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; use phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag; @@ -233,7 +234,7 @@ private function createTag(string $body, string $name, TypeContext $context): Ta /** * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). * - * @return class-string|SimpleTagFactory + * @return class-string|Factory */ private function findHandlerClassName(string $tagName, TypeContext $context) { @@ -297,7 +298,7 @@ private function getArgumentsForParametersFromWiring(array $parameters, array $l * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given * tag handler class name. * - * @param class-string|SimpleTagFactory $handler + * @param class-string|Factory $handler * * @return ReflectionParameter[] */ diff --git a/src/DocBlock/TagFactory.php b/src/DocBlock/TagFactory.php index f394a8a9..9dec7994 100644 --- a/src/DocBlock/TagFactory.php +++ b/src/DocBlock/TagFactory.php @@ -14,9 +14,10 @@ namespace phpDocumentor\Reflection\DocBlock; use InvalidArgumentException; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; use phpDocumentor\Reflection\Types\Context as TypeContext; -interface TagFactory extends SimpleTagFactory +interface TagFactory extends Factory { /** * Adds a parameter to the service locator that can be injected in a tag's factory method. diff --git a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php index d30df1c0..281cbbc9 100644 --- a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php +++ b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php @@ -13,7 +13,7 @@ namespace phpDocumentor\Reflection\DocBlock\Tags\Factory; -use phpDocumentor\Reflection\DocBlock\SimpleTagFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\TagFactory; use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; @@ -32,7 +32,7 @@ * * @internal This class is not part of the BC promise of this library. */ -class AbstractPHPStanFactory implements SimpleTagFactory +class AbstractPHPStanFactory implements Factory { private PhpDocParser $parser; private Lexer $lexer; diff --git a/src/DocBlock/SimpleTagFactory.php b/src/DocBlock/Tags/Factory/Factory.php similarity index 68% rename from src/DocBlock/SimpleTagFactory.php rename to src/DocBlock/Tags/Factory/Factory.php index 4f5c42fc..a8c94b63 100644 --- a/src/DocBlock/SimpleTagFactory.php +++ b/src/DocBlock/Tags/Factory/Factory.php @@ -1,4 +1,13 @@ descriptionFactory = $descriptionFactory; $this->tagFactory = $tagFactory; From b66b6dc6447c9a30073d83e377cb099d899a6839 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 14:54:08 +0200 Subject: [PATCH 05/16] Fix some php8+ issues --- phpcs.xml.dist | 5 +---- src/DocBlock/StandardTagFactory.php | 8 +++++++- src/DocBlock/TagFactory.php | 5 ++--- src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php | 3 +-- src/DocBlock/Tags/Factory/Factory.php | 1 - src/DocBlockFactory.php | 4 ++-- tests/unit/DocBlock/StandardTagFactoryTest.php | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 27aa317b..03c89541 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -10,13 +10,10 @@ - + - - - diff --git a/src/DocBlock/StandardTagFactory.php b/src/DocBlock/StandardTagFactory.php index b728d58f..eb0c75b7 100644 --- a/src/DocBlock/StandardTagFactory.php +++ b/src/DocBlock/StandardTagFactory.php @@ -41,6 +41,7 @@ use ReflectionParameter; use Webmozart\Assert\Assert; +use function array_key_exists; use function array_merge; use function array_slice; use function call_user_func_array; @@ -48,6 +49,7 @@ use function get_class; use function is_object; use function preg_match; +use function sprintf; use function strpos; use function trim; @@ -164,6 +166,7 @@ public function addService(object $service, ?string $alias = null): void $this->serviceLocator[$alias ?: get_class($service)] = $service; } + /** {@inheritDoc} */ public function registerTagHandler(string $tagName, $handler): void { Assert::stringNotEmpty($tagName); @@ -176,6 +179,7 @@ public function registerTagHandler(string $tagName, $handler): void if (is_object($handler)) { Assert::implementsInterface($handler, TagFactory::class); $this->tagHandlerMappings[$tagName] = $handler; + return; } @@ -217,7 +221,9 @@ private function createTag(string $body, string $name, TypeContext $context): Ta $this->getServiceLocatorWithDynamicParameters($context, $name, $body) ); - $arguments['tagLine'] = sprintf('@%s %s', $name, $body); + if (array_key_exists('tagLine', $arguments)) { + $arguments['tagLine'] = sprintf('@%s %s', $name, $body); + } try { $callable = [$handlerClassName, 'create']; diff --git a/src/DocBlock/TagFactory.php b/src/DocBlock/TagFactory.php index 9dec7994..a6f1ae8d 100644 --- a/src/DocBlock/TagFactory.php +++ b/src/DocBlock/TagFactory.php @@ -15,7 +15,6 @@ use InvalidArgumentException; use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; -use phpDocumentor\Reflection\Types\Context as TypeContext; interface TagFactory extends Factory { @@ -61,7 +60,7 @@ public function addService(object $service): void; * * @param string $tagName Name of tag to register a handler for. When registering a namespaced * tag, the full name, along with a prefixing slash MUST be provided. - * @param class-string $handler FQCN of handler. + * @param class-string|Factory $handler FQCN of handler. * * @throws InvalidArgumentException If the tag name is not a string. * @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but @@ -70,5 +69,5 @@ public function addService(object $service): void; * @throws InvalidArgumentException If the handler is not an existing class. * @throws InvalidArgumentException If the handler does not implement the {@see Tag} interface. */ - public function registerTagHandler(string $tagName, string $handler): void; + public function registerTagHandler(string $tagName, $handler): void; } diff --git a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php index 281cbbc9..62d32f27 100644 --- a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php +++ b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php @@ -13,9 +13,7 @@ namespace phpDocumentor\Reflection\DocBlock\Tags\Factory; -use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; use phpDocumentor\Reflection\DocBlock\Tag; -use phpDocumentor\Reflection\DocBlock\TagFactory; use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; use phpDocumentor\Reflection\Types\Context as TypeContext; use PHPStan\PhpDocParser\Lexer\Lexer; @@ -36,6 +34,7 @@ class AbstractPHPStanFactory implements Factory { private PhpDocParser $parser; private Lexer $lexer; + /** @var PHPStanFactory[] */ private array $factories; public function __construct(PHPStanFactory ...$factories) diff --git a/src/DocBlock/Tags/Factory/Factory.php b/src/DocBlock/Tags/Factory/Factory.php index a8c94b63..190d3ff8 100644 --- a/src/DocBlock/Tags/Factory/Factory.php +++ b/src/DocBlock/Tags/Factory/Factory.php @@ -28,7 +28,6 @@ interface Factory { - /** * Factory method responsible for instantiating the correct sub type. * diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index f75a02c1..2599e548 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -16,11 +16,11 @@ use InvalidArgumentException; use LogicException; use phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; 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\Factory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory; use Webmozart\Assert\Assert; @@ -42,7 +42,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface /** @var DocBlock\DescriptionFactory */ private $descriptionFactory; - /** @var \phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory */ + /** @var Factory */ private $tagFactory; /** diff --git a/tests/unit/DocBlock/StandardTagFactoryTest.php b/tests/unit/DocBlock/StandardTagFactoryTest.php index 893c3b6a..5a954d52 100644 --- a/tests/unit/DocBlock/StandardTagFactoryTest.php +++ b/tests/unit/DocBlock/StandardTagFactoryTest.php @@ -228,7 +228,7 @@ public function testPassingYourOwnSetOfTagHandlersWithoutComment(): void public function testTagWithHandlerObject(): void { - $fqsenResolver = new FqsenResolver(); + $fqsenResolver = new FqsenResolver(); $customFactory = new CustomTagFactory(); $injectedClass = new CustomServiceClass(); From 25d696587f6ec124fa493663b104db89520470a3 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 16:27:10 +0200 Subject: [PATCH 06/16] Cleanup and var implementation --- src/DocBlock/StandardTagFactory.php | 8 +-- src/DocBlock/Tags/Factory/PHPStanFactory.php | 2 +- src/DocBlock/Tags/Factory/ParamFactory.php | 2 +- src/DocBlock/Tags/Factory/TypeFactory.php | 34 ++++++---- src/DocBlock/Tags/Factory/VarFactory.php | 47 ++++++++++++++ src/DocBlockFactory.php | 11 ++-- src/PseudoTypes/ArrayShapeItem.php | 7 ++- tests/unit/Assets/CustomTagFactory.php | 20 +----- .../Tags/Factory/ParamFactoryTest.php | 46 ++++++++++++++ .../Tags/Factory/TagFactoryTestCase.php | 62 +++++++++++++++++++ .../DocBlock/Tags/Factory/TypeFactoryTest.php | 21 +++++++ .../DocBlock/Tags/Factory/VarFactoryTest.php | 44 +++++++++++++ 12 files changed, 259 insertions(+), 45 deletions(-) create mode 100644 src/DocBlock/Tags/Factory/VarFactory.php create mode 100644 tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php create mode 100644 tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php create mode 100644 tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php diff --git a/src/DocBlock/StandardTagFactory.php b/src/DocBlock/StandardTagFactory.php index eb0c75b7..fa724aa4 100644 --- a/src/DocBlock/StandardTagFactory.php +++ b/src/DocBlock/StandardTagFactory.php @@ -76,7 +76,7 @@ final class StandardTagFactory implements TagFactory public const REGEX_TAGNAME = '[\w\-\_\\\\:]+'; /** - * @var array> An array with a tag as a key, and an + * @var array|Factory> An array with a tag as a key, and an * FQCN to a class that handles it as an array value. */ private $tagHandlerMappings = [ @@ -177,7 +177,7 @@ public function registerTagHandler(string $tagName, $handler): void } if (is_object($handler)) { - Assert::implementsInterface($handler, TagFactory::class); + Assert::implementsInterface($handler, Factory::class); $this->tagHandlerMappings[$tagName] = $handler; return; @@ -283,12 +283,12 @@ private function getArgumentsForParametersFromWiring(array $parameters, array $l } } + $parameterName = $parameter->getName(); if (isset($locator[$typeHint])) { - $arguments[] = $locator[$typeHint]; + $arguments[$parameterName] = $locator[$typeHint]; continue; } - $parameterName = $parameter->getName(); if (isset($locator[$parameterName])) { $arguments[$parameterName] = $locator[$parameterName]; continue; diff --git a/src/DocBlock/Tags/Factory/PHPStanFactory.php b/src/DocBlock/Tags/Factory/PHPStanFactory.php index 4e9f77e4..8f352dfa 100644 --- a/src/DocBlock/Tags/Factory/PHPStanFactory.php +++ b/src/DocBlock/Tags/Factory/PHPStanFactory.php @@ -10,7 +10,7 @@ interface PHPStanFactory { - public function create(PhpDocTagNode $node, Context $context): Tag; + public function create(PhpDocTagNode $node, ?Context $context): Tag; public function supports(PhpDocTagNode $node, ?Context $context): bool; } diff --git a/src/DocBlock/Tags/Factory/ParamFactory.php b/src/DocBlock/Tags/Factory/ParamFactory.php index de5713d1..6dbab3dc 100644 --- a/src/DocBlock/Tags/Factory/ParamFactory.php +++ b/src/DocBlock/Tags/Factory/ParamFactory.php @@ -28,7 +28,7 @@ public function __construct(TypeFactory $typeFactory, DescriptionFactory $descri $this->descriptionFactory = $descriptionFactory; } - public function create(PhpDocTagNode $node, Context $context): Tag + public function create(PhpDocTagNode $node, ?Context $context): Tag { $tagValue = $node->value; Assert::isInstanceOf($tagValue, ParamTagValueNode::class); diff --git a/src/DocBlock/Tags/Factory/TypeFactory.php b/src/DocBlock/Tags/Factory/TypeFactory.php index 6c41c1eb..51fa5270 100644 --- a/src/DocBlock/Tags/Factory/TypeFactory.php +++ b/src/DocBlock/Tags/Factory/TypeFactory.php @@ -44,7 +44,7 @@ /** * @internal This class is not part of the BC promise of this library. */ -class TypeFactory +final class TypeFactory { private TypeResolver $resolver; @@ -53,7 +53,7 @@ public function __construct(TypeResolver $resolver) $this->resolver = $resolver; } - public function createType(TypeNode $type, Context $context): ?Type + public function createType(TypeNode $type, ?Context $context): ?Type { switch (get_class($type)) { case ArrayTypeNode::class: @@ -77,6 +77,7 @@ public function createType(TypeNode $type, Context $context): ?Type return $this->createFromCallable($type, $context); case ConstTypeNode::class: + return null; case GenericTypeNode::class: return $this->createFromGeneric($type, $context); @@ -85,22 +86,29 @@ public function createType(TypeNode $type, Context $context): ?Type case IntersectionTypeNode::class: return new Intersection( - array_map( - fn (TypeNode $nestedType) => $this->createType($nestedType, $context), - $type->types + array_filter( + array_map( + fn (TypeNode $nestedType) => $this->createType($nestedType, $context), + $type->types + ) ) ); case NullableTypeNode::class: - return new Nullable( - $this->createType($type->type, $context) - ); + $nestedType = $this->createType($type->type, $context); + if ($nestedType === null) { + return null; + } + + return new Nullable($nestedType); case UnionTypeNode::class: return new Compound( - array_map( - fn (TypeNode $nestedType) => $this->createType($nestedType, $context), - $type->types + array_filter( + array_map( + fn (TypeNode $nestedType) => $this->createType($nestedType, $context), + $type->types + ) ) ); @@ -115,7 +123,7 @@ public function createType(TypeNode $type, Context $context): ?Type } } - private function createFromGeneric(GenericTypeNode $type, Context $context): Type + private function createFromGeneric(GenericTypeNode $type, ?Context $context): Type { switch (strtolower($type->type->name)) { case 'array': @@ -162,7 +170,7 @@ private function createFromGeneric(GenericTypeNode $type, Context $context): Typ } } - private function createFromCallable(CallableTypeNode $type, Context $context): Callable_ + private function createFromCallable(CallableTypeNode $type, ?Context $context): Callable_ { return new Callable_(); } diff --git a/src/DocBlock/Tags/Factory/VarFactory.php b/src/DocBlock/Tags/Factory/VarFactory.php new file mode 100644 index 00000000..1af6f76e --- /dev/null +++ b/src/DocBlock/Tags/Factory/VarFactory.php @@ -0,0 +1,47 @@ +typeFactory = $typeFactory; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, ?Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, VarTagValueNode::class); + + return new Var_( + trim($tagValue->variableName, '$'), + $this->typeFactory->createType($tagValue->type, $context), + $this->descriptionFactory->create($tagValue->description, $context) + ); + } + + public function supports(PhpDocTagNode $node, ?Context $context): bool + { + return $node->value instanceof VarTagValueNode; + } +} diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index 2599e548..299fd6d9 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -42,13 +42,13 @@ final class DocBlockFactory implements DocBlockFactoryInterface /** @var DocBlock\DescriptionFactory */ private $descriptionFactory; - /** @var Factory */ + /** @var TagFactory */ private $tagFactory; /** * Initializes this factory with the required subcontractors. */ - public function __construct(DescriptionFactory $descriptionFactory, Factory $tagFactory) + public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory) { $this->descriptionFactory = $descriptionFactory; $this->tagFactory = $tagFactory; @@ -57,7 +57,7 @@ public function __construct(DescriptionFactory $descriptionFactory, Factory $tag /** * Factory method for easy instantiation. * - * @param array|TagFactory> $additionalTags + * @param array|Factory> $additionalTags */ public static function createInstance(array $additionalTags = []): self { @@ -74,6 +74,7 @@ public static function createInstance(array $additionalTags = []): self $tagFactory->addService($descriptionFactory); $tagFactory->addService($typeResolver); $tagFactory->registerTagHandler('param', $phpstanTagFactory); + $tagFactory->registerTagHandler('var', $phpstanTagFactory); $docBlockFactory = new self($descriptionFactory, $tagFactory); foreach ($additionalTags as $tagName => $tagHandler) { @@ -122,9 +123,9 @@ public function create($docblock, ?Types\Context $context = null, ?Location $loc } /** - * @param class-string $handler + * @param class-string|Factory $handler */ - public function registerTagHandler(string $tagName, string $handler): void + public function registerTagHandler(string $tagName, $handler): void { $this->tagFactory->registerTagHandler($tagName, $handler); } diff --git a/src/PseudoTypes/ArrayShapeItem.php b/src/PseudoTypes/ArrayShapeItem.php index 2480f3ba..5ff245e2 100644 --- a/src/PseudoTypes/ArrayShapeItem.php +++ b/src/PseudoTypes/ArrayShapeItem.php @@ -6,6 +6,7 @@ use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\Types\Mixed_; use function sprintf; final class ArrayShapeItem @@ -14,10 +15,10 @@ final class ArrayShapeItem private Type $value; private bool $optional; - public function __construct(?string $key, Type $value, bool $optional) + public function __construct(?string $key, ?Type $value, bool $optional) { $this->key = $key; - $this->value = $value; + $this->value = $value ?? new Mixed_(); $this->optional = $optional; } @@ -36,7 +37,7 @@ public function isOptional(): bool return $this->optional; } - public function __toString() + public function __toString(): string { if ($this->key !== null) { return sprintf( diff --git a/tests/unit/Assets/CustomTagFactory.php b/tests/unit/Assets/CustomTagFactory.php index ad054f9a..df4a3f69 100644 --- a/tests/unit/Assets/CustomTagFactory.php +++ b/tests/unit/Assets/CustomTagFactory.php @@ -14,34 +14,18 @@ namespace phpDocumentor\Reflection\Assets; use phpDocumentor\Reflection\DocBlock\Tag; -use phpDocumentor\Reflection\DocBlock\TagFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; use phpDocumentor\Reflection\DocBlock\Tags\Generic; use phpDocumentor\Reflection\Types\Context; -use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; -class CustomTagFactory implements TagFactory +class CustomTagFactory implements Factory { public $class; - public function addParameter(string $name, $value): void - { - // TODO: Implement addParameter() method. - } - public function create(string $tagLine, ?Context $context = null, CustomServiceClass $class = null): Tag { $this->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/Tags/Factory/ParamFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php new file mode 100644 index 00000000..86b0f6f3 --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php @@ -0,0 +1,46 @@ +parseTag('@param string $var'); + $factory = new ParamFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + new Param( + 'var', + new String_(), + false, + new Description(''), + false + ), + $factory->create($ast, $context) + ); + } +} diff --git a/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php new file mode 100644 index 00000000..625958fd --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php @@ -0,0 +1,62 @@ +tokenize($tag); + $constParser = new ConstExprParser(); + + return (new PhpDocParser(new TypeParser($constParser), $constParser))->parseTag(new TokenIterator($tokens)); + } + + public function giveTypeFactory(): TypeFactory + { + return new TypeFactory(new TypeResolver(new FqsenResolver())); + } + + public function givenDescriptionFactory(): DescriptionFactory + { + $factory = m::mock(DescriptionFactory::class); + $factory->shouldReceive('create')->andReturn(new Description('')); + + return $factory; + } + + /** + * Call Mockery::close after each test. + * + * @after + */ + public function closeMockery(): void + { + m::close(); + } +} diff --git a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php index 8958389e..ffec35a3 100644 --- a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of phpDocumentor. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @link http://phpdoc.org + */ + namespace phpDocumentor\Reflection\DocBlock\Tags\Factory; use phpDocumentor\Reflection\Fqsen; @@ -56,6 +65,9 @@ public function testTypeBuilding(string $type, Type $expected): void self::assertEquals($expected, $actual); } + /** + * @return array + */ public function typeProvider(): array { return [ @@ -126,6 +138,9 @@ public function typeProvider(): array ]; } + /** + * @return array + */ public function genericsProvider(): array { return [ @@ -169,6 +184,9 @@ public function genericsProvider(): array ]; } + /** + * @return array + */ public function callableProvider(): array { return [ @@ -195,6 +213,9 @@ public function callableProvider(): array ]; } + /** + * @return array + */ public function constExpressions(): array { return [ diff --git a/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php new file mode 100644 index 00000000..a25058b3 --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php @@ -0,0 +1,44 @@ +parseTag('@var string $var'); + $factory = new VarFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + new Var_( + 'var', + new String_(), + new Description('') + ), + $factory->create($ast, $context) + ); + } +} From b12a33921b23b311f34cb6dd77f1fdfbfd20ad1b Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 16:41:00 +0200 Subject: [PATCH 07/16] Add deprecations --- phpunit.xml.dist | 4 ++-- src/DocBlock/Tags/Method.php | 1 + src/DocBlock/Tags/Param.php | 4 ++++ src/DocBlock/Tags/Property.php | 1 + src/DocBlock/Tags/PropertyRead.php | 1 + src/DocBlock/Tags/PropertyWrite.php | 1 + src/DocBlock/Tags/Return_.php | 1 + src/DocBlock/Tags/Var_.php | 1 + 8 files changed, 12 insertions(+), 2 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fd7ab748..baf0ad63 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,8 +4,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" colors="true" - convertDeprecationsToExceptions="true" - beStrictAboutOutputDuringTests="true" + convertDeprecationsToExceptions="false" + beStrictAboutOutputDuringTests="false" forceCoversAnnotation="true" verbose="true" bootstrap="vendor/autoload.php" diff --git a/src/DocBlock/Tags/Method.php b/src/DocBlock/Tags/Method.php index 12996a42..e50cd4c4 100644 --- a/src/DocBlock/Tags/Method.php +++ b/src/DocBlock/Tags/Method.php @@ -92,6 +92,7 @@ public static function create( ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): ?self { + trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/Param.php b/src/DocBlock/Tags/Param.php index 3399649b..1a7eac93 100644 --- a/src/DocBlock/Tags/Param.php +++ b/src/DocBlock/Tags/Param.php @@ -58,12 +58,16 @@ public function __construct( $this->isReference = $isReference; } + /** + * @deprecated Create using static factory is deprecated, this method should not be called directly by library consumers + */ public static function create( string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { + trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/Property.php b/src/DocBlock/Tags/Property.php index 2521fb3f..fd9b57b8 100644 --- a/src/DocBlock/Tags/Property.php +++ b/src/DocBlock/Tags/Property.php @@ -53,6 +53,7 @@ public static function create( ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { + trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/PropertyRead.php b/src/DocBlock/Tags/PropertyRead.php index 9491b39c..37d01af7 100644 --- a/src/DocBlock/Tags/PropertyRead.php +++ b/src/DocBlock/Tags/PropertyRead.php @@ -53,6 +53,7 @@ public static function create( ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { + trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/PropertyWrite.php b/src/DocBlock/Tags/PropertyWrite.php index 2bfdac6a..3b429e51 100644 --- a/src/DocBlock/Tags/PropertyWrite.php +++ b/src/DocBlock/Tags/PropertyWrite.php @@ -53,6 +53,7 @@ public static function create( ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { + trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/Return_.php b/src/DocBlock/Tags/Return_.php index f021b609..64b276d5 100644 --- a/src/DocBlock/Tags/Return_.php +++ b/src/DocBlock/Tags/Return_.php @@ -38,6 +38,7 @@ public static function create( ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { + trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/Var_.php b/src/DocBlock/Tags/Var_.php index fa1f9dbf..12e3aad8 100644 --- a/src/DocBlock/Tags/Var_.php +++ b/src/DocBlock/Tags/Var_.php @@ -53,6 +53,7 @@ public static function create( ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { + trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); From 1dd491c8ecad9698e1bb2c3b87b2513f76f5bc2c Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 16:47:01 +0200 Subject: [PATCH 08/16] Add return tag factory --- src/DocBlock/Tags/Factory/ReturnFactory.php | 44 +++++++++++++++++++ src/DocBlock/Tags/Factory/TypeFactory.php | 2 + src/DocBlock/Tags/Method.php | 3 ++ src/DocBlock/Tags/Param.php | 2 + src/DocBlock/Tags/Property.php | 2 + src/DocBlock/Tags/PropertyRead.php | 2 + src/DocBlock/Tags/PropertyWrite.php | 2 + src/DocBlock/Tags/Return_.php | 4 ++ src/DocBlock/Tags/Var_.php | 2 + src/DocBlockFactory.php | 7 ++- src/PseudoTypes/ArrayShapeItem.php | 2 +- .../Tags/Factory/ReturnFactoryTest.php | 43 ++++++++++++++++++ 12 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 src/DocBlock/Tags/Factory/ReturnFactory.php create mode 100644 tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php diff --git a/src/DocBlock/Tags/Factory/ReturnFactory.php b/src/DocBlock/Tags/Factory/ReturnFactory.php new file mode 100644 index 00000000..f01a1119 --- /dev/null +++ b/src/DocBlock/Tags/Factory/ReturnFactory.php @@ -0,0 +1,44 @@ +typeFactory = $typeFactory; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, ?Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, ReturnTagValueNode::class); + + return new Return_( + $this->typeFactory->createType($tagValue->type, $context), + $this->descriptionFactory->create($tagValue->description, $context) + ); + } + + public function supports(PhpDocTagNode $node, ?Context $context): bool + { + return $node->value instanceof ReturnTagValueNode; + } +} diff --git a/src/DocBlock/Tags/Factory/TypeFactory.php b/src/DocBlock/Tags/Factory/TypeFactory.php index 51fa5270..469930cb 100644 --- a/src/DocBlock/Tags/Factory/TypeFactory.php +++ b/src/DocBlock/Tags/Factory/TypeFactory.php @@ -36,6 +36,7 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; +use function array_filter; use function array_map; use function array_reverse; use function get_class; @@ -78,6 +79,7 @@ public function createType(TypeNode $type, ?Context $context): ?Type case ConstTypeNode::class: return null; + case GenericTypeNode::class: return $this->createFromGeneric($type, $context); diff --git a/src/DocBlock/Tags/Method.php b/src/DocBlock/Tags/Method.php index e50cd4c4..10a0fbc7 100644 --- a/src/DocBlock/Tags/Method.php +++ b/src/DocBlock/Tags/Method.php @@ -31,9 +31,12 @@ use function sort; use function strpos; use function substr; +use function trigger_error; use function trim; use function var_export; +use const E_USER_DEPRECATED; + /** * Reflection class for an {@}method in a Docblock. */ diff --git a/src/DocBlock/Tags/Param.php b/src/DocBlock/Tags/Param.php index 1a7eac93..c4c08808 100644 --- a/src/DocBlock/Tags/Param.php +++ b/src/DocBlock/Tags/Param.php @@ -26,7 +26,9 @@ use function implode; use function strpos; use function substr; +use function trigger_error; +use const E_USER_DEPRECATED; use const PREG_SPLIT_DELIM_CAPTURE; /** diff --git a/src/DocBlock/Tags/Property.php b/src/DocBlock/Tags/Property.php index fd9b57b8..429e7a67 100644 --- a/src/DocBlock/Tags/Property.php +++ b/src/DocBlock/Tags/Property.php @@ -26,7 +26,9 @@ use function implode; use function strpos; use function substr; +use function trigger_error; +use const E_USER_DEPRECATED; use const PREG_SPLIT_DELIM_CAPTURE; /** diff --git a/src/DocBlock/Tags/PropertyRead.php b/src/DocBlock/Tags/PropertyRead.php index 37d01af7..150d4513 100644 --- a/src/DocBlock/Tags/PropertyRead.php +++ b/src/DocBlock/Tags/PropertyRead.php @@ -26,7 +26,9 @@ use function implode; use function strpos; use function substr; +use function trigger_error; +use const E_USER_DEPRECATED; use const PREG_SPLIT_DELIM_CAPTURE; /** diff --git a/src/DocBlock/Tags/PropertyWrite.php b/src/DocBlock/Tags/PropertyWrite.php index 3b429e51..debbb45f 100644 --- a/src/DocBlock/Tags/PropertyWrite.php +++ b/src/DocBlock/Tags/PropertyWrite.php @@ -26,7 +26,9 @@ use function implode; use function strpos; use function substr; +use function trigger_error; +use const E_USER_DEPRECATED; use const PREG_SPLIT_DELIM_CAPTURE; /** diff --git a/src/DocBlock/Tags/Return_.php b/src/DocBlock/Tags/Return_.php index 64b276d5..af3a24a5 100644 --- a/src/DocBlock/Tags/Return_.php +++ b/src/DocBlock/Tags/Return_.php @@ -20,6 +20,10 @@ use phpDocumentor\Reflection\Types\Context as TypeContext; use Webmozart\Assert\Assert; +use function trigger_error; + +use const E_USER_DEPRECATED; + /** * Reflection class for a {@}return tag in a Docblock. */ diff --git a/src/DocBlock/Tags/Var_.php b/src/DocBlock/Tags/Var_.php index 12e3aad8..c523612e 100644 --- a/src/DocBlock/Tags/Var_.php +++ b/src/DocBlock/Tags/Var_.php @@ -26,7 +26,9 @@ use function implode; use function strpos; use function substr; +use function trigger_error; +use const E_USER_DEPRECATED; use const PREG_SPLIT_DELIM_CAPTURE; /** diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index 299fd6d9..43ad3be8 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -22,7 +22,9 @@ use phpDocumentor\Reflection\DocBlock\Tags\Factory\AbstractPHPStanFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\ReturnFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\VarFactory; use Webmozart\Assert\Assert; use function array_shift; @@ -68,13 +70,16 @@ public static function createInstance(array $additionalTags = []): self $typeFactory = new TypeFactory($typeResolver); $phpstanTagFactory = new AbstractPHPStanFactory( - new ParamFactory($typeFactory, $descriptionFactory) + new ParamFactory($typeFactory, $descriptionFactory), + new VarFactory($typeFactory, $descriptionFactory), + new ReturnFactory($typeFactory, $descriptionFactory), ); $tagFactory->addService($descriptionFactory); $tagFactory->addService($typeResolver); $tagFactory->registerTagHandler('param', $phpstanTagFactory); $tagFactory->registerTagHandler('var', $phpstanTagFactory); + $tagFactory->registerTagHandler('return', $phpstanTagFactory); $docBlockFactory = new self($descriptionFactory, $tagFactory); foreach ($additionalTags as $tagName => $tagHandler) { diff --git a/src/PseudoTypes/ArrayShapeItem.php b/src/PseudoTypes/ArrayShapeItem.php index 5ff245e2..62317820 100644 --- a/src/PseudoTypes/ArrayShapeItem.php +++ b/src/PseudoTypes/ArrayShapeItem.php @@ -5,8 +5,8 @@ namespace phpDocumentor\Reflection\PseudoTypes; use phpDocumentor\Reflection\Type; - use phpDocumentor\Reflection\Types\Mixed_; + use function sprintf; final class ArrayShapeItem diff --git a/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php new file mode 100644 index 00000000..3d3be54f --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php @@ -0,0 +1,43 @@ +parseTag('@return string'); + $factory = new ReturnFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + new Return_( + new String_(), + new Description('') + ), + $factory->create($ast, $context) + ); + } +} From cfe98452601997c333b1552942e7b7fd3e8c21c3 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 21:07:34 +0200 Subject: [PATCH 09/16] Add property support --- src/DocBlock/Tags/Factory/PropertyFactory.php | 47 +++++++++++++++++++ .../Tags/Factory/PropertyReadFactory.php | 47 +++++++++++++++++++ .../Tags/Factory/PropertyWriteFactory.php | 47 +++++++++++++++++++ src/DocBlockFactory.php | 9 ++++ .../Tags/Factory/PropertyFactoryTest.php | 44 +++++++++++++++++ .../Tags/Factory/PropertyReadFactoryTest.php | 44 +++++++++++++++++ .../Tags/Factory/PropertyWriteFactoryTest.php | 44 +++++++++++++++++ .../Tags/Factory/ReturnFactoryTest.php | 6 +-- .../DocBlock/Tags/Factory/VarFactoryTest.php | 6 +-- 9 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 src/DocBlock/Tags/Factory/PropertyFactory.php create mode 100644 src/DocBlock/Tags/Factory/PropertyReadFactory.php create mode 100644 src/DocBlock/Tags/Factory/PropertyWriteFactory.php create mode 100644 tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php create mode 100644 tests/unit/DocBlock/Tags/Factory/PropertyReadFactoryTest.php create mode 100644 tests/unit/DocBlock/Tags/Factory/PropertyWriteFactoryTest.php diff --git a/src/DocBlock/Tags/Factory/PropertyFactory.php b/src/DocBlock/Tags/Factory/PropertyFactory.php new file mode 100644 index 00000000..9e7ec668 --- /dev/null +++ b/src/DocBlock/Tags/Factory/PropertyFactory.php @@ -0,0 +1,47 @@ +typeFactory = $typeFactory; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, ?Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); + + return new Property( + trim($tagValue->propertyName, '$'), + $this->typeFactory->createType($tagValue->type, $context), + $this->descriptionFactory->create($tagValue->description, $context) + ); + } + + public function supports(PhpDocTagNode $node, ?Context $context): bool + { + return $node->value instanceof PropertyTagValueNode && $node->name === '@property'; + } +} diff --git a/src/DocBlock/Tags/Factory/PropertyReadFactory.php b/src/DocBlock/Tags/Factory/PropertyReadFactory.php new file mode 100644 index 00000000..31734289 --- /dev/null +++ b/src/DocBlock/Tags/Factory/PropertyReadFactory.php @@ -0,0 +1,47 @@ +typeFactory = $typeFactory; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, ?Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); + + return new PropertyRead( + trim($tagValue->propertyName, '$'), + $this->typeFactory->createType($tagValue->type, $context), + $this->descriptionFactory->create($tagValue->description, $context) + ); + } + + public function supports(PhpDocTagNode $node, ?Context $context): bool + { + return $node->value instanceof PropertyTagValueNode && $node->name === '@property-read'; + } +} diff --git a/src/DocBlock/Tags/Factory/PropertyWriteFactory.php b/src/DocBlock/Tags/Factory/PropertyWriteFactory.php new file mode 100644 index 00000000..0b6ba42a --- /dev/null +++ b/src/DocBlock/Tags/Factory/PropertyWriteFactory.php @@ -0,0 +1,47 @@ +typeFactory = $typeFactory; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, ?Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); + + return new PropertyWrite( + trim($tagValue->propertyName, '$'), + $this->typeFactory->createType($tagValue->type, $context), + $this->descriptionFactory->create($tagValue->description, $context) + ); + } + + public function supports(PhpDocTagNode $node, ?Context $context): bool + { + return $node->value instanceof PropertyTagValueNode && $node->name === '@property-write'; + } +} diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index 43ad3be8..a08bdb10 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -22,6 +22,9 @@ use phpDocumentor\Reflection\DocBlock\Tags\Factory\AbstractPHPStanFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyReadFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyWriteFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\ReturnFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\VarFactory; @@ -73,6 +76,9 @@ public static function createInstance(array $additionalTags = []): self new ParamFactory($typeFactory, $descriptionFactory), new VarFactory($typeFactory, $descriptionFactory), new ReturnFactory($typeFactory, $descriptionFactory), + new PropertyFactory($typeFactory, $descriptionFactory), + new PropertyReadFactory($typeFactory, $descriptionFactory), + new PropertyWriteFactory($typeFactory, $descriptionFactory), ); $tagFactory->addService($descriptionFactory); @@ -80,6 +86,9 @@ public static function createInstance(array $additionalTags = []): self $tagFactory->registerTagHandler('param', $phpstanTagFactory); $tagFactory->registerTagHandler('var', $phpstanTagFactory); $tagFactory->registerTagHandler('return', $phpstanTagFactory); + $tagFactory->registerTagHandler('property', $phpstanTagFactory); + $tagFactory->registerTagHandler('property-read', $phpstanTagFactory); + $tagFactory->registerTagHandler('property-write', $phpstanTagFactory); $docBlockFactory = new self($descriptionFactory, $tagFactory); foreach ($additionalTags as $tagName => $tagHandler) { diff --git a/tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php new file mode 100644 index 00000000..c1ae0cda --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php @@ -0,0 +1,44 @@ +parseTag('@property string $var'); + $factory = new PropertyFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + new Property( + 'var', + new String_(), + new Description('') + ), + $factory->create($ast, $context) + ); + } +} diff --git a/tests/unit/DocBlock/Tags/Factory/PropertyReadFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/PropertyReadFactoryTest.php new file mode 100644 index 00000000..2e1f4cdf --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/PropertyReadFactoryTest.php @@ -0,0 +1,44 @@ +parseTag('@property-read string $var'); + $factory = new PropertyReadFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + new PropertyRead( + 'var', + new String_(), + new Description('') + ), + $factory->create($ast, $context) + ); + } +} diff --git a/tests/unit/DocBlock/Tags/Factory/PropertyWriteFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/PropertyWriteFactoryTest.php new file mode 100644 index 00000000..af6e9034 --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/PropertyWriteFactoryTest.php @@ -0,0 +1,44 @@ +parseTag('@property-write string $var'); + $factory = new PropertyWriteFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + new PropertyWrite( + 'var', + new String_(), + new Description('') + ), + $factory->create($ast, $context) + ); + } +} diff --git a/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php index 3d3be54f..95d1e143 100644 --- a/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php @@ -21,9 +21,9 @@ final class ReturnFactoryTest extends TagFactoryTestCase { /** - * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory::__construct - * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory::create - * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory::supports + * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ReturnFactory::__construct + * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ReturnFactory::create + * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ReturnFactory::supports */ public function testParamIsCreated(): void { diff --git a/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php index a25058b3..a37193b5 100644 --- a/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php @@ -21,9 +21,9 @@ final class VarFactoryTest extends TagFactoryTestCase { /** - * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory::__construct - * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory::create - * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory::supports + * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\VarFactory::__construct + * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\VarFactory::create + * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\VarFactory::supports */ public function testParamIsCreated(): void { From e4ac07bc405d1a3765d684317b8c815acbd91849 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 22:24:15 +0200 Subject: [PATCH 10/16] Add method support --- src/DocBlock/Tags/Factory/MethodFactory.php | 72 ++++++++++ src/DocBlock/Tags/Factory/TypeFactory.php | 6 +- src/DocBlock/Tags/Method.php | 56 +++++++- src/DocBlock/Tags/MethodParameter.php | 72 ++++++++++ src/DocBlockFactory.php | 1 + .../Tags/Factory/MethodFactoryTest.php | 126 ++++++++++++++++++ 6 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 src/DocBlock/Tags/Factory/MethodFactory.php create mode 100644 src/DocBlock/Tags/MethodParameter.php create mode 100644 tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php diff --git a/src/DocBlock/Tags/Factory/MethodFactory.php b/src/DocBlock/Tags/Factory/MethodFactory.php new file mode 100644 index 00000000..cb2d463f --- /dev/null +++ b/src/DocBlock/Tags/Factory/MethodFactory.php @@ -0,0 +1,72 @@ +typeFactory = $typeFactory; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, ?Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, MethodTagValueNode::class); + + return new Method( + $tagValue->methodName, + [], + $this->createReturnType($tagValue, $context), + $tagValue->isStatic, + $this->descriptionFactory->create($tagValue->description, $context), + false, + array_map( + function (MethodTagValueParameterNode $param) use ($context) { + return new MethodParameter( + trim($param->parameterName, '$'), + $this->typeFactory->createType($param->type, $context), + $param->isReference, + $param->isVariadic, + (string) $param->defaultValue + ); + }, + $tagValue->parameters + ), + ); + } + + public function supports(PhpDocTagNode $node, ?Context $context): bool + { + return $node->value instanceof MethodTagValueNode; + } + + private function createReturnType(MethodTagValueNode $tagValue, ?Context $context): Type + { + return $this->typeFactory->createType($tagValue->returnType, $context) ?? new Void_(); + } +} diff --git a/src/DocBlock/Tags/Factory/TypeFactory.php b/src/DocBlock/Tags/Factory/TypeFactory.php index 469930cb..6cf20a36 100644 --- a/src/DocBlock/Tags/Factory/TypeFactory.php +++ b/src/DocBlock/Tags/Factory/TypeFactory.php @@ -54,8 +54,12 @@ public function __construct(TypeResolver $resolver) $this->resolver = $resolver; } - public function createType(TypeNode $type, ?Context $context): ?Type + public function createType(?TypeNode $type, ?Context $context): ?Type { + if ($type === null) { + return null; + } + switch (get_class($type)) { case ArrayTypeNode::class: return new Array_( diff --git a/src/DocBlock/Tags/Method.php b/src/DocBlock/Tags/Method.php index 10a0fbc7..e97df450 100644 --- a/src/DocBlock/Tags/Method.php +++ b/src/DocBlock/Tags/Method.php @@ -24,6 +24,7 @@ use Webmozart\Assert\Assert; use function array_keys; +use function array_map; use function explode; use function implode; use function is_string; @@ -63,6 +64,9 @@ final class Method extends BaseTag implements Factory\StaticMethod /** @var bool */ private $returnsReference; + /** @var MethodParameter[] */ + private array $parameters; + /** * @param array> $arguments * @phpstan-param array $arguments @@ -73,7 +77,8 @@ public function __construct( ?Type $returnType = null, bool $static = false, ?Description $description = null, - bool $returnsReference = false + bool $returnsReference = false, + ?array $parameters = null ) { Assert::stringNotEmpty($methodName); @@ -81,12 +86,15 @@ public function __construct( $returnType = new Void_(); } + $arguments = $this->filterArguments($arguments); + $this->methodName = $methodName; - $this->arguments = $this->filterArguments($arguments); + $this->arguments = $arguments; $this->returnType = $returnType; $this->isStatic = $static; $this->description = $description; $this->returnsReference = $returnsReference; + $this->parameters = $parameters ?? $this->fromLegacyArguments($arguments); } public static function create( @@ -190,7 +198,14 @@ public static function create( } } - return new static($methodName, $arguments, $returnType, $static, $description, $returnsReference); + return new static( + $methodName, + $arguments, + $returnType, + $static, + $description, + $returnsReference + ); } /** @@ -210,6 +225,12 @@ public function getArguments(): array return $this->arguments; } + /** @return MethodParameter[] */ + public function getParameters(): array + { + return $this->parameters; + } + /** * Checks whether the method tag describes a static method or not. * @@ -233,8 +254,11 @@ public function returnsReference(): bool public function __toString(): string { $arguments = []; - foreach ($this->arguments as $argument) { - $arguments[] = $argument['type'] . ' $' . $argument['name']; + foreach ($this->parameters as $parameter) { + $arguments[] = ($parameter->getType() ?? new Mixed_()) . ' ' . + ($parameter->isReference() ? '&' : '') . + ($parameter->isVariadic() ? '...' : '') . + '$' . $parameter->getName(); } $argumentStr = '(' . implode(', ', $arguments) . ')'; @@ -301,4 +325,26 @@ private static function stripRestArg(string $argument): string return $argument; } + + /** + * @param array{name: string, type: Type} $arguments + * @return MethodParameter[] + */ + private function fromLegacyArguments(array $arguments): array + { + trigger_error( + 'Create method parameters via legacy format is deprecated add parameters via the constructor', + E_USER_DEPRECATED + ); + + return array_map( + static function ($arg) { + return new MethodParameter( + $arg['name'], + $arg['type'] + ); + }, + $arguments + ); + } } diff --git a/src/DocBlock/Tags/MethodParameter.php b/src/DocBlock/Tags/MethodParameter.php new file mode 100644 index 00000000..15780a6f --- /dev/null +++ b/src/DocBlock/Tags/MethodParameter.php @@ -0,0 +1,72 @@ +type = $type; + $this->isReference = $isReference; + $this->isVariadic = $isVariadic; + $this->name = $name; + $this->defaultValue = $defaultValue; + } + + public function getName(): string + { + return $this->name; + } + + public function getType(): Type + { + return $this->type; + } + + public function isReference(): bool + { + return $this->isReference; + } + + public function isVariadic(): bool + { + return $this->isVariadic; + } + + public function getDefaultValue(): ?string + { + return $this->defaultValue; + } +} diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index a08bdb10..4c0d58f0 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -89,6 +89,7 @@ public static function createInstance(array $additionalTags = []): self $tagFactory->registerTagHandler('property', $phpstanTagFactory); $tagFactory->registerTagHandler('property-read', $phpstanTagFactory); $tagFactory->registerTagHandler('property-write', $phpstanTagFactory); + $tagFactory->registerTagHandler('method', $phpstanTagFactory); $docBlockFactory = new self($descriptionFactory, $tagFactory); foreach ($additionalTags as $tagName => $tagHandler) { diff --git a/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php new file mode 100644 index 00000000..c29f969d --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php @@ -0,0 +1,126 @@ +parseTag($tagLine); + $factory = new MethodFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + $tag, + $factory->create($ast, $context) + ); + } + + /** @return array> */ + public function tagProvider(): array + { + return [ + [ + '@method static string myMethod()', + new Method( + 'myMethod', + [], + new String_(), + true, + new Description(''), + false, + [] + ), + ], + [ + '@method string myMethod()', + new Method( + 'myMethod', + [], + new String_(), + false, + new Description(''), + false, + [] + ), + ], + [ + '@method myMethod()', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [] + ), + ], + [ + '@method myMethod(int $a = 1)', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [new MethodParameter('a', new Integer(), false, false, '1')] + ), + ], + [ + '@method myMethod(int ...$a)', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [new MethodParameter('a', new Integer(), false, true)] + ), + ], + [ + '@method myMethod(int &$a, string $b)', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [ + new MethodParameter('a', new Integer(), true, false), + new MethodParameter('b', new String_(), false, false), + ] + ), + ], + ]; + } +} From 81553b535f45abf71b95312be297728cb806f7ae Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 22:37:28 +0200 Subject: [PATCH 11/16] Add default types to method tag arguments --- src/DocBlock/Tags/Factory/MethodFactory.php | 3 ++- src/DocBlock/Tags/Method.php | 1 + .../Tags/Factory/MethodFactoryTest.php | 25 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/DocBlock/Tags/Factory/MethodFactory.php b/src/DocBlock/Tags/Factory/MethodFactory.php index cb2d463f..6a12204f 100644 --- a/src/DocBlock/Tags/Factory/MethodFactory.php +++ b/src/DocBlock/Tags/Factory/MethodFactory.php @@ -10,6 +10,7 @@ use phpDocumentor\Reflection\DocBlock\Tags\MethodParameter; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Types\Context; +use phpDocumentor\Reflection\Types\Mixed_; use phpDocumentor\Reflection\Types\Void_; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode; @@ -49,7 +50,7 @@ public function create(PhpDocTagNode $node, ?Context $context): Tag function (MethodTagValueParameterNode $param) use ($context) { return new MethodParameter( trim($param->parameterName, '$'), - $this->typeFactory->createType($param->type, $context), + $this->typeFactory->createType($param->type, $context) ?? new Mixed_(), $param->isReference, $param->isVariadic, (string) $param->defaultValue diff --git a/src/DocBlock/Tags/Method.php b/src/DocBlock/Tags/Method.php index e97df450..701325d0 100644 --- a/src/DocBlock/Tags/Method.php +++ b/src/DocBlock/Tags/Method.php @@ -328,6 +328,7 @@ private static function stripRestArg(string $argument): string /** * @param array{name: string, type: Type} $arguments + * * @return MethodParameter[] */ private function fromLegacyArguments(array $arguments): array diff --git a/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php index c29f969d..17ac469b 100644 --- a/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php @@ -18,6 +18,7 @@ use phpDocumentor\Reflection\DocBlock\Tags\MethodParameter; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Integer; +use phpDocumentor\Reflection\Types\Mixed_; use phpDocumentor\Reflection\Types\String_; use phpDocumentor\Reflection\Types\Void_; @@ -82,6 +83,30 @@ public function tagProvider(): array [] ), ], + [ + '@method myMethod($a)', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [new MethodParameter('a', new Mixed_())] + ), + ], + [ + '@method myMethod($a = 1)', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [new MethodParameter('a', new Mixed_(), false, false, '1')] + ), + ], [ '@method myMethod(int $a = 1)', new Method( From 8d57d3da2d0b8427581ebbf8096f44ce03c37d81 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 22:43:02 +0200 Subject: [PATCH 12/16] Add deprecation to getArguments --- src/DocBlock/Tags/Method.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/DocBlock/Tags/Method.php b/src/DocBlock/Tags/Method.php index 701325d0..bbb20620 100644 --- a/src/DocBlock/Tags/Method.php +++ b/src/DocBlock/Tags/Method.php @@ -49,12 +49,6 @@ final class Method extends BaseTag implements Factory\StaticMethod /** @var string */ private $methodName; - /** - * @phpstan-var array - * @var array> - */ - private $arguments; - /** @var bool */ private $isStatic; @@ -89,7 +83,6 @@ public function __construct( $arguments = $this->filterArguments($arguments); $this->methodName = $methodName; - $this->arguments = $arguments; $this->returnType = $returnType; $this->isStatic = $static; $this->description = $description; @@ -103,7 +96,11 @@ public static function create( ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): ?self { - trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); + trigger_error( + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + E_USER_DEPRECATED + ); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); @@ -217,12 +214,21 @@ public function getMethodName(): string } /** + * @deprecated Method deprecated, use {@see self::getParameters()} + * * @return array> * @phpstan-return array */ public function getArguments(): array { - return $this->arguments; + trigger_error('Method deprecated, use ::getParameters()', E_USER_DEPRECATED); + + return array_map( + static function (MethodParameter $methodParameter) { + return ['name' => $methodParameter->getName(), 'type' => $methodParameter->getType()]; + }, + $this->parameters + ); } /** @return MethodParameter[] */ From 50bf16734e2102c83c5c50caa79046ae4fe2fdb9 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 4 Nov 2022 11:11:01 +0100 Subject: [PATCH 13/16] Add support for constant types. phpstan supports contant definitions and expressions to link to constants. --- src/DocBlock/Tags/Factory/TypeFactory.php | 39 +++++++++- src/DocBlockFactory.php | 2 +- src/PseudoTypes/ConstExpression.php | 44 +++++++++++ src/PseudoTypes/FloatValue.php | 34 +++++++++ src/PseudoTypes/IntegerValue.php | 34 +++++++++ src/PseudoTypes/StringValue.php | 36 +++++++++ .../Tags/Factory/TagFactoryTestCase.php | 2 +- .../DocBlock/Tags/Factory/TypeFactoryTest.php | 75 +++++-------------- 8 files changed, 205 insertions(+), 61 deletions(-) create mode 100644 src/PseudoTypes/ConstExpression.php create mode 100644 src/PseudoTypes/FloatValue.php create mode 100644 src/PseudoTypes/IntegerValue.php create mode 100644 src/PseudoTypes/StringValue.php diff --git a/src/DocBlock/Tags/Factory/TypeFactory.php b/src/DocBlock/Tags/Factory/TypeFactory.php index 6cf20a36..2204c2f4 100644 --- a/src/DocBlock/Tags/Factory/TypeFactory.php +++ b/src/DocBlock/Tags/Factory/TypeFactory.php @@ -4,10 +4,15 @@ namespace phpDocumentor\Reflection\DocBlock\Tags\Factory; +use phpDocumentor\Reflection\FqsenResolver; use phpDocumentor\Reflection\PseudoTypes\ArrayShape; use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem; +use phpDocumentor\Reflection\PseudoTypes\ConstExpression; +use phpDocumentor\Reflection\PseudoTypes\FloatValue; use phpDocumentor\Reflection\PseudoTypes\IntegerRange; +use phpDocumentor\Reflection\PseudoTypes\IntegerValue; use phpDocumentor\Reflection\PseudoTypes\List_; +use phpDocumentor\Reflection\PseudoTypes\StringValue; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Array_; @@ -20,6 +25,10 @@ use phpDocumentor\Reflection\Types\Intersection; use phpDocumentor\Reflection\Types\Nullable; use phpDocumentor\Reflection\Types\This; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; @@ -48,10 +57,12 @@ final class TypeFactory { private TypeResolver $resolver; + private FqsenResolver $fqsenResolver; - public function __construct(TypeResolver $resolver) + public function __construct(TypeResolver $resolver, FqsenResolver $fqsenResolver) { $this->resolver = $resolver; + $this->fqsenResolver = $fqsenResolver; } public function createType(?TypeNode $type, ?Context $context): ?Type @@ -82,7 +93,7 @@ public function createType(?TypeNode $type, ?Context $context): ?Type return $this->createFromCallable($type, $context); case ConstTypeNode::class: - return null; + return $this->createFromConst($type, $context); case GenericTypeNode::class: return $this->createFromGeneric($type, $context); @@ -144,12 +155,12 @@ private function createFromGeneric(GenericTypeNode $type, ?Context $context): Ty case 'class-string': return new ClassString( - $this->createType($type->genericTypes[0], $context)->getFqsen() + $this->fqsenResolver->resolve((string) $type->genericTypes[0], $context) ); case 'interface-string': return new InterfaceString( - $this->createType($type->genericTypes[0], $context)->getFqsen() + $this->fqsenResolver->resolve((string) $type->genericTypes[0], $context) ); case 'list': @@ -180,4 +191,24 @@ private function createFromCallable(CallableTypeNode $type, ?Context $context): { return new Callable_(); } + + private function createFromConst(ConstTypeNode $type, ?Context $context): ?Type + { + switch (get_class($type->constExpr)) { + case ConstExprIntegerNode::class: + return new IntegerValue((int) $type->constExpr->value); + + case ConstExprFloatNode::class: + return new FloatValue((float) $type->constExpr->value); + + case ConstExprStringNode::class: + return new StringValue($type->constExpr->value); + + case ConstFetchNode::class: + return new ConstExpression( + $this->fqsenResolver->resolve($type->constExpr->className, $context), + $type->constExpr->name + ); + } + } } diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index 4c0d58f0..8610dbb0 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -70,7 +70,7 @@ public static function createInstance(array $additionalTags = []): self $tagFactory = new StandardTagFactory($fqsenResolver); $descriptionFactory = new DescriptionFactory($tagFactory); $typeResolver = new TypeResolver($fqsenResolver); - $typeFactory = new TypeFactory($typeResolver); + $typeFactory = new TypeFactory($typeResolver, $fqsenResolver); $phpstanTagFactory = new AbstractPHPStanFactory( new ParamFactory($typeFactory, $descriptionFactory), diff --git a/src/PseudoTypes/ConstExpression.php b/src/PseudoTypes/ConstExpression.php new file mode 100644 index 00000000..b7189cec --- /dev/null +++ b/src/PseudoTypes/ConstExpression.php @@ -0,0 +1,44 @@ +owner = $owner; + $this->expression = $expression; + } + + public function getOwner(): Fqsen + { + return $this->owner; + } + + public function getExpression(): string + { + return $this->expression; + } + + public function underlyingType(): Type + { + return new Mixed_(); + } + + public function __toString(): string + { + return sprintf('%s::%s', $this->owner, $this->expression); + } +} diff --git a/src/PseudoTypes/FloatValue.php b/src/PseudoTypes/FloatValue.php new file mode 100644 index 00000000..07e0156c --- /dev/null +++ b/src/PseudoTypes/FloatValue.php @@ -0,0 +1,34 @@ +value = $value; + } + + public function getValue(): float + { + return $this->value; + } + + public function underlyingType(): Type + { + return new Float_(); + } + + public function __toString(): string + { + return (string) $this->value; + } +} diff --git a/src/PseudoTypes/IntegerValue.php b/src/PseudoTypes/IntegerValue.php new file mode 100644 index 00000000..2a56ad4d --- /dev/null +++ b/src/PseudoTypes/IntegerValue.php @@ -0,0 +1,34 @@ +value = $value; + } + + public function getValue(): int + { + return $this->value; + } + + public function underlyingType(): Type + { + return new Integer(); + } + + public function __toString(): string + { + return (string) $this->value; + } +} diff --git a/src/PseudoTypes/StringValue.php b/src/PseudoTypes/StringValue.php new file mode 100644 index 00000000..f4909cd3 --- /dev/null +++ b/src/PseudoTypes/StringValue.php @@ -0,0 +1,36 @@ +value = $value; + } + + public function getValue(): string + { + return $this->value; + } + + public function underlyingType(): Type + { + return new Float_(); + } + + public function __toString(): string + { + return sprintf('"%s"', $this->value); + } +} diff --git a/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php index 625958fd..4b2c7bea 100644 --- a/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php +++ b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php @@ -39,7 +39,7 @@ public function parseTag(string $tag): PhpDocTagNode public function giveTypeFactory(): TypeFactory { - return new TypeFactory(new TypeResolver(new FqsenResolver())); + return new TypeFactory(new TypeResolver(new FqsenResolver()), new FqsenResolver()); } public function givenDescriptionFactory(): DescriptionFactory diff --git a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php index ffec35a3..ca3c282b 100644 --- a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php @@ -15,8 +15,13 @@ use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\FqsenResolver; +use phpDocumentor\Reflection\PseudoTypes\ConstExpression; +use phpDocumentor\Reflection\PseudoTypes\FloatValue; use phpDocumentor\Reflection\PseudoTypes\IntegerRange; +use phpDocumentor\Reflection\PseudoTypes\IntegerValue; use phpDocumentor\Reflection\PseudoTypes\List_; +use phpDocumentor\Reflection\PseudoTypes\StringValue; +use phpDocumentor\Reflection\PseudoTypes\True_; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Array_; @@ -50,6 +55,8 @@ final class TypeFactoryTest extends TestCase * @dataProvider typeProvider * @dataProvider genericsProvider * @dataProvider callableProvider + * @dataProvider constExpressions + * @testdox create type from $type */ public function testTypeBuilding(string $type, Type $expected): void { @@ -58,8 +65,9 @@ public function testTypeBuilding(string $type, Type $expected): void $constParser = new ConstExprParser(); $parser = new TypeParser($constParser); $ast = $parser->parse(new TokenIterator($tokens)); + $fqsenResolver = new FqsenResolver(); - $factory = new TypeFactory(new TypeResolver(new FqsenResolver())); + $factory = new TypeFactory(new TypeResolver($fqsenResolver), $fqsenResolver); $actual = $factory->createType($ast, new Context('phpDocumentor')); self::assertEquals($expected, $actual); @@ -219,72 +227,29 @@ public function callableProvider(): array public function constExpressions(): array { return [ - ['Foo::FOO_CONSTANT'], [ '123', - //new ConstTypeNode(new ConstExprIntegerNode('123')), + new IntegerValue(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*')), + 'true', + new True_(), ], [ - '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')), + '123.2', + new FloatValue(123.2), ], [ - '( "foo" | Foo::FOO_* )', -// new UnionTypeNode([ -// new ConstTypeNode(new ConstExprStringNode('foo')), -// new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*')), -// ]), + '"bar"', + new StringValue('bar'), ], [ - 'DateTimeImmutable::*|DateTime::*', -// new UnionTypeNode([ -// new ConstTypeNode(new ConstFetchNode('DateTimeImmutable', '*')), -// new ConstTypeNode(new ConstFetchNode('DateTime', '*')), -// ]), + 'Foo::FOO_CONSTANT', + new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_CONSTANT'), ], [ - 'ParameterTier::*|null', -// new UnionTypeNode([ -// new ConstTypeNode(new ConstFetchNode('ParameterTier', '*')), -// new IdentifierTypeNode('null'), -// ]), + 'Foo::FOO_*', + new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_*'), ], ]; } From d6e90a32380c0cded3b66255456b7c24d15decdc Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 4 Nov 2022 15:43:05 +0100 Subject: [PATCH 14/16] Add callable parameter support --- src/DocBlock/Tags/Factory/TypeFactory.php | 18 ++++++- .../DocBlock/Tags/Factory/TypeFactoryTest.php | 47 ++++++++++++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/DocBlock/Tags/Factory/TypeFactory.php b/src/DocBlock/Tags/Factory/TypeFactory.php index 2204c2f4..5745d992 100644 --- a/src/DocBlock/Tags/Factory/TypeFactory.php +++ b/src/DocBlock/Tags/Factory/TypeFactory.php @@ -17,6 +17,7 @@ use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\Callable_; +use phpDocumentor\Reflection\Types\CallableParameter; use phpDocumentor\Reflection\Types\ClassString; use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Compound; @@ -33,6 +34,7 @@ use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode; use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; @@ -189,7 +191,21 @@ private function createFromGeneric(GenericTypeNode $type, ?Context $context): Ty private function createFromCallable(CallableTypeNode $type, ?Context $context): Callable_ { - return new Callable_(); + return new Callable_( + array_map( + function (CallableTypeParameterNode $param) use ($context) { + return new CallableParameter( + $param->parameterName !== '' ? trim($param->parameterName, '$') : null, + $this->createType($param->type, $context), + $param->isReference, + $param->isVariadic, + $param->isOptional + ); + }, + $type->parameters + ), + $this->createType($type->returnType, $context), + ); } private function createFromConst(ConstTypeNode $type, ?Context $context): ?Type diff --git a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php index ca3c282b..bb5a126c 100644 --- a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php @@ -27,6 +27,7 @@ use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\ArrayKey; use phpDocumentor\Reflection\Types\Callable_; +use phpDocumentor\Reflection\Types\CallableParameter; use phpDocumentor\Reflection\Types\ClassString; use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Compound; @@ -49,9 +50,9 @@ final class TypeFactoryTest extends TestCase { /** - * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory::createType - * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory::createFromGeneric - * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory::createFromCallable + * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory::createType + * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory::createFromGeneric + * @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory::createFromCallable * @dataProvider typeProvider * @dataProvider genericsProvider * @dataProvider callableProvider @@ -208,15 +209,49 @@ public function callableProvider(): array ], [ 'callable(): Foo', - new Callable_(), + new Callable_([], new Object_(new Fqsen('\\phpDocumentor\\Foo'))), ], [ 'callable(): (Foo&Bar)', - new Callable_(), + new Callable_( + [], + new Intersection( + [ + new Object_(new Fqsen('\\phpDocumentor\\Foo')), + new Object_(new Fqsen('\\phpDocumentor\\Bar')) + ] + ) + ), ], [ 'callable(A&...$a=, B&...=, C): Foo', - new Callable_(), + new Callable_( + [ + new CallableParameter( + 'a', + new Object_(new Fqsen('\\phpDocumentor\\A')), + true, + true, + true + ), + new CallableParameter( + null, + new Object_(new Fqsen('\\phpDocumentor\\B')), + true, + true, + true + ), + new CallableParameter( + null, + new Object_(new Fqsen('\\phpDocumentor\\C')), + false, + false, + false + ), + ], + new Object_(new Fqsen('\\phpDocumentor\\Foo') + ) + ), ], ]; } From 6a92bc3ce91ed40e8b1fc478f167ae5b8de17f5f Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 11 Nov 2022 12:30:14 +0100 Subject: [PATCH 15/16] Remove type logic from this package --- composer.json | 4 +- composer.lock | 46 +-- .../Tags/Factory/AbstractPHPStanFactory.php | 4 + src/DocBlock/Tags/Factory/MethodFactory.php | 17 +- src/DocBlock/Tags/Factory/PHPStanFactory.php | 4 +- src/DocBlock/Tags/Factory/ParamFactory.php | 13 +- src/DocBlock/Tags/Factory/PropertyFactory.php | 13 +- .../Tags/Factory/PropertyReadFactory.php | 13 +- .../Tags/Factory/PropertyWriteFactory.php | 13 +- src/DocBlock/Tags/Factory/ReturnFactory.php | 13 +- src/DocBlock/Tags/Factory/TypeFactory.php | 230 -------------- src/DocBlock/Tags/Factory/VarFactory.php | 13 +- src/DocBlockFactory.php | 13 +- src/PseudoTypes/ArrayShape.php | 34 -- src/PseudoTypes/ArrayShapeItem.php | 53 ---- src/PseudoTypes/ConstExpression.php | 44 --- src/PseudoTypes/FloatValue.php | 34 -- src/PseudoTypes/IntegerValue.php | 34 -- src/PseudoTypes/StringValue.php | 36 --- .../Tags/Factory/MethodFactoryTest.php | 14 +- .../Tags/Factory/ParamFactoryTest.php | 2 +- .../Tags/Factory/PropertyFactoryTest.php | 2 +- .../Tags/Factory/PropertyReadFactoryTest.php | 2 +- .../Tags/Factory/PropertyWriteFactoryTest.php | 2 +- .../Tags/Factory/ReturnFactoryTest.php | 2 +- .../Tags/Factory/TagFactoryTestCase.php | 4 +- .../DocBlock/Tags/Factory/TypeFactoryTest.php | 291 ------------------ .../DocBlock/Tags/Factory/VarFactoryTest.php | 2 +- 28 files changed, 108 insertions(+), 844 deletions(-) delete mode 100644 src/DocBlock/Tags/Factory/TypeFactory.php delete mode 100644 src/PseudoTypes/ArrayShape.php delete mode 100644 src/PseudoTypes/ArrayShapeItem.php delete mode 100644 src/PseudoTypes/ConstExpression.php delete mode 100644 src/PseudoTypes/FloatValue.php delete mode 100644 src/PseudoTypes/IntegerValue.php delete mode 100644 src/PseudoTypes/StringValue.php delete mode 100644 tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php diff --git a/composer.json b/composer.json index 8a91cb29..c4348f1f 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,8 @@ } ], "require": { - "php": "^7.4 || ^8.0", - "phpdocumentor/type-resolver": "^1.3", + "php": "^7.2 || ^8.0", + "phpdocumentor/type-resolver": "1.x-dev@dev", "webmozart/assert": "^1.9.1", "phpdocumentor/reflection-common": "^2.2", "ext-filter": "*", diff --git a/composer.lock b/composer.lock index b5912bb2..55b451a9 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": "99af646bb3e1fa0d77d855f0f5995c9f", + "content-hash": "829061026cc6b2284953cbe816406760", "packages": [ { "name": "phpdocumentor/reflection-common", @@ -61,26 +61,34 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.1", + "version": "1.x-dev", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" + "reference": "662b17f56786e34354684f6c141c1909b1ee3ac0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/662b17f56786e34354684f6c141c1909b1ee3ac0", + "reference": "662b17f56786e34354684f6c141c1909b1ee3ac0", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" }, "require-dev": { "ext-tokenizer": "*", - "psalm/phar": "^4.8" + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -105,22 +113,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.x" }, - "time": "2022-03-15T21:29:03+00:00" + "time": "2022-11-11T10:44:03+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.7.0", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "367a8d9d5f7da2a0136422d27ce8840583926955" + "reference": "33aefcdab42900e36366d0feab6206e2dd68f947" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/367a8d9d5f7da2a0136422d27ce8840583926955", - "reference": "367a8d9d5f7da2a0136422d27ce8840583926955", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/33aefcdab42900e36366d0feab6206e2dd68f947", + "reference": "33aefcdab42900e36366d0feab6206e2dd68f947", "shasum": "" }, "require": { @@ -150,9 +158,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.7.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.13.0" }, - "time": "2022-08-09T12:23:23+00:00" + "time": "2022-10-21T09:57:39+00:00" }, { "name": "webmozart/assert", @@ -4049,11 +4057,13 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "phpdocumentor/type-resolver": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.4 || ^8.0", + "php": "^7.2 || ^8.0", "ext-filter": "*" }, "platform-dev": [], diff --git a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php index 62d32f27..7d96d314 100644 --- a/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php +++ b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php @@ -50,6 +50,10 @@ public function create(string $tagLine, ?TypeContext $context = null): Tag $tokens = $this->lexer->tokenize($tagLine); $ast = $this->parser->parseTag(new TokenIterator($tokens)); + if ($context === null) { + $context = new TypeContext(''); + } + foreach ($this->factories as $factory) { if ($factory->supports($ast, $context)) { return $factory->create($ast, $context); diff --git a/src/DocBlock/Tags/Factory/MethodFactory.php b/src/DocBlock/Tags/Factory/MethodFactory.php index 6a12204f..c884743f 100644 --- a/src/DocBlock/Tags/Factory/MethodFactory.php +++ b/src/DocBlock/Tags/Factory/MethodFactory.php @@ -9,6 +9,7 @@ use phpDocumentor\Reflection\DocBlock\Tags\Method; use phpDocumentor\Reflection\DocBlock\Tags\MethodParameter; use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Mixed_; use phpDocumentor\Reflection\Types\Void_; @@ -25,16 +26,16 @@ */ final class MethodFactory implements PHPStanFactory { - private TypeFactory $typeFactory; private DescriptionFactory $descriptionFactory; + private TypeResolver $typeResolver; - public function __construct(TypeFactory $typeFactory, DescriptionFactory $descriptionFactory) + public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory) { - $this->typeFactory = $typeFactory; $this->descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; } - public function create(PhpDocTagNode $node, ?Context $context): Tag + public function create(PhpDocTagNode $node, Context $context): Tag { $tagValue = $node->value; Assert::isInstanceOf($tagValue, MethodTagValueNode::class); @@ -50,7 +51,7 @@ public function create(PhpDocTagNode $node, ?Context $context): Tag function (MethodTagValueParameterNode $param) use ($context) { return new MethodParameter( trim($param->parameterName, '$'), - $this->typeFactory->createType($param->type, $context) ?? new Mixed_(), + $this->typeResolver->createType($param->type, $context) ?? new Mixed_(), $param->isReference, $param->isVariadic, (string) $param->defaultValue @@ -61,13 +62,13 @@ function (MethodTagValueParameterNode $param) use ($context) { ); } - public function supports(PhpDocTagNode $node, ?Context $context): bool + public function supports(PhpDocTagNode $node, Context $context): bool { return $node->value instanceof MethodTagValueNode; } - private function createReturnType(MethodTagValueNode $tagValue, ?Context $context): Type + private function createReturnType(MethodTagValueNode $tagValue, Context $context): Type { - return $this->typeFactory->createType($tagValue->returnType, $context) ?? new Void_(); + return $this->typeResolver->createType($tagValue->returnType, $context) ?? new Void_(); } } diff --git a/src/DocBlock/Tags/Factory/PHPStanFactory.php b/src/DocBlock/Tags/Factory/PHPStanFactory.php index 8f352dfa..cf04a06e 100644 --- a/src/DocBlock/Tags/Factory/PHPStanFactory.php +++ b/src/DocBlock/Tags/Factory/PHPStanFactory.php @@ -10,7 +10,7 @@ interface PHPStanFactory { - public function create(PhpDocTagNode $node, ?Context $context): Tag; + public function create(PhpDocTagNode $node, Context $context): Tag; - public function supports(PhpDocTagNode $node, ?Context $context): bool; + public function supports(PhpDocTagNode $node, Context $context): bool; } diff --git a/src/DocBlock/Tags/Factory/ParamFactory.php b/src/DocBlock/Tags/Factory/ParamFactory.php index 6dbab3dc..fd0c0061 100644 --- a/src/DocBlock/Tags/Factory/ParamFactory.php +++ b/src/DocBlock/Tags/Factory/ParamFactory.php @@ -7,6 +7,7 @@ use phpDocumentor\Reflection\DocBlock\DescriptionFactory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Tags\Param; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Context; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; @@ -19,30 +20,30 @@ */ final class ParamFactory implements PHPStanFactory { - private TypeFactory $typeFactory; private DescriptionFactory $descriptionFactory; + private TypeResolver $typeResolver; - public function __construct(TypeFactory $typeFactory, DescriptionFactory $descriptionFactory) + public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory) { - $this->typeFactory = $typeFactory; $this->descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; } - public function create(PhpDocTagNode $node, ?Context $context): Tag + 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), + $this->typeResolver->createType($tagValue->type, $context), $tagValue->isVariadic, $this->descriptionFactory->create($tagValue->description, $context), $tagValue->isReference ); } - public function supports(PhpDocTagNode $node, ?Context $context): bool + public function supports(PhpDocTagNode $node, Context $context): bool { return $node->value instanceof ParamTagValueNode; } diff --git a/src/DocBlock/Tags/Factory/PropertyFactory.php b/src/DocBlock/Tags/Factory/PropertyFactory.php index 9e7ec668..83a18e8a 100644 --- a/src/DocBlock/Tags/Factory/PropertyFactory.php +++ b/src/DocBlock/Tags/Factory/PropertyFactory.php @@ -7,6 +7,7 @@ use phpDocumentor\Reflection\DocBlock\DescriptionFactory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Tags\Property; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Context; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode; @@ -19,28 +20,28 @@ */ final class PropertyFactory implements PHPStanFactory { - private TypeFactory $typeFactory; private DescriptionFactory $descriptionFactory; + private TypeResolver $typeResolver; - public function __construct(TypeFactory $typeFactory, DescriptionFactory $descriptionFactory) + public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory) { - $this->typeFactory = $typeFactory; $this->descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; } - public function create(PhpDocTagNode $node, ?Context $context): Tag + public function create(PhpDocTagNode $node, Context $context): Tag { $tagValue = $node->value; Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); return new Property( trim($tagValue->propertyName, '$'), - $this->typeFactory->createType($tagValue->type, $context), + $this->typeResolver->createType($tagValue->type, $context), $this->descriptionFactory->create($tagValue->description, $context) ); } - public function supports(PhpDocTagNode $node, ?Context $context): bool + public function supports(PhpDocTagNode $node, Context $context): bool { return $node->value instanceof PropertyTagValueNode && $node->name === '@property'; } diff --git a/src/DocBlock/Tags/Factory/PropertyReadFactory.php b/src/DocBlock/Tags/Factory/PropertyReadFactory.php index 31734289..be443ab6 100644 --- a/src/DocBlock/Tags/Factory/PropertyReadFactory.php +++ b/src/DocBlock/Tags/Factory/PropertyReadFactory.php @@ -7,6 +7,7 @@ use phpDocumentor\Reflection\DocBlock\DescriptionFactory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Tags\PropertyRead; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Context; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode; @@ -19,28 +20,28 @@ */ final class PropertyReadFactory implements PHPStanFactory { - private TypeFactory $typeFactory; private DescriptionFactory $descriptionFactory; + private TypeResolver $typeResolver; - public function __construct(TypeFactory $typeFactory, DescriptionFactory $descriptionFactory) + public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory) { - $this->typeFactory = $typeFactory; + $this->typeResolver = $typeResolver; $this->descriptionFactory = $descriptionFactory; } - public function create(PhpDocTagNode $node, ?Context $context): Tag + public function create(PhpDocTagNode $node, Context $context): Tag { $tagValue = $node->value; Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); return new PropertyRead( trim($tagValue->propertyName, '$'), - $this->typeFactory->createType($tagValue->type, $context), + $this->typeResolver->createType($tagValue->type, $context), $this->descriptionFactory->create($tagValue->description, $context) ); } - public function supports(PhpDocTagNode $node, ?Context $context): bool + public function supports(PhpDocTagNode $node, Context $context): bool { return $node->value instanceof PropertyTagValueNode && $node->name === '@property-read'; } diff --git a/src/DocBlock/Tags/Factory/PropertyWriteFactory.php b/src/DocBlock/Tags/Factory/PropertyWriteFactory.php index 0b6ba42a..08cf7534 100644 --- a/src/DocBlock/Tags/Factory/PropertyWriteFactory.php +++ b/src/DocBlock/Tags/Factory/PropertyWriteFactory.php @@ -7,6 +7,7 @@ use phpDocumentor\Reflection\DocBlock\DescriptionFactory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Context; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode; @@ -19,28 +20,28 @@ */ final class PropertyWriteFactory implements PHPStanFactory { - private TypeFactory $typeFactory; private DescriptionFactory $descriptionFactory; + private TypeResolver $typeResolver; - public function __construct(TypeFactory $typeFactory, DescriptionFactory $descriptionFactory) + public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory) { - $this->typeFactory = $typeFactory; $this->descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; } - public function create(PhpDocTagNode $node, ?Context $context): Tag + public function create(PhpDocTagNode $node, Context $context): Tag { $tagValue = $node->value; Assert::isInstanceOf($tagValue, PropertyTagValueNode::class); return new PropertyWrite( trim($tagValue->propertyName, '$'), - $this->typeFactory->createType($tagValue->type, $context), + $this->typeResolver->createType($tagValue->type, $context), $this->descriptionFactory->create($tagValue->description, $context) ); } - public function supports(PhpDocTagNode $node, ?Context $context): bool + public function supports(PhpDocTagNode $node, Context $context): bool { return $node->value instanceof PropertyTagValueNode && $node->name === '@property-write'; } diff --git a/src/DocBlock/Tags/Factory/ReturnFactory.php b/src/DocBlock/Tags/Factory/ReturnFactory.php index f01a1119..8ebe5ad4 100644 --- a/src/DocBlock/Tags/Factory/ReturnFactory.php +++ b/src/DocBlock/Tags/Factory/ReturnFactory.php @@ -7,6 +7,7 @@ use phpDocumentor\Reflection\DocBlock\DescriptionFactory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Tags\Return_; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Context; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; @@ -17,27 +18,27 @@ */ final class ReturnFactory implements PHPStanFactory { - private TypeFactory $typeFactory; private DescriptionFactory $descriptionFactory; + private TypeResolver $typeResolver; - public function __construct(TypeFactory $typeFactory, DescriptionFactory $descriptionFactory) + public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory) { - $this->typeFactory = $typeFactory; $this->descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; } - public function create(PhpDocTagNode $node, ?Context $context): Tag + public function create(PhpDocTagNode $node, Context $context): Tag { $tagValue = $node->value; Assert::isInstanceOf($tagValue, ReturnTagValueNode::class); return new Return_( - $this->typeFactory->createType($tagValue->type, $context), + $this->typeResolver->createType($tagValue->type, $context), $this->descriptionFactory->create($tagValue->description, $context) ); } - public function supports(PhpDocTagNode $node, ?Context $context): bool + public function supports(PhpDocTagNode $node, Context $context): bool { return $node->value instanceof ReturnTagValueNode; } diff --git a/src/DocBlock/Tags/Factory/TypeFactory.php b/src/DocBlock/Tags/Factory/TypeFactory.php deleted file mode 100644 index 5745d992..00000000 --- a/src/DocBlock/Tags/Factory/TypeFactory.php +++ /dev/null @@ -1,230 +0,0 @@ -resolver = $resolver; - $this->fqsenResolver = $fqsenResolver; - } - - public function createType(?TypeNode $type, ?Context $context): ?Type - { - if ($type === null) { - return null; - } - - 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: - return $this->createFromConst($type, $context); - - 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_filter( - array_map( - fn (TypeNode $nestedType) => $this->createType($nestedType, $context), - $type->types - ) - ) - ); - - case NullableTypeNode::class: - $nestedType = $this->createType($type->type, $context); - if ($nestedType === null) { - return null; - } - - return new Nullable($nestedType); - - case UnionTypeNode::class: - return new Compound( - array_filter( - 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->fqsenResolver->resolve((string) $type->genericTypes[0], $context) - ); - - case 'interface-string': - return new InterfaceString( - $this->fqsenResolver->resolve((string) $type->genericTypes[0], $context) - ); - - 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_( - array_map( - function (CallableTypeParameterNode $param) use ($context) { - return new CallableParameter( - $param->parameterName !== '' ? trim($param->parameterName, '$') : null, - $this->createType($param->type, $context), - $param->isReference, - $param->isVariadic, - $param->isOptional - ); - }, - $type->parameters - ), - $this->createType($type->returnType, $context), - ); - } - - private function createFromConst(ConstTypeNode $type, ?Context $context): ?Type - { - switch (get_class($type->constExpr)) { - case ConstExprIntegerNode::class: - return new IntegerValue((int) $type->constExpr->value); - - case ConstExprFloatNode::class: - return new FloatValue((float) $type->constExpr->value); - - case ConstExprStringNode::class: - return new StringValue($type->constExpr->value); - - case ConstFetchNode::class: - return new ConstExpression( - $this->fqsenResolver->resolve($type->constExpr->className, $context), - $type->constExpr->name - ); - } - } -} diff --git a/src/DocBlock/Tags/Factory/VarFactory.php b/src/DocBlock/Tags/Factory/VarFactory.php index 1af6f76e..79ffdf12 100644 --- a/src/DocBlock/Tags/Factory/VarFactory.php +++ b/src/DocBlock/Tags/Factory/VarFactory.php @@ -7,6 +7,7 @@ use phpDocumentor\Reflection\DocBlock\DescriptionFactory; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Tags\Var_; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Context; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; @@ -19,28 +20,28 @@ */ final class VarFactory implements PHPStanFactory { - private TypeFactory $typeFactory; private DescriptionFactory $descriptionFactory; + private TypeResolver $typeResolver; - public function __construct(TypeFactory $typeFactory, DescriptionFactory $descriptionFactory) + public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory) { - $this->typeFactory = $typeFactory; $this->descriptionFactory = $descriptionFactory; + $this->typeResolver = $typeResolver; } - public function create(PhpDocTagNode $node, ?Context $context): Tag + public function create(PhpDocTagNode $node, Context $context): Tag { $tagValue = $node->value; Assert::isInstanceOf($tagValue, VarTagValueNode::class); return new Var_( trim($tagValue->variableName, '$'), - $this->typeFactory->createType($tagValue->type, $context), + $this->typeResolver->createType($tagValue->type, $context), $this->descriptionFactory->create($tagValue->description, $context) ); } - public function supports(PhpDocTagNode $node, ?Context $context): bool + public function supports(PhpDocTagNode $node, Context $context): bool { return $node->value instanceof VarTagValueNode; } diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index 8610dbb0..102d220d 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -70,15 +70,14 @@ public static function createInstance(array $additionalTags = []): self $tagFactory = new StandardTagFactory($fqsenResolver); $descriptionFactory = new DescriptionFactory($tagFactory); $typeResolver = new TypeResolver($fqsenResolver); - $typeFactory = new TypeFactory($typeResolver, $fqsenResolver); $phpstanTagFactory = new AbstractPHPStanFactory( - new ParamFactory($typeFactory, $descriptionFactory), - new VarFactory($typeFactory, $descriptionFactory), - new ReturnFactory($typeFactory, $descriptionFactory), - new PropertyFactory($typeFactory, $descriptionFactory), - new PropertyReadFactory($typeFactory, $descriptionFactory), - new PropertyWriteFactory($typeFactory, $descriptionFactory), + new ParamFactory($typeResolver, $descriptionFactory), + new VarFactory($typeResolver, $descriptionFactory), + new ReturnFactory($typeResolver, $descriptionFactory), + new PropertyFactory($typeResolver, $descriptionFactory), + new PropertyReadFactory($typeResolver, $descriptionFactory), + new PropertyWriteFactory($typeResolver, $descriptionFactory), ); $tagFactory->addService($descriptionFactory); diff --git a/src/PseudoTypes/ArrayShape.php b/src/PseudoTypes/ArrayShape.php deleted file mode 100644 index 93380a3a..00000000 --- a/src/PseudoTypes/ArrayShape.php +++ /dev/null @@ -1,34 +0,0 @@ -items = $items; - } - - public function underlyingType(): Type - { - return new Array_(new Mixed_(), new ArrayKey()); - } - - public function __toString(): string - { - return 'array{' . implode(', ', $this->items) . '}'; - } -} diff --git a/src/PseudoTypes/ArrayShapeItem.php b/src/PseudoTypes/ArrayShapeItem.php deleted file mode 100644 index 62317820..00000000 --- a/src/PseudoTypes/ArrayShapeItem.php +++ /dev/null @@ -1,53 +0,0 @@ -key = $key; - $this->value = $value ?? new Mixed_(); - $this->optional = $optional; - } - - public function getKey(): ?string - { - return $this->key; - } - - public function getValue(): Type - { - return $this->value; - } - - public function isOptional(): bool - { - return $this->optional; - } - - public function __toString(): string - { - if ($this->key !== null) { - return sprintf( - '%s%s: %s', - $this->key, - $this->optional ? '?' : '', - $this->value - ); - } - - return (string) $this->value; - } -} diff --git a/src/PseudoTypes/ConstExpression.php b/src/PseudoTypes/ConstExpression.php deleted file mode 100644 index b7189cec..00000000 --- a/src/PseudoTypes/ConstExpression.php +++ /dev/null @@ -1,44 +0,0 @@ -owner = $owner; - $this->expression = $expression; - } - - public function getOwner(): Fqsen - { - return $this->owner; - } - - public function getExpression(): string - { - return $this->expression; - } - - public function underlyingType(): Type - { - return new Mixed_(); - } - - public function __toString(): string - { - return sprintf('%s::%s', $this->owner, $this->expression); - } -} diff --git a/src/PseudoTypes/FloatValue.php b/src/PseudoTypes/FloatValue.php deleted file mode 100644 index 07e0156c..00000000 --- a/src/PseudoTypes/FloatValue.php +++ /dev/null @@ -1,34 +0,0 @@ -value = $value; - } - - public function getValue(): float - { - return $this->value; - } - - public function underlyingType(): Type - { - return new Float_(); - } - - public function __toString(): string - { - return (string) $this->value; - } -} diff --git a/src/PseudoTypes/IntegerValue.php b/src/PseudoTypes/IntegerValue.php deleted file mode 100644 index 2a56ad4d..00000000 --- a/src/PseudoTypes/IntegerValue.php +++ /dev/null @@ -1,34 +0,0 @@ -value = $value; - } - - public function getValue(): int - { - return $this->value; - } - - public function underlyingType(): Type - { - return new Integer(); - } - - public function __toString(): string - { - return (string) $this->value; - } -} diff --git a/src/PseudoTypes/StringValue.php b/src/PseudoTypes/StringValue.php deleted file mode 100644 index f4909cd3..00000000 --- a/src/PseudoTypes/StringValue.php +++ /dev/null @@ -1,36 +0,0 @@ -value = $value; - } - - public function getValue(): string - { - return $this->value; - } - - public function underlyingType(): Type - { - return new Float_(); - } - - public function __toString(): string - { - return sprintf('"%s"', $this->value); - } -} diff --git a/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php index 17ac469b..a8ab6ea3 100644 --- a/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php @@ -33,7 +33,7 @@ final class MethodFactoryTest extends TagFactoryTestCase public function testIsCreated(string $tagLine, Method $tag): void { $ast = $this->parseTag($tagLine); - $factory = new MethodFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $factory = new MethodFactory($this->giveTypeResolver(), $this->givenDescriptionFactory()); $context = new Context('global'); self::assertTrue($factory->supports($ast, $context)); @@ -76,7 +76,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Void_(), + new Mixed_(), false, new Description(''), false, @@ -88,7 +88,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Void_(), + new Mixed_(), false, new Description(''), false, @@ -100,7 +100,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Void_(), + new Mixed_(), false, new Description(''), false, @@ -112,7 +112,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Void_(), + new Mixed_(), false, new Description(''), false, @@ -124,7 +124,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Void_(), + new Mixed_(), false, new Description(''), false, @@ -136,7 +136,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Void_(), + new Mixed_(), false, new Description(''), false, diff --git a/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php index 86b0f6f3..df2c23cd 100644 --- a/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php @@ -28,7 +28,7 @@ final class ParamFactoryTest extends TagFactoryTestCase public function testParamIsCreated(): void { $ast = $this->parseTag('@param string $var'); - $factory = new ParamFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $factory = new ParamFactory($this->giveTypeResolver(), $this->givenDescriptionFactory()); $context = new Context('global'); self::assertTrue($factory->supports($ast, $context)); diff --git a/tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php index c1ae0cda..738275cb 100644 --- a/tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php @@ -28,7 +28,7 @@ final class PropertyFactoryTest extends TagFactoryTestCase public function testParamIsCreated(): void { $ast = $this->parseTag('@property string $var'); - $factory = new PropertyFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $factory = new PropertyFactory($this->giveTypeResolver(), $this->givenDescriptionFactory()); $context = new Context('global'); self::assertTrue($factory->supports($ast, $context)); diff --git a/tests/unit/DocBlock/Tags/Factory/PropertyReadFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/PropertyReadFactoryTest.php index 2e1f4cdf..40fd1226 100644 --- a/tests/unit/DocBlock/Tags/Factory/PropertyReadFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/PropertyReadFactoryTest.php @@ -28,7 +28,7 @@ final class PropertyReadFactoryTest extends TagFactoryTestCase public function testParamIsCreated(): void { $ast = $this->parseTag('@property-read string $var'); - $factory = new PropertyReadFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $factory = new PropertyReadFactory($this->giveTypeResolver(), $this->givenDescriptionFactory()); $context = new Context('global'); self::assertTrue($factory->supports($ast, $context)); diff --git a/tests/unit/DocBlock/Tags/Factory/PropertyWriteFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/PropertyWriteFactoryTest.php index af6e9034..0f3e239d 100644 --- a/tests/unit/DocBlock/Tags/Factory/PropertyWriteFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/PropertyWriteFactoryTest.php @@ -28,7 +28,7 @@ final class PropertyWriteFactoryTest extends TagFactoryTestCase public function testParamIsCreated(): void { $ast = $this->parseTag('@property-write string $var'); - $factory = new PropertyWriteFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $factory = new PropertyWriteFactory($this->giveTypeResolver(), $this->givenDescriptionFactory()); $context = new Context('global'); self::assertTrue($factory->supports($ast, $context)); diff --git a/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php index 95d1e143..5b664cc3 100644 --- a/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php @@ -28,7 +28,7 @@ final class ReturnFactoryTest extends TagFactoryTestCase public function testParamIsCreated(): void { $ast = $this->parseTag('@return string'); - $factory = new ReturnFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $factory = new ReturnFactory($this->giveTypeResolver(), $this->givenDescriptionFactory()); $context = new Context('global'); self::assertTrue($factory->supports($ast, $context)); diff --git a/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php index 4b2c7bea..8684ba51 100644 --- a/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php +++ b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php @@ -37,9 +37,9 @@ public function parseTag(string $tag): PhpDocTagNode return (new PhpDocParser(new TypeParser($constParser), $constParser))->parseTag(new TokenIterator($tokens)); } - public function giveTypeFactory(): TypeFactory + public function giveTypeResolver(): TypeResolver { - return new TypeFactory(new TypeResolver(new FqsenResolver()), new FqsenResolver()); + return new TypeResolver(new FqsenResolver()); } public function givenDescriptionFactory(): DescriptionFactory diff --git a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php deleted file mode 100644 index bb5a126c..00000000 --- a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php +++ /dev/null @@ -1,291 +0,0 @@ -tokenize($type); - $constParser = new ConstExprParser(); - $parser = new TypeParser($constParser); - $ast = $parser->parse(new TokenIterator($tokens)); - $fqsenResolver = new FqsenResolver(); - - $factory = new TypeFactory(new TypeResolver($fqsenResolver), $fqsenResolver); - $actual = $factory->createType($ast, new Context('phpDocumentor')); - - self::assertEquals($expected, $actual); - } - - /** - * @return array - */ - 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_(), - ], - ]; - } - - /** - * @return array - */ - 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'), - ], - ]; - } - - /** - * @return array - */ - public function callableProvider(): array - { - return [ - [ - 'callable', - new Callable_(), - ], - [ - 'callable()', - new Callable_(), - ], - [ - 'callable(): Foo', - new Callable_([], new Object_(new Fqsen('\\phpDocumentor\\Foo'))), - ], - [ - 'callable(): (Foo&Bar)', - new Callable_( - [], - new Intersection( - [ - new Object_(new Fqsen('\\phpDocumentor\\Foo')), - new Object_(new Fqsen('\\phpDocumentor\\Bar')) - ] - ) - ), - ], - [ - 'callable(A&...$a=, B&...=, C): Foo', - new Callable_( - [ - new CallableParameter( - 'a', - new Object_(new Fqsen('\\phpDocumentor\\A')), - true, - true, - true - ), - new CallableParameter( - null, - new Object_(new Fqsen('\\phpDocumentor\\B')), - true, - true, - true - ), - new CallableParameter( - null, - new Object_(new Fqsen('\\phpDocumentor\\C')), - false, - false, - false - ), - ], - new Object_(new Fqsen('\\phpDocumentor\\Foo') - ) - ), - ], - ]; - } - - /** - * @return array - */ - public function constExpressions(): array - { - return [ - [ - '123', - new IntegerValue(123), - ], - [ - 'true', - new True_(), - ], - [ - '123.2', - new FloatValue(123.2), - ], - [ - '"bar"', - new StringValue('bar'), - ], - [ - 'Foo::FOO_CONSTANT', - new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_CONSTANT'), - ], - [ - 'Foo::FOO_*', - new ConstExpression(new Fqsen('\\phpDocumentor\\Foo'), 'FOO_*'), - ], - ]; - } -} diff --git a/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php index a37193b5..06befbb9 100644 --- a/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php @@ -28,7 +28,7 @@ final class VarFactoryTest extends TagFactoryTestCase public function testParamIsCreated(): void { $ast = $this->parseTag('@var string $var'); - $factory = new VarFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $factory = new VarFactory($this->giveTypeResolver(), $this->givenDescriptionFactory()); $context = new Context('global'); self::assertTrue($factory->supports($ast, $context)); From 1f95f3b7359ce38c011cffa036a4a785d55d21f2 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 11 Nov 2022 14:24:45 +0100 Subject: [PATCH 16/16] Codestyle fixes and static analysis --- src/DocBlock/StandardTagFactory.php | 2 +- src/DocBlock/Tags/Factory/MethodFactory.php | 11 +++++++++-- src/DocBlock/Tags/Method.php | 8 +++++++- src/DocBlock/Tags/Param.php | 9 +++++++-- src/DocBlock/Tags/Property.php | 10 +++++++++- src/DocBlock/Tags/PropertyRead.php | 10 +++++++++- src/DocBlock/Tags/PropertyWrite.php | 10 +++++++++- src/DocBlock/Tags/Return_.php | 10 +++++++++- src/DocBlock/Tags/Var_.php | 10 +++++++++- src/DocBlockFactory.php | 3 +-- .../unit/DocBlock/Tags/Factory/MethodFactoryTest.php | 12 ++++++------ 11 files changed, 76 insertions(+), 19 deletions(-) diff --git a/src/DocBlock/StandardTagFactory.php b/src/DocBlock/StandardTagFactory.php index fa724aa4..e5c071c1 100644 --- a/src/DocBlock/StandardTagFactory.php +++ b/src/DocBlock/StandardTagFactory.php @@ -177,7 +177,7 @@ public function registerTagHandler(string $tagName, $handler): void } if (is_object($handler)) { - Assert::implementsInterface($handler, Factory::class); + Assert::isInstanceOf($handler, Factory::class); $this->tagHandlerMappings[$tagName] = $handler; return; diff --git a/src/DocBlock/Tags/Factory/MethodFactory.php b/src/DocBlock/Tags/Factory/MethodFactory.php index c884743f..920be845 100644 --- a/src/DocBlock/Tags/Factory/MethodFactory.php +++ b/src/DocBlock/Tags/Factory/MethodFactory.php @@ -51,7 +51,10 @@ public function create(PhpDocTagNode $node, Context $context): Tag function (MethodTagValueParameterNode $param) use ($context) { return new MethodParameter( trim($param->parameterName, '$'), - $this->typeResolver->createType($param->type, $context) ?? new Mixed_(), + $param->type === null ? new Mixed_() : $this->typeResolver->createType( + $param->type, + $context + ), $param->isReference, $param->isVariadic, (string) $param->defaultValue @@ -69,6 +72,10 @@ public function supports(PhpDocTagNode $node, Context $context): bool private function createReturnType(MethodTagValueNode $tagValue, Context $context): Type { - return $this->typeResolver->createType($tagValue->returnType, $context) ?? new Void_(); + if ($tagValue->returnType === null) { + return new Void_(); + } + + return $this->typeResolver->createType($tagValue->returnType, $context); } } diff --git a/src/DocBlock/Tags/Method.php b/src/DocBlock/Tags/Method.php index bbb20620..02aa2b47 100644 --- a/src/DocBlock/Tags/Method.php +++ b/src/DocBlock/Tags/Method.php @@ -63,6 +63,7 @@ final class Method extends BaseTag implements Factory\StaticMethod /** * @param array> $arguments + * @param MethodParameter[] $parameters * @phpstan-param array $arguments */ public function __construct( @@ -90,6 +91,10 @@ public function __construct( $this->parameters = $parameters ?? $this->fromLegacyArguments($arguments); } + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ public static function create( string $body, ?TypeResolver $typeResolver = null, @@ -261,7 +266,7 @@ public function __toString(): string { $arguments = []; foreach ($this->parameters as $parameter) { - $arguments[] = ($parameter->getType() ?? new Mixed_()) . ' ' . + $arguments[] = $parameter->getType() . ' ' . ($parameter->isReference() ? '&' : '') . ($parameter->isVariadic() ? '...' : '') . '$' . $parameter->getName(); @@ -334,6 +339,7 @@ private static function stripRestArg(string $argument): string /** * @param array{name: string, type: Type} $arguments + * @phpstan-param array $arguments * * @return MethodParameter[] */ diff --git a/src/DocBlock/Tags/Param.php b/src/DocBlock/Tags/Param.php index c4c08808..cb14abeb 100644 --- a/src/DocBlock/Tags/Param.php +++ b/src/DocBlock/Tags/Param.php @@ -61,7 +61,8 @@ public function __construct( } /** - * @deprecated Create using static factory is deprecated, this method should not be called directly by library consumers + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers */ public static function create( string $body, @@ -69,7 +70,11 @@ public static function create( ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { - trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); + trigger_error( + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + E_USER_DEPRECATED + ); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/Property.php b/src/DocBlock/Tags/Property.php index 429e7a67..b3d9df42 100644 --- a/src/DocBlock/Tags/Property.php +++ b/src/DocBlock/Tags/Property.php @@ -49,13 +49,21 @@ public function __construct(?string $variableName, ?Type $type = null, ?Descript $this->description = $description; } + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ public static function create( string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { - trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); + trigger_error( + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + E_USER_DEPRECATED + ); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/PropertyRead.php b/src/DocBlock/Tags/PropertyRead.php index 150d4513..ccffde63 100644 --- a/src/DocBlock/Tags/PropertyRead.php +++ b/src/DocBlock/Tags/PropertyRead.php @@ -49,13 +49,21 @@ public function __construct(?string $variableName, ?Type $type = null, ?Descript $this->description = $description; } + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ public static function create( string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { - trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); + trigger_error( + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + E_USER_DEPRECATED + ); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/PropertyWrite.php b/src/DocBlock/Tags/PropertyWrite.php index debbb45f..35dac201 100644 --- a/src/DocBlock/Tags/PropertyWrite.php +++ b/src/DocBlock/Tags/PropertyWrite.php @@ -49,13 +49,21 @@ public function __construct(?string $variableName, ?Type $type = null, ?Descript $this->description = $description; } + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ public static function create( string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { - trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); + trigger_error( + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + E_USER_DEPRECATED + ); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/Return_.php b/src/DocBlock/Tags/Return_.php index af3a24a5..2a1efb73 100644 --- a/src/DocBlock/Tags/Return_.php +++ b/src/DocBlock/Tags/Return_.php @@ -36,13 +36,21 @@ public function __construct(Type $type, ?Description $description = null) $this->description = $description; } + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ public static function create( string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { - trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); + trigger_error( + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + E_USER_DEPRECATED + ); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlock/Tags/Var_.php b/src/DocBlock/Tags/Var_.php index c523612e..d8e75b99 100644 --- a/src/DocBlock/Tags/Var_.php +++ b/src/DocBlock/Tags/Var_.php @@ -49,13 +49,21 @@ public function __construct(?string $variableName, ?Type $type = null, ?Descript $this->description = $description; } + /** + * @deprecated Create using static factory is deprecated, + * this method should not be called directly by library consumers + */ public static function create( string $body, ?TypeResolver $typeResolver = null, ?DescriptionFactory $descriptionFactory = null, ?TypeContext $context = null ): self { - trigger_error('Create using static factory is deprecated, this method should not be called directly by library consumers', E_USER_DEPRECATED); + trigger_error( + 'Create using static factory is deprecated, this method should not be called directly + by library consumers', + E_USER_DEPRECATED + ); Assert::stringNotEmpty($body); Assert::notNull($typeResolver); Assert::notNull($descriptionFactory); diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index 102d220d..46eb87f9 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -26,7 +26,6 @@ use phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyReadFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyWriteFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\ReturnFactory; -use phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory; use phpDocumentor\Reflection\DocBlock\Tags\Factory\VarFactory; use Webmozart\Assert\Assert; @@ -64,7 +63,7 @@ public function __construct(DescriptionFactory $descriptionFactory, TagFactory $ * * @param array|Factory> $additionalTags */ - public static function createInstance(array $additionalTags = []): self + public static function createInstance(array $additionalTags = []): DocBlockFactoryInterface { $fqsenResolver = new FqsenResolver(); $tagFactory = new StandardTagFactory($fqsenResolver); diff --git a/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php index a8ab6ea3..2daad8b3 100644 --- a/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php @@ -76,7 +76,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Mixed_(), + new Void_(), false, new Description(''), false, @@ -88,7 +88,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Mixed_(), + new Void_(), false, new Description(''), false, @@ -100,7 +100,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Mixed_(), + new Void_(), false, new Description(''), false, @@ -112,7 +112,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Mixed_(), + new Void_(), false, new Description(''), false, @@ -124,7 +124,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Mixed_(), + new Void_(), false, new Description(''), false, @@ -136,7 +136,7 @@ public function tagProvider(): array new Method( 'myMethod', [], - new Mixed_(), + new Void_(), false, new Description(''), false,