diff --git a/composer.json b/composer.json
index 7d76f16e..c4348f1f 100644
--- a/composer.json
+++ b/composer.json
@@ -15,10 +15,11 @@
],
"require": {
"php": "^7.2 || ^8.0",
- "phpdocumentor/type-resolver": "^1.3",
+ "phpdocumentor/type-resolver": "1.x-dev@dev",
"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..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": "cbf52dda9a68fb6e5d4da2511d6b4c0d",
+ "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,9 +113,54 @@
"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-11-11T10:44:03+00:00"
+ },
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "1.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "33aefcdab42900e36366d0feab6206e2dd68f947"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/33aefcdab42900e36366d0feab6206e2dd68f947",
+ "reference": "33aefcdab42900e36366d0feab6206e2dd68f947",
+ "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.13.0"
},
- "time": "2022-03-15T21:29:03+00:00"
+ "time": "2022-10-21T09:57:39+00:00"
},
{
"name": "webmozart/assert",
@@ -4004,7 +4057,9 @@
],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {
+ "phpdocumentor/type-resolver": 20
+ },
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
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/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/DescriptionFactory.php b/src/DocBlock/DescriptionFactory.php
index 1a519ec4..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 TagFactory */
+ /** @var Factory */
private $tagFactory;
/**
* Initializes this factory with the means to construct (inline) tags.
*/
- public function __construct(TagFactory $tagFactory)
+ public function __construct(Factory $tagFactory)
{
$this->tagFactory = $tagFactory;
}
diff --git a/src/DocBlock/StandardTagFactory.php b/src/DocBlock/StandardTagFactory.php
index 8d765951..e5c071c1 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;
@@ -40,12 +41,15 @@
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;
use function count;
use function get_class;
+use function is_object;
use function preg_match;
+use function sprintf;
use function strpos;
use function trim;
@@ -72,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 = [
@@ -162,18 +166,25 @@ public function addService(object $service, ?string $alias = null): void
$this->serviceLocator[$alias ?: get_class($service)] = $service;
}
- public function registerTagHandler(string $tagName, string $handler): void
+ /** {@inheritDoc} */
+ 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::isInstanceOf($handler, Factory::class);
+ $this->tagHandlerMappings[$tagName] = $handler;
+
+ return;
+ }
+
+ Assert::classExists($handler);
+ Assert::implementsInterface($handler, Tag::class);
$this->tagHandlerMappings[$tagName] = $handler;
}
@@ -210,6 +221,10 @@ private function createTag(string $body, string $name, TypeContext $context): Ta
$this->getServiceLocatorWithDynamicParameters($context, $name, $body)
);
+ if (array_key_exists('tagLine', $arguments)) {
+ $arguments['tagLine'] = sprintf('@%s %s', $name, $body);
+ }
+
try {
$callable = [$handlerClassName, 'create'];
Assert::isCallable($callable);
@@ -225,9 +240,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|Factory
*/
- private function findHandlerClassName(string $tagName, TypeContext $context): string
+ private function findHandlerClassName(string $tagName, TypeContext $context)
{
$handlerClassName = Generic::class;
if (isset($this->tagHandlerMappings[$tagName])) {
@@ -268,18 +283,18 @@ 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[] = $locator[$parameterName];
+ $arguments[$parameterName] = $locator[$parameterName];
continue;
}
- $arguments[] = null;
+ $arguments[$parameterName] = null;
}
return $arguments;
@@ -289,12 +304,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|Factory $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/TagFactory.php b/src/DocBlock/TagFactory.php
index c0868dcb..a6f1ae8d 100644
--- a/src/DocBlock/TagFactory.php
+++ b/src/DocBlock/TagFactory.php
@@ -14,9 +14,9 @@
namespace phpDocumentor\Reflection\DocBlock;
use InvalidArgumentException;
-use phpDocumentor\Reflection\Types\Context as TypeContext;
+use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory;
-interface TagFactory
+interface TagFactory extends Factory
{
/**
* 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.
*
@@ -71,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
@@ -80,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
new file mode 100644
index 00000000..7d96d314
--- /dev/null
+++ b/src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php
@@ -0,0 +1,68 @@
+lexer = new Lexer();
+ $constParser = new ConstExprParser();
+ $this->parser = new PhpDocParser(new TypeParser($constParser), $constParser);
+ $this->factories = $factories;
+ }
+
+ 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);
+ }
+ }
+
+ return InvalidTag::create(
+ $ast->name,
+ (string) $ast->value
+ );
+ }
+}
diff --git a/src/DocBlock/Tags/Factory/Factory.php b/src/DocBlock/Tags/Factory/Factory.php
new file mode 100644
index 00000000..190d3ff8
--- /dev/null
+++ b/src/DocBlock/Tags/Factory/Factory.php
@@ -0,0 +1,41 @@
+descriptionFactory = $descriptionFactory;
+ $this->typeResolver = $typeResolver;
+ }
+
+ 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, '$'),
+ $param->type === null ? new Mixed_() : $this->typeResolver->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
+ {
+ if ($tagValue->returnType === null) {
+ return new Void_();
+ }
+
+ return $this->typeResolver->createType($tagValue->returnType, $context);
+ }
+}
diff --git a/src/DocBlock/Tags/Factory/PHPStanFactory.php b/src/DocBlock/Tags/Factory/PHPStanFactory.php
new file mode 100644
index 00000000..cf04a06e
--- /dev/null
+++ b/src/DocBlock/Tags/Factory/PHPStanFactory.php
@@ -0,0 +1,16 @@
+descriptionFactory = $descriptionFactory;
+ $this->typeResolver = $typeResolver;
+ }
+
+ public function create(PhpDocTagNode $node, Context $context): Tag
+ {
+ $tagValue = $node->value;
+ Assert::isInstanceOf($tagValue, ParamTagValueNode::class);
+
+ return new Param(
+ trim($tagValue->parameterName, '$'),
+ $this->typeResolver->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/PropertyFactory.php b/src/DocBlock/Tags/Factory/PropertyFactory.php
new file mode 100644
index 00000000..83a18e8a
--- /dev/null
+++ b/src/DocBlock/Tags/Factory/PropertyFactory.php
@@ -0,0 +1,48 @@
+descriptionFactory = $descriptionFactory;
+ $this->typeResolver = $typeResolver;
+ }
+
+ public function create(PhpDocTagNode $node, Context $context): Tag
+ {
+ $tagValue = $node->value;
+ Assert::isInstanceOf($tagValue, PropertyTagValueNode::class);
+
+ return new Property(
+ trim($tagValue->propertyName, '$'),
+ $this->typeResolver->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..be443ab6
--- /dev/null
+++ b/src/DocBlock/Tags/Factory/PropertyReadFactory.php
@@ -0,0 +1,48 @@
+typeResolver = $typeResolver;
+ $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->typeResolver->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..08cf7534
--- /dev/null
+++ b/src/DocBlock/Tags/Factory/PropertyWriteFactory.php
@@ -0,0 +1,48 @@
+descriptionFactory = $descriptionFactory;
+ $this->typeResolver = $typeResolver;
+ }
+
+ public function create(PhpDocTagNode $node, Context $context): Tag
+ {
+ $tagValue = $node->value;
+ Assert::isInstanceOf($tagValue, PropertyTagValueNode::class);
+
+ return new PropertyWrite(
+ trim($tagValue->propertyName, '$'),
+ $this->typeResolver->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/DocBlock/Tags/Factory/ReturnFactory.php b/src/DocBlock/Tags/Factory/ReturnFactory.php
new file mode 100644
index 00000000..8ebe5ad4
--- /dev/null
+++ b/src/DocBlock/Tags/Factory/ReturnFactory.php
@@ -0,0 +1,45 @@
+descriptionFactory = $descriptionFactory;
+ $this->typeResolver = $typeResolver;
+ }
+
+ public function create(PhpDocTagNode $node, Context $context): Tag
+ {
+ $tagValue = $node->value;
+ Assert::isInstanceOf($tagValue, ReturnTagValueNode::class);
+
+ return new Return_(
+ $this->typeResolver->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/VarFactory.php b/src/DocBlock/Tags/Factory/VarFactory.php
new file mode 100644
index 00000000..79ffdf12
--- /dev/null
+++ b/src/DocBlock/Tags/Factory/VarFactory.php
@@ -0,0 +1,48 @@
+descriptionFactory = $descriptionFactory;
+ $this->typeResolver = $typeResolver;
+ }
+
+ public function create(PhpDocTagNode $node, Context $context): Tag
+ {
+ $tagValue = $node->value;
+ Assert::isInstanceOf($tagValue, VarTagValueNode::class);
+
+ return new Var_(
+ trim($tagValue->variableName, '$'),
+ $this->typeResolver->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/DocBlock/Tags/Method.php b/src/DocBlock/Tags/Method.php
index 12996a42..02aa2b47 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;
@@ -31,9 +32,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.
*/
@@ -45,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;
@@ -60,8 +58,12 @@ final class Method extends BaseTag implements Factory\StaticMethod
/** @var bool */
private $returnsReference;
+ /** @var MethodParameter[] */
+ private array $parameters;
+
/**
* @param array> $arguments
+ * @param MethodParameter[] $parameters
* @phpstan-param array $arguments
*/
public function __construct(
@@ -70,7 +72,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);
@@ -78,20 +81,31 @@ public function __construct(
$returnType = new Void_();
}
+ $arguments = $this->filterArguments($arguments);
+
$this->methodName = $methodName;
- $this->arguments = $this->filterArguments($arguments);
$this->returnType = $returnType;
$this->isStatic = $static;
$this->description = $description;
$this->returnsReference = $returnsReference;
+ $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,
?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);
@@ -186,7 +200,14 @@ public static function create(
}
}
- return new static($methodName, $arguments, $returnType, $static, $description, $returnsReference);
+ return new static(
+ $methodName,
+ $arguments,
+ $returnType,
+ $static,
+ $description,
+ $returnsReference
+ );
}
/**
@@ -198,12 +219,27 @@ 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[] */
+ public function getParameters(): array
+ {
+ return $this->parameters;
}
/**
@@ -229,8 +265,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() . ' ' .
+ ($parameter->isReference() ? '&' : '') .
+ ($parameter->isVariadic() ? '...' : '') .
+ '$' . $parameter->getName();
}
$argumentStr = '(' . implode(', ', $arguments) . ')';
@@ -297,4 +336,28 @@ private static function stripRestArg(string $argument): string
return $argument;
}
+
+ /**
+ * @param array{name: string, type: Type} $arguments
+ * @phpstan-param array $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/DocBlock/Tags/Param.php b/src/DocBlock/Tags/Param.php
index 3399649b..cb14abeb 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;
/**
@@ -58,12 +60,21 @@ 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..b3d9df42 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;
/**
@@ -47,12 +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
+ );
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..ccffde63 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;
/**
@@ -47,12 +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
+ );
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..35dac201 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;
/**
@@ -47,12 +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
+ );
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..2a1efb73 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.
*/
@@ -32,12 +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
+ );
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
diff --git a/src/DocBlock/Tags/Var_.php b/src/DocBlock/Tags/Var_.php
index fa1f9dbf..d8e75b99 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;
/**
@@ -47,12 +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
+ );
Assert::stringNotEmpty($body);
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php
index 37f72dd2..46eb87f9 100644
--- a/src/DocBlockFactory.php
+++ b/src/DocBlockFactory.php
@@ -19,6 +19,14 @@
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\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\VarFactory;
use Webmozart\Assert\Assert;
use function array_shift;
@@ -38,7 +46,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface
/** @var DocBlock\DescriptionFactory */
private $descriptionFactory;
- /** @var DocBlock\TagFactory */
+ /** @var TagFactory */
private $tagFactory;
/**
@@ -47,22 +55,39 @@ 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|Factory> $additionalTags
*/
- public static function createInstance(array $additionalTags = []): self
+ public static function createInstance(array $additionalTags = []): DocBlockFactoryInterface
{
- $fqsenResolver = new FqsenResolver();
- $tagFactory = new StandardTagFactory($fqsenResolver);
+ $fqsenResolver = new FqsenResolver();
+ $tagFactory = new StandardTagFactory($fqsenResolver);
$descriptionFactory = new DescriptionFactory($tagFactory);
+ $typeResolver = new TypeResolver($fqsenResolver);
+
+ $phpstanTagFactory = new AbstractPHPStanFactory(
+ 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);
- $tagFactory->addService(new TypeResolver($fqsenResolver));
+ $tagFactory->addService($typeResolver);
+ $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);
+ $tagFactory->registerTagHandler('method', $phpstanTagFactory);
$docBlockFactory = new self($descriptionFactory, $tagFactory);
foreach ($additionalTags as $tagName => $tagHandler) {
@@ -111,9 +136,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);
}
@@ -138,6 +163,7 @@ private function stripDocComment(string $comment): string
}
// phpcs:disable
+
/**
* Splits the DocBlock into a template marker, summary, description and block of tags.
*
@@ -149,7 +175,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 +253,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 +266,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/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/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/unit/Assets/CustomTagFactory.php b/tests/unit/Assets/CustomTagFactory.php
new file mode 100644
index 00000000..df4a3f69
--- /dev/null
+++ b/tests/unit/Assets/CustomTagFactory.php
@@ -0,0 +1,31 @@
+class = $class;
+
+ return new Generic('custom');
+ }
+}
diff --git a/tests/unit/DocBlock/StandardTagFactoryTest.php b/tests/unit/DocBlock/StandardTagFactoryTest.php
index 2e380d91..5a954d52 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/MethodFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php
new file mode 100644
index 00000000..2daad8b3
--- /dev/null
+++ b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php
@@ -0,0 +1,151 @@
+parseTag($tagLine);
+ $factory = new MethodFactory($this->giveTypeResolver(), $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($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(
+ '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),
+ ]
+ ),
+ ],
+ ];
+ }
+}
diff --git a/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php
new file mode 100644
index 00000000..df2c23cd
--- /dev/null
+++ b/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php
@@ -0,0 +1,46 @@
+parseTag('@param string $var');
+ $factory = new ParamFactory($this->giveTypeResolver(), $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/PropertyFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php
new file mode 100644
index 00000000..738275cb
--- /dev/null
+++ b/tests/unit/DocBlock/Tags/Factory/PropertyFactoryTest.php
@@ -0,0 +1,44 @@
+parseTag('@property string $var');
+ $factory = new PropertyFactory($this->giveTypeResolver(), $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..40fd1226
--- /dev/null
+++ b/tests/unit/DocBlock/Tags/Factory/PropertyReadFactoryTest.php
@@ -0,0 +1,44 @@
+parseTag('@property-read string $var');
+ $factory = new PropertyReadFactory($this->giveTypeResolver(), $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..0f3e239d
--- /dev/null
+++ b/tests/unit/DocBlock/Tags/Factory/PropertyWriteFactoryTest.php
@@ -0,0 +1,44 @@
+parseTag('@property-write string $var');
+ $factory = new PropertyWriteFactory($this->giveTypeResolver(), $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
new file mode 100644
index 00000000..5b664cc3
--- /dev/null
+++ b/tests/unit/DocBlock/Tags/Factory/ReturnFactoryTest.php
@@ -0,0 +1,43 @@
+parseTag('@return string');
+ $factory = new ReturnFactory($this->giveTypeResolver(), $this->givenDescriptionFactory());
+ $context = new Context('global');
+
+ self::assertTrue($factory->supports($ast, $context));
+ self::assertEquals(
+ new Return_(
+ new String_(),
+ new Description('')
+ ),
+ $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..8684ba51
--- /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 giveTypeResolver(): TypeResolver
+ {
+ return 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/VarFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php
new file mode 100644
index 00000000..06befbb9
--- /dev/null
+++ b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php
@@ -0,0 +1,44 @@
+parseTag('@var string $var');
+ $factory = new VarFactory($this->giveTypeResolver(), $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)
+ );
+ }
+}