Merge pull request #343 from phpDocumentor/poc-phpstan-integration

PhpStan based tag parsing
This commit is contained in:
Jaap van Otterdijk
2022-11-11 15:54:50 +01:00
committed by GitHub
38 changed files with 1424 additions and 80 deletions
+3 -2
View File
@@ -15,10 +15,11 @@
], ],
"require": { "require": {
"php": "^7.2 || ^8.0", "php": "^7.2 || ^8.0",
"phpdocumentor/type-resolver": "^1.3", "phpdocumentor/type-resolver": "1.x-dev@dev",
"webmozart/assert": "^1.9.1", "webmozart/assert": "^1.9.1",
"phpdocumentor/reflection-common": "^2.2", "phpdocumentor/reflection-common": "^2.2",
"ext-filter": "*" "ext-filter": "*",
"phpstan/phpdoc-parser": "^1.7"
}, },
"require-dev": { "require-dev": {
"mockery/mockery": "~1.3.5", "mockery/mockery": "~1.3.5",
Generated
+66 -11
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "cbf52dda9a68fb6e5d4da2511d6b4c0d", "content-hash": "829061026cc6b2284953cbe816406760",
"packages": [ "packages": [
{ {
"name": "phpdocumentor/reflection-common", "name": "phpdocumentor/reflection-common",
@@ -61,26 +61,34 @@
}, },
{ {
"name": "phpdocumentor/type-resolver", "name": "phpdocumentor/type-resolver",
"version": "1.6.1", "version": "1.x-dev",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git", "url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "77a32518733312af16a44300404e945338981de3" "reference": "662b17f56786e34354684f6c141c1909b1ee3ac0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/662b17f56786e34354684f6c141c1909b1ee3ac0",
"reference": "77a32518733312af16a44300404e945338981de3", "reference": "662b17f56786e34354684f6c141c1909b1ee3ac0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2 || ^8.0", "php": "^7.4 || ^8.0",
"phpdocumentor/reflection-common": "^2.0" "phpdocumentor/reflection-common": "^2.0",
"phpstan/phpdoc-parser": "^1.13"
}, },
"require-dev": { "require-dev": {
"ext-tokenizer": "*", "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", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@@ -105,9 +113,54 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": { "support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues", "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.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-10-21T09:57:39+00:00"
}, },
{ {
"name": "webmozart/assert", "name": "webmozart/assert",
@@ -4004,7 +4057,9 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": {
"phpdocumentor/type-resolver": 20
},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
+1 -4
View File
@@ -10,13 +10,10 @@
<!-- Set the minimum PHP version for PHPCompatibility. <!-- Set the minimum PHP version for PHPCompatibility.
This should be kept in sync with the requirements in the composer.json file. --> This should be kept in sync with the requirements in the composer.json file. -->
<config name="testVersion" value="7.2-"/> <config name="testVersion" value="7.4-"/>
<rule ref="phpDocumentor"> <rule ref="phpDocumentor">
<exclude name="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException" /> <exclude name="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException" />
<!-- Property type declarations are a PHP 7.4 feature. -->
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint"/>
</rule> </rule>
<rule ref="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix"> <rule ref="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix">
+2 -2
View File
@@ -4,8 +4,8 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd"
colors="true" colors="true"
convertDeprecationsToExceptions="true" convertDeprecationsToExceptions="false"
beStrictAboutOutputDuringTests="true" beStrictAboutOutputDuringTests="false"
forceCoversAnnotation="true" forceCoversAnnotation="true"
verbose="true" verbose="true"
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
+3 -2
View File
@@ -13,6 +13,7 @@ declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock; namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory;
use phpDocumentor\Reflection\Types\Context as TypeContext; use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils; use phpDocumentor\Reflection\Utils;
@@ -47,13 +48,13 @@ use const PREG_SPLIT_DELIM_CAPTURE;
*/ */
class DescriptionFactory class DescriptionFactory
{ {
/** @var TagFactory */ /** @var Factory */
private $tagFactory; private $tagFactory;
/** /**
* Initializes this factory with the means to construct (inline) tags. * Initializes this factory with the means to construct (inline) tags.
*/ */
public function __construct(TagFactory $tagFactory) public function __construct(Factory $tagFactory)
{ {
$this->tagFactory = $tagFactory; $this->tagFactory = $tagFactory;
} }
+34 -17
View File
@@ -17,6 +17,7 @@ use InvalidArgumentException;
use phpDocumentor\Reflection\DocBlock\Tags\Author; use phpDocumentor\Reflection\DocBlock\Tags\Author;
use phpDocumentor\Reflection\DocBlock\Tags\Covers; use phpDocumentor\Reflection\DocBlock\Tags\Covers;
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated; use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory;
use phpDocumentor\Reflection\DocBlock\Tags\Generic; use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag; use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag; use phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag;
@@ -40,12 +41,15 @@ use ReflectionNamedType;
use ReflectionParameter; use ReflectionParameter;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use function array_key_exists;
use function array_merge; use function array_merge;
use function array_slice; use function array_slice;
use function call_user_func_array; use function call_user_func_array;
use function count; use function count;
use function get_class; use function get_class;
use function is_object;
use function preg_match; use function preg_match;
use function sprintf;
use function strpos; use function strpos;
use function trim; use function trim;
@@ -72,7 +76,7 @@ final class StandardTagFactory implements TagFactory
public const REGEX_TAGNAME = '[\w\-\_\\\\:]+'; public const REGEX_TAGNAME = '[\w\-\_\\\\:]+';
/** /**
* @var array<class-string<Tag>> An array with a tag as a key, and an * @var array<class-string<Tag>|Factory> An array with a tag as a key, and an
* FQCN to a class that handles it as an array value. * FQCN to a class that handles it as an array value.
*/ */
private $tagHandlerMappings = [ private $tagHandlerMappings = [
@@ -162,18 +166,25 @@ final class StandardTagFactory implements TagFactory
$this->serviceLocator[$alias ?: get_class($service)] = $service; $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::stringNotEmpty($tagName);
Assert::classExists($handler);
Assert::implementsInterface($handler, Tag::class);
if (strpos($tagName, '\\') && $tagName[0] !== '\\') { if (strpos($tagName, '\\') && $tagName[0] !== '\\') {
throw new InvalidArgumentException( throw new InvalidArgumentException(
'A namespaced tag must have a leading backslash as it must be fully qualified' '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; $this->tagHandlerMappings[$tagName] = $handler;
} }
@@ -210,6 +221,10 @@ final class StandardTagFactory implements TagFactory
$this->getServiceLocatorWithDynamicParameters($context, $name, $body) $this->getServiceLocatorWithDynamicParameters($context, $name, $body)
); );
if (array_key_exists('tagLine', $arguments)) {
$arguments['tagLine'] = sprintf('@%s %s', $name, $body);
}
try { try {
$callable = [$handlerClassName, 'create']; $callable = [$handlerClassName, 'create'];
Assert::isCallable($callable); Assert::isCallable($callable);
@@ -225,9 +240,9 @@ final class StandardTagFactory implements TagFactory
/** /**
* Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
* *
* @return class-string<Tag> * @return class-string<Tag>|Factory
*/ */
private function findHandlerClassName(string $tagName, TypeContext $context): string private function findHandlerClassName(string $tagName, TypeContext $context)
{ {
$handlerClassName = Generic::class; $handlerClassName = Generic::class;
if (isset($this->tagHandlerMappings[$tagName])) { if (isset($this->tagHandlerMappings[$tagName])) {
@@ -268,18 +283,18 @@ final class StandardTagFactory implements TagFactory
} }
} }
if (isset($locator[$typeHint])) {
$arguments[] = $locator[$typeHint];
continue;
}
$parameterName = $parameter->getName(); $parameterName = $parameter->getName();
if (isset($locator[$parameterName])) { if (isset($locator[$typeHint])) {
$arguments[] = $locator[$parameterName]; $arguments[$parameterName] = $locator[$typeHint];
continue; continue;
} }
$arguments[] = null; if (isset($locator[$parameterName])) {
$arguments[$parameterName] = $locator[$parameterName];
continue;
}
$arguments[$parameterName] = null;
} }
return $arguments; return $arguments;
@@ -289,12 +304,14 @@ final class StandardTagFactory implements TagFactory
* Retrieves a series of ReflectionParameter objects for the static 'create' method of the given * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
* tag handler class name. * tag handler class name.
* *
* @param class-string $handlerClassName * @param class-string|Factory $handler
* *
* @return ReflectionParameter[] * @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])) { if (!isset($this->tagHandlerParameterCache[$handlerClassName])) {
$methodReflection = new ReflectionMethod($handlerClassName, 'create'); $methodReflection = new ReflectionMethod($handlerClassName, 'create');
$this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters();
+4 -15
View File
@@ -14,9 +14,9 @@ declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock; namespace phpDocumentor\Reflection\DocBlock;
use InvalidArgumentException; 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. * 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; 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. * Registers a service with the Service Locator using the FQCN of the class or the alias, if provided.
* *
@@ -71,7 +60,7 @@ interface TagFactory
* *
* @param string $tagName Name of tag to register a handler for. When registering a namespaced * @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. * tag, the full name, along with a prefixing slash MUST be provided.
* @param class-string<Tag> $handler FQCN of handler. * @param class-string<Tag>|Factory $handler FQCN of handler.
* *
* @throws InvalidArgumentException If the tag name is not a string. * @throws InvalidArgumentException If the tag name is not a string.
* @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but * @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but
@@ -80,5 +69,5 @@ interface TagFactory
* @throws InvalidArgumentException If the handler is not an existing class. * @throws InvalidArgumentException If the handler is not an existing class.
* @throws InvalidArgumentException If the handler does not implement the {@see Tag} interface. * @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;
} }
@@ -0,0 +1,68 @@
<?php
/*
* 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
*
*/
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
/**
* Factory class creating tags using phpstan's parser
*
* This class uses {@see PHPStanFactory} implementations to create tags
* from the ast of the phpstan docblock parser.
*
* @internal This class is not part of the BC promise of this library.
*/
class AbstractPHPStanFactory implements Factory
{
private PhpDocParser $parser;
private Lexer $lexer;
/** @var PHPStanFactory[] */
private array $factories;
public function __construct(PHPStanFactory ...$factories)
{
$this->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
);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
/*
* 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
*
*/
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 InvalidArgumentException;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\Types\Context as TypeContext;
interface Factory
{
/**
* 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;
}
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
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_;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Webmozart\Assert\Assert;
use function array_map;
use function trim;
/**
* @internal This class is not part of the BC promise of this library.
*/
final class MethodFactory implements PHPStanFactory
{
private DescriptionFactory $descriptionFactory;
private TypeResolver $typeResolver;
public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory)
{
$this->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);
}
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\Types\Context;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
interface PHPStanFactory
{
public function create(PhpDocTagNode $node, Context $context): Tag;
public function supports(PhpDocTagNode $node, Context $context): bool;
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
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;
use Webmozart\Assert\Assert;
use function trim;
/**
* @internal This class is not part of the BC promise of this library.
*/
final class ParamFactory implements PHPStanFactory
{
private DescriptionFactory $descriptionFactory;
private TypeResolver $typeResolver;
public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory)
{
$this->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;
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
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;
use Webmozart\Assert\Assert;
use function trim;
/**
* @internal This class is not part of the BC promise of this library.
*/
final class PropertyFactory implements PHPStanFactory
{
private DescriptionFactory $descriptionFactory;
private TypeResolver $typeResolver;
public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory)
{
$this->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';
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
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;
use Webmozart\Assert\Assert;
use function trim;
/**
* @internal This class is not part of the BC promise of this library.
*/
final class PropertyReadFactory implements PHPStanFactory
{
private DescriptionFactory $descriptionFactory;
private TypeResolver $typeResolver;
public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory)
{
$this->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';
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
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;
use Webmozart\Assert\Assert;
use function trim;
/**
* @internal This class is not part of the BC promise of this library.
*/
final class PropertyWriteFactory implements PHPStanFactory
{
private DescriptionFactory $descriptionFactory;
private TypeResolver $typeResolver;
public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory)
{
$this->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';
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
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;
use Webmozart\Assert\Assert;
/**
* @internal This class is not part of the BC promise of this library.
*/
final class ReturnFactory implements PHPStanFactory
{
private DescriptionFactory $descriptionFactory;
private TypeResolver $typeResolver;
public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory)
{
$this->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;
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
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;
use Webmozart\Assert\Assert;
use function trim;
/**
* @internal This class is not part of the BC promise of this library.
*/
final class VarFactory implements PHPStanFactory
{
private DescriptionFactory $descriptionFactory;
private TypeResolver $typeResolver;
public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory)
{
$this->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;
}
}
+75 -12
View File
@@ -24,6 +24,7 @@ use phpDocumentor\Reflection\Types\Void_;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use function array_keys; use function array_keys;
use function array_map;
use function explode; use function explode;
use function implode; use function implode;
use function is_string; use function is_string;
@@ -31,9 +32,12 @@ use function preg_match;
use function sort; use function sort;
use function strpos; use function strpos;
use function substr; use function substr;
use function trigger_error;
use function trim; use function trim;
use function var_export; use function var_export;
use const E_USER_DEPRECATED;
/** /**
* Reflection class for an {@}method in a Docblock. * Reflection class for an {@}method in a Docblock.
*/ */
@@ -45,12 +49,6 @@ final class Method extends BaseTag implements Factory\StaticMethod
/** @var string */ /** @var string */
private $methodName; private $methodName;
/**
* @phpstan-var array<int, array{name: string, type: Type}>
* @var array<int, array<string, Type|string>>
*/
private $arguments;
/** @var bool */ /** @var bool */
private $isStatic; private $isStatic;
@@ -60,8 +58,12 @@ final class Method extends BaseTag implements Factory\StaticMethod
/** @var bool */ /** @var bool */
private $returnsReference; private $returnsReference;
/** @var MethodParameter[] */
private array $parameters;
/** /**
* @param array<int, array<string, Type|string>> $arguments * @param array<int, array<string, Type|string>> $arguments
* @param MethodParameter[] $parameters
* @phpstan-param array<int, array{name: string, type: Type}|string> $arguments * @phpstan-param array<int, array{name: string, type: Type}|string> $arguments
*/ */
public function __construct( public function __construct(
@@ -70,7 +72,8 @@ final class Method extends BaseTag implements Factory\StaticMethod
?Type $returnType = null, ?Type $returnType = null,
bool $static = false, bool $static = false,
?Description $description = null, ?Description $description = null,
bool $returnsReference = false bool $returnsReference = false,
?array $parameters = null
) { ) {
Assert::stringNotEmpty($methodName); Assert::stringNotEmpty($methodName);
@@ -78,20 +81,31 @@ final class Method extends BaseTag implements Factory\StaticMethod
$returnType = new Void_(); $returnType = new Void_();
} }
$arguments = $this->filterArguments($arguments);
$this->methodName = $methodName; $this->methodName = $methodName;
$this->arguments = $this->filterArguments($arguments);
$this->returnType = $returnType; $this->returnType = $returnType;
$this->isStatic = $static; $this->isStatic = $static;
$this->description = $description; $this->description = $description;
$this->returnsReference = $returnsReference; $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( public static function create(
string $body, string $body,
?TypeResolver $typeResolver = null, ?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null, ?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null ?TypeContext $context = null
): ?self { ): ?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::stringNotEmpty($body);
Assert::notNull($typeResolver); Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory); Assert::notNull($descriptionFactory);
@@ -186,7 +200,14 @@ final class Method extends BaseTag implements Factory\StaticMethod
} }
} }
return new static($methodName, $arguments, $returnType, $static, $description, $returnsReference); return new static(
$methodName,
$arguments,
$returnType,
$static,
$description,
$returnsReference
);
} }
/** /**
@@ -198,12 +219,27 @@ final class Method extends BaseTag implements Factory\StaticMethod
} }
/** /**
* @deprecated Method deprecated, use {@see self::getParameters()}
*
* @return array<int, array<string, Type|string>> * @return array<int, array<string, Type|string>>
* @phpstan-return array<int, array{name: string, type: Type}> * @phpstan-return array<int, array{name: string, type: Type}>
*/ */
public function getArguments(): 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 @@ final class Method extends BaseTag implements Factory\StaticMethod
public function __toString(): string public function __toString(): string
{ {
$arguments = []; $arguments = [];
foreach ($this->arguments as $argument) { foreach ($this->parameters as $parameter) {
$arguments[] = $argument['type'] . ' $' . $argument['name']; $arguments[] = $parameter->getType() . ' ' .
($parameter->isReference() ? '&' : '') .
($parameter->isVariadic() ? '...' : '') .
'$' . $parameter->getName();
} }
$argumentStr = '(' . implode(', ', $arguments) . ')'; $argumentStr = '(' . implode(', ', $arguments) . ')';
@@ -297,4 +336,28 @@ final class Method extends BaseTag implements Factory\StaticMethod
return $argument; return $argument;
} }
/**
* @param array{name: string, type: Type} $arguments
* @phpstan-param array<int, 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
);
}
} }
+72
View File
@@ -0,0 +1,72 @@
<?php
/**
* 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
*/
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\Type;
final class MethodParameter
{
/** @var Type */
private $type;
/** @var bool */
private $isReference;
/** @var bool */
private $isVariadic;
/** @var string */
private $name;
/** @var string|null */
private $defaultValue;
public function __construct(
string $name,
Type $type,
bool $isReference = false,
bool $isVariadic = false,
?string $defaultValue = null
) {
$this->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;
}
}
+11
View File
@@ -26,7 +26,9 @@ use function array_unshift;
use function implode; use function implode;
use function strpos; use function strpos;
use function substr; use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
use const PREG_SPLIT_DELIM_CAPTURE; use const PREG_SPLIT_DELIM_CAPTURE;
/** /**
@@ -58,12 +60,21 @@ final class Param extends TagWithType implements Factory\StaticMethod
$this->isReference = $isReference; $this->isReference = $isReference;
} }
/**
* @deprecated Create using static factory is deprecated,
* this method should not be called directly by library consumers
*/
public static function create( public static function create(
string $body, string $body,
?TypeResolver $typeResolver = null, ?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null, ?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null ?TypeContext $context = null
): self { ): 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::stringNotEmpty($body);
Assert::notNull($typeResolver); Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory); Assert::notNull($descriptionFactory);
+11
View File
@@ -26,7 +26,9 @@ use function array_unshift;
use function implode; use function implode;
use function strpos; use function strpos;
use function substr; use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
use const PREG_SPLIT_DELIM_CAPTURE; use const PREG_SPLIT_DELIM_CAPTURE;
/** /**
@@ -47,12 +49,21 @@ final class Property extends TagWithType implements Factory\StaticMethod
$this->description = $description; $this->description = $description;
} }
/**
* @deprecated Create using static factory is deprecated,
* this method should not be called directly by library consumers
*/
public static function create( public static function create(
string $body, string $body,
?TypeResolver $typeResolver = null, ?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null, ?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null ?TypeContext $context = null
): self { ): 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::stringNotEmpty($body);
Assert::notNull($typeResolver); Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory); Assert::notNull($descriptionFactory);
+11
View File
@@ -26,7 +26,9 @@ use function array_unshift;
use function implode; use function implode;
use function strpos; use function strpos;
use function substr; use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
use const PREG_SPLIT_DELIM_CAPTURE; use const PREG_SPLIT_DELIM_CAPTURE;
/** /**
@@ -47,12 +49,21 @@ final class PropertyRead extends TagWithType implements Factory\StaticMethod
$this->description = $description; $this->description = $description;
} }
/**
* @deprecated Create using static factory is deprecated,
* this method should not be called directly by library consumers
*/
public static function create( public static function create(
string $body, string $body,
?TypeResolver $typeResolver = null, ?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null, ?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null ?TypeContext $context = null
): self { ): 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::stringNotEmpty($body);
Assert::notNull($typeResolver); Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory); Assert::notNull($descriptionFactory);
+11
View File
@@ -26,7 +26,9 @@ use function array_unshift;
use function implode; use function implode;
use function strpos; use function strpos;
use function substr; use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
use const PREG_SPLIT_DELIM_CAPTURE; use const PREG_SPLIT_DELIM_CAPTURE;
/** /**
@@ -47,12 +49,21 @@ final class PropertyWrite extends TagWithType implements Factory\StaticMethod
$this->description = $description; $this->description = $description;
} }
/**
* @deprecated Create using static factory is deprecated,
* this method should not be called directly by library consumers
*/
public static function create( public static function create(
string $body, string $body,
?TypeResolver $typeResolver = null, ?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null, ?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null ?TypeContext $context = null
): self { ): 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::stringNotEmpty($body);
Assert::notNull($typeResolver); Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory); Assert::notNull($descriptionFactory);
+13
View File
@@ -20,6 +20,10 @@ use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext; use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use function trigger_error;
use const E_USER_DEPRECATED;
/** /**
* Reflection class for a {@}return tag in a Docblock. * Reflection class for a {@}return tag in a Docblock.
*/ */
@@ -32,12 +36,21 @@ final class Return_ extends TagWithType implements Factory\StaticMethod
$this->description = $description; $this->description = $description;
} }
/**
* @deprecated Create using static factory is deprecated,
* this method should not be called directly by library consumers
*/
public static function create( public static function create(
string $body, string $body,
?TypeResolver $typeResolver = null, ?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null, ?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null ?TypeContext $context = null
): self { ): 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($typeResolver);
Assert::notNull($descriptionFactory); Assert::notNull($descriptionFactory);
+11
View File
@@ -26,7 +26,9 @@ use function array_unshift;
use function implode; use function implode;
use function strpos; use function strpos;
use function substr; use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
use const PREG_SPLIT_DELIM_CAPTURE; use const PREG_SPLIT_DELIM_CAPTURE;
/** /**
@@ -47,12 +49,21 @@ final class Var_ extends TagWithType implements Factory\StaticMethod
$this->description = $description; $this->description = $description;
} }
/**
* @deprecated Create using static factory is deprecated,
* this method should not be called directly by library consumers
*/
public static function create( public static function create(
string $body, string $body,
?TypeResolver $typeResolver = null, ?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null, ?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null ?TypeContext $context = null
): self { ): 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::stringNotEmpty($body);
Assert::notNull($typeResolver); Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory); Assert::notNull($descriptionFactory);
+38 -12
View File
@@ -19,6 +19,14 @@ use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory; use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\TagFactory; 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 Webmozart\Assert\Assert;
use function array_shift; use function array_shift;
@@ -38,7 +46,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface
/** @var DocBlock\DescriptionFactory */ /** @var DocBlock\DescriptionFactory */
private $descriptionFactory; private $descriptionFactory;
/** @var DocBlock\TagFactory */ /** @var TagFactory */
private $tagFactory; private $tagFactory;
/** /**
@@ -47,22 +55,39 @@ final class DocBlockFactory implements DocBlockFactoryInterface
public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory) public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory)
{ {
$this->descriptionFactory = $descriptionFactory; $this->descriptionFactory = $descriptionFactory;
$this->tagFactory = $tagFactory; $this->tagFactory = $tagFactory;
} }
/** /**
* Factory method for easy instantiation. * Factory method for easy instantiation.
* *
* @param array<string, class-string<Tag>> $additionalTags * @param array<string, class-string<Tag>|Factory> $additionalTags
*/ */
public static function createInstance(array $additionalTags = []): self public static function createInstance(array $additionalTags = []): DocBlockFactoryInterface
{ {
$fqsenResolver = new FqsenResolver(); $fqsenResolver = new FqsenResolver();
$tagFactory = new StandardTagFactory($fqsenResolver); $tagFactory = new StandardTagFactory($fqsenResolver);
$descriptionFactory = new DescriptionFactory($tagFactory); $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($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); $docBlockFactory = new self($descriptionFactory, $tagFactory);
foreach ($additionalTags as $tagName => $tagHandler) { foreach ($additionalTags as $tagName => $tagHandler) {
@@ -111,9 +136,9 @@ final class DocBlockFactory implements DocBlockFactoryInterface
} }
/** /**
* @param class-string<Tag> $handler * @param class-string<Tag>|Factory $handler
*/ */
public function registerTagHandler(string $tagName, string $handler): void public function registerTagHandler(string $tagName, $handler): void
{ {
$this->tagFactory->registerTagHandler($tagName, $handler); $this->tagFactory->registerTagHandler($tagName, $handler);
} }
@@ -138,6 +163,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface
} }
// phpcs:disable // phpcs:disable
/** /**
* Splits the DocBlock into a template marker, summary, description and block of tags. * Splits the DocBlock into a template marker, summary, description and block of tags.
* *
@@ -149,7 +175,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface
* *
* @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split. * @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 // phpcs:enable
// Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This
@@ -227,7 +253,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface
/** /**
* Creates the tag objects. * 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 * @param Types\Context $context Context of the parsed Tag
* *
* @return DocBlock\Tag[] * @return DocBlock\Tag[]
@@ -240,7 +266,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface
} }
$result = []; $result = [];
$lines = $this->splitTagBlockIntoTagLines($tags); $lines = $this->splitTagBlockIntoTagLines($tags);
foreach ($lines as $key => $tagLine) { foreach ($lines as $key => $tagLine) {
$result[$key] = $this->tagFactory->create(trim($tagLine), $context); $result[$key] = $this->tagFactory->create(trim($tagLine), $context);
} }
+1 -1
View File
@@ -14,7 +14,7 @@ interface DocBlockFactoryInterface
* *
* @param array<string, class-string<Tag>> $additionalTags * @param array<string, class-string<Tag>> $additionalTags
*/ */
public static function createInstance(array $additionalTags = []): DocBlockFactory; public static function createInstance(array $additionalTags = []): self;
/** /**
* @param string|object $docblock * @param string|object $docblock
@@ -17,7 +17,11 @@ use Mockery as m;
use phpDocumentor\Reflection\DocBlock\Description; use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory; use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\See; 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; use PHPUnit\Framework\TestCase;
/** /**
@@ -83,7 +87,7 @@ DESCRIPTION;
str_replace( str_replace(
PHP_EOL, PHP_EOL,
"\n", "\n",
$descriptionText $descriptionText
), ),
$description->render() $description->render()
); );
@@ -134,7 +138,7 @@ DESCRIPTION;
str_replace( str_replace(
PHP_EOL, PHP_EOL,
"\n", "\n",
<<<'DESCRIPTION' <<<'DESCRIPTION'
You can escape the @-sign by surrounding it with braces, for example: @. And escape a closing brace within an 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: }. inline tag by adding an opening brace in front of it like this: }.
@@ -149,4 +153,36 @@ DESCRIPTION
$foundDescription $foundDescription
); );
} }
public function testMultilineTags(): void
{
$docCommment = <<<DOC
/**
* This is an example of a summary.
*
* @param array<
* int,
* string
* > \$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()
);
}
} }
+31
View File
@@ -0,0 +1,31 @@
<?php
/*
* 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
*
*/
declare(strict_types=1);
namespace phpDocumentor\Reflection\Assets;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory;
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\Types\Context;
class CustomTagFactory implements Factory
{
public $class;
public function create(string $tagLine, ?Context $context = null, CustomServiceClass $class = null): Tag
{
$this->class = $class;
return new Generic('custom');
}
}
@@ -18,6 +18,7 @@ use Mockery as m;
use phpDocumentor\Reflection\Assets\CustomParam; use phpDocumentor\Reflection\Assets\CustomParam;
use phpDocumentor\Reflection\Assets\CustomServiceClass; use phpDocumentor\Reflection\Assets\CustomServiceClass;
use phpDocumentor\Reflection\Assets\CustomServiceInterface; use phpDocumentor\Reflection\Assets\CustomServiceInterface;
use phpDocumentor\Reflection\Assets\CustomTagFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Author; use phpDocumentor\Reflection\DocBlock\Tags\Author;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter; use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter; use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
@@ -225,6 +226,22 @@ class StandardTagFactoryTest extends TestCase
$this->assertSame('author', $tag->getName()); $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\StandardTagFactory::addService
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Author * @uses \phpDocumentor\Reflection\DocBlock\Tags\Author
@@ -0,0 +1,151 @@
<?php
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\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Method;
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_;
final class MethodFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\MethodFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\MethodFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\MethodFactory::supports
* @dataProvider tagProvider
*/
public function testIsCreated(string $tagLine, Method $tag): void
{
$ast = $this->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<array<string|Method>> */
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),
]
),
],
];
}
}
@@ -0,0 +1,46 @@
<?php
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\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\String_;
final class ParamFactoryTest 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
*/
public function testParamIsCreated(): void
{
$ast = $this->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)
);
}
}
@@ -0,0 +1,44 @@
<?php
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\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Property;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\String_;
final class PropertyFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyFactory::supports
*/
public function testParamIsCreated(): void
{
$ast = $this->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)
);
}
}
@@ -0,0 +1,44 @@
<?php
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\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\PropertyRead;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\String_;
final class PropertyReadFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyReadFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyReadFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyReadFactory::supports
*/
public function testParamIsCreated(): void
{
$ast = $this->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)
);
}
}
@@ -0,0 +1,44 @@
<?php
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\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\String_;
final class PropertyWriteFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyWriteFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyWriteFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyWriteFactory::supports
*/
public function testParamIsCreated(): void
{
$ast = $this->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)
);
}
}
@@ -0,0 +1,43 @@
<?php
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\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\String_;
final class ReturnFactoryTest extends TagFactoryTestCase
{
/**
* @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
{
$ast = $this->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)
);
}
}
@@ -0,0 +1,62 @@
<?php
/*
* 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
*
*/
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\TypeResolver;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use PHPUnit\Framework\TestCase;
abstract class TagFactoryTestCase extends TestCase
{
public function parseTag(string $tag): PhpDocTagNode
{
$lexer = new Lexer();
$tokens = $lexer->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();
}
}
@@ -0,0 +1,44 @@
<?php
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\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\String_;
final class VarFactoryTest extends TagFactoryTestCase
{
/**
* @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
{
$ast = $this->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)
);
}
}