mirror of
https://github.com/barryvdh/ReflectionDocBlock.git
synced 2026-08-18 18:13:13 +00:00
WIP
This commit is contained in:
Generated
+2
-2
@@ -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": "5ca8bb643a11d17932ef43044dc2f12c",
|
"content-hash": "99af646bb3e1fa0d77d855f0f5995c9f",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "phpdocumentor/reflection-common",
|
"name": "phpdocumentor/reflection-common",
|
||||||
@@ -4053,7 +4053,7 @@
|
|||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "^7.2 || ^8.0",
|
"php": "^7.4 || ^8.0",
|
||||||
"ext-filter": "*"
|
"ext-filter": "*"
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ 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 strpos;
|
use function strpos;
|
||||||
use function trim;
|
use function trim;
|
||||||
@@ -162,18 +163,23 @@ 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
|
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::implementsInterface($handler, TagFactory::class);
|
||||||
|
$this->tagHandlerMappings[$tagName] = $handler;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::classExists($handler);
|
||||||
|
Assert::implementsInterface($handler, Tag::class);
|
||||||
$this->tagHandlerMappings[$tagName] = $handler;
|
$this->tagHandlerMappings[$tagName] = $handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,6 +216,8 @@ final class StandardTagFactory implements TagFactory
|
|||||||
$this->getServiceLocatorWithDynamicParameters($context, $name, $body)
|
$this->getServiceLocatorWithDynamicParameters($context, $name, $body)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$arguments['tagLine'] = sprintf('@%s %s', $name, $body);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$callable = [$handlerClassName, 'create'];
|
$callable = [$handlerClassName, 'create'];
|
||||||
Assert::isCallable($callable);
|
Assert::isCallable($callable);
|
||||||
@@ -225,9 +233,9 @@ final class StandardTagFactory implements TagFactory
|
|||||||
/**
|
/**
|
||||||
* Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
|
* Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
|
||||||
*
|
*
|
||||||
* @return class-string<Tag>
|
* @return class-string<Tag>|TagFactory
|
||||||
*/
|
*/
|
||||||
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])) {
|
||||||
@@ -275,11 +283,11 @@ final class StandardTagFactory implements TagFactory
|
|||||||
|
|
||||||
$parameterName = $parameter->getName();
|
$parameterName = $parameter->getName();
|
||||||
if (isset($locator[$parameterName])) {
|
if (isset($locator[$parameterName])) {
|
||||||
$arguments[] = $locator[$parameterName];
|
$arguments[$parameterName] = $locator[$parameterName];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$arguments[] = null;
|
$arguments[$parameterName] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $arguments;
|
return $arguments;
|
||||||
@@ -289,12 +297,14 @@ final class StandardTagFactory implements TagFactory
|
|||||||
* Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
|
* 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|TagFactory $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();
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?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\TagFactory;
|
||||||
|
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 TagFactory
|
||||||
|
{
|
||||||
|
private PhpDocParser $parser;
|
||||||
|
private Lexer $lexer;
|
||||||
|
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 addParameter(string $name, $value): void
|
||||||
|
{
|
||||||
|
// TODO: Implement addParameter() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(string $tagLine, ?TypeContext $context = null): Tag
|
||||||
|
{
|
||||||
|
$tokens = $this->lexer->tokenize($tagLine);
|
||||||
|
$ast = $this->parser->parseTag(new TokenIterator($tokens));
|
||||||
|
|
||||||
|
foreach ($this->factories as $factory) {
|
||||||
|
if ($factory->supports($ast, $context)) {
|
||||||
|
return $factory->create($ast, $context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvalidTag::create(
|
||||||
|
$ast->name,
|
||||||
|
(string) $ast->value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addService(object $service): void
|
||||||
|
{
|
||||||
|
// TODO: Implement addService() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerTagHandler(string $tagName, string $handler): void
|
||||||
|
{
|
||||||
|
// TODO: Implement registerTagHandler() method.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,49 @@
|
|||||||
|
<?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\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 TypeFactory $typeFactory;
|
||||||
|
private DescriptionFactory $descriptionFactory;
|
||||||
|
|
||||||
|
public function __construct(TypeFactory $typeFactory, DescriptionFactory $descriptionFactory)
|
||||||
|
{
|
||||||
|
$this->typeFactory = $typeFactory;
|
||||||
|
$this->descriptionFactory = $descriptionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(PhpDocTagNode $node, Context $context): Tag
|
||||||
|
{
|
||||||
|
$tagValue = $node->value;
|
||||||
|
Assert::isInstanceOf($tagValue, ParamTagValueNode::class);
|
||||||
|
|
||||||
|
return new Param(
|
||||||
|
trim($tagValue->parameterName, '$'),
|
||||||
|
$this->typeFactory->createType($tagValue->type, $context),
|
||||||
|
$tagValue->isVariadic,
|
||||||
|
$this->descriptionFactory->create($tagValue->description, $context),
|
||||||
|
$tagValue->isReference
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports(PhpDocTagNode $node, ?Context $context): bool
|
||||||
|
{
|
||||||
|
return $node->value instanceof ParamTagValueNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
|
||||||
|
|
||||||
|
use phpDocumentor\Reflection\PseudoTypes\ArrayShape;
|
||||||
|
use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem;
|
||||||
|
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
|
||||||
|
use phpDocumentor\Reflection\PseudoTypes\List_;
|
||||||
|
use phpDocumentor\Reflection\Type;
|
||||||
|
use phpDocumentor\Reflection\TypeResolver;
|
||||||
|
use phpDocumentor\Reflection\Types\Array_;
|
||||||
|
use phpDocumentor\Reflection\Types\Callable_;
|
||||||
|
use phpDocumentor\Reflection\Types\ClassString;
|
||||||
|
use phpDocumentor\Reflection\Types\Collection;
|
||||||
|
use phpDocumentor\Reflection\Types\Compound;
|
||||||
|
use phpDocumentor\Reflection\Types\Context;
|
||||||
|
use phpDocumentor\Reflection\Types\InterfaceString;
|
||||||
|
use phpDocumentor\Reflection\Types\Intersection;
|
||||||
|
use phpDocumentor\Reflection\Types\Nullable;
|
||||||
|
use phpDocumentor\Reflection\Types\This;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||||
|
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||||
|
|
||||||
|
use function array_map;
|
||||||
|
use function array_reverse;
|
||||||
|
use function get_class;
|
||||||
|
use function strtolower;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal This class is not part of the BC promise of this library.
|
||||||
|
*/
|
||||||
|
class TypeFactory
|
||||||
|
{
|
||||||
|
private TypeResolver $resolver;
|
||||||
|
|
||||||
|
public function __construct(TypeResolver $resolver)
|
||||||
|
{
|
||||||
|
$this->resolver = $resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createType(TypeNode $type, Context $context): ?Type
|
||||||
|
{
|
||||||
|
switch (get_class($type)) {
|
||||||
|
case ArrayTypeNode::class:
|
||||||
|
return new Array_(
|
||||||
|
$this->createType($type->type, $context)
|
||||||
|
);
|
||||||
|
|
||||||
|
case ArrayShapeNode::class:
|
||||||
|
return new ArrayShape(
|
||||||
|
...array_map(
|
||||||
|
fn (ArrayShapeItemNode $item) => new ArrayShapeItem(
|
||||||
|
(string) $item->keyName,
|
||||||
|
$this->createType($item->valueType, $context),
|
||||||
|
$item->optional
|
||||||
|
),
|
||||||
|
$type->items
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
case CallableTypeNode::class:
|
||||||
|
return $this->createFromCallable($type, $context);
|
||||||
|
|
||||||
|
case ConstTypeNode::class:
|
||||||
|
case GenericTypeNode::class:
|
||||||
|
return $this->createFromGeneric($type, $context);
|
||||||
|
|
||||||
|
case IdentifierTypeNode::class:
|
||||||
|
return $this->resolver->resolve($type->name, $context);
|
||||||
|
|
||||||
|
case IntersectionTypeNode::class:
|
||||||
|
return new Intersection(
|
||||||
|
array_map(
|
||||||
|
fn (TypeNode $nestedType) => $this->createType($nestedType, $context),
|
||||||
|
$type->types
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
case NullableTypeNode::class:
|
||||||
|
return new Nullable(
|
||||||
|
$this->createType($type->type, $context)
|
||||||
|
);
|
||||||
|
|
||||||
|
case UnionTypeNode::class:
|
||||||
|
return new Compound(
|
||||||
|
array_map(
|
||||||
|
fn (TypeNode $nestedType) => $this->createType($nestedType, $context),
|
||||||
|
$type->types
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
case ThisTypeNode::class:
|
||||||
|
return new This();
|
||||||
|
|
||||||
|
case ConditionalTypeNode::class:
|
||||||
|
case ConditionalTypeForParameterNode::class:
|
||||||
|
case OffsetAccessTypeNode::class:
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createFromGeneric(GenericTypeNode $type, Context $context): Type
|
||||||
|
{
|
||||||
|
switch (strtolower($type->type->name)) {
|
||||||
|
case 'array':
|
||||||
|
return new Array_(
|
||||||
|
...array_reverse(
|
||||||
|
array_map(
|
||||||
|
fn (TypeNode $genericType) => $this->createType($genericType, $context),
|
||||||
|
$type->genericTypes
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'class-string':
|
||||||
|
return new ClassString(
|
||||||
|
$this->createType($type->genericTypes[0], $context)->getFqsen()
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'interface-string':
|
||||||
|
return new InterfaceString(
|
||||||
|
$this->createType($type->genericTypes[0], $context)->getFqsen()
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'list':
|
||||||
|
return new List_(
|
||||||
|
$this->createType($type->genericTypes[0], $context)
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'int':
|
||||||
|
return new IntegerRange(
|
||||||
|
(string) $type->genericTypes[0],
|
||||||
|
(string) ($type->genericTypes[1] ?? ''),
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return new Collection(
|
||||||
|
$this->createType($type->type, $context)->getFqsen(),
|
||||||
|
...array_reverse(
|
||||||
|
array_map(
|
||||||
|
fn (TypeNode $genericType) => $this->createType($genericType, $context),
|
||||||
|
$type->genericTypes
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createFromCallable(CallableTypeNode $type, Context $context): Callable_
|
||||||
|
{
|
||||||
|
return new Callable_();
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-3
@@ -19,6 +19,9 @@ 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\ParamFactory;
|
||||||
|
use phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory;
|
||||||
use Webmozart\Assert\Assert;
|
use Webmozart\Assert\Assert;
|
||||||
|
|
||||||
use function array_shift;
|
use function array_shift;
|
||||||
@@ -53,16 +56,23 @@ final class DocBlockFactory implements DocBlockFactoryInterface
|
|||||||
/**
|
/**
|
||||||
* Factory method for easy instantiation.
|
* Factory method for easy instantiation.
|
||||||
*
|
*
|
||||||
* @param array<string, class-string<Tag>> $additionalTags
|
* @param array<string, class-string<Tag>|TagFactory> $additionalTags
|
||||||
*/
|
*/
|
||||||
public static function createInstance(array $additionalTags = []): self
|
public static function createInstance(array $additionalTags = []): self
|
||||||
{
|
{
|
||||||
$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);
|
||||||
|
$typeFactory = new TypeFactory($typeResolver);
|
||||||
|
|
||||||
|
$phpstanTagFactory = new AbstractPHPStanFactory(
|
||||||
|
new ParamFactory($typeFactory, $descriptionFactory)
|
||||||
|
);
|
||||||
|
|
||||||
$tagFactory->addService($descriptionFactory);
|
$tagFactory->addService($descriptionFactory);
|
||||||
$tagFactory->addService(new TypeResolver($fqsenResolver));
|
$tagFactory->addService($typeResolver);
|
||||||
|
$tagFactory->registerTagHandler('param', $phpstanTagFactory);
|
||||||
|
|
||||||
$docBlockFactory = new self($descriptionFactory, $tagFactory);
|
$docBlockFactory = new self($descriptionFactory, $tagFactory);
|
||||||
foreach ($additionalTags as $tagName => $tagHandler) {
|
foreach ($additionalTags as $tagName => $tagHandler) {
|
||||||
@@ -138,6 +148,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 +160,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
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace phpDocumentor\Reflection\PhpStan;
|
|
||||||
|
|
||||||
use phpDocumentor\Reflection\DocBlock\Tag;
|
|
||||||
use phpDocumentor\Reflection\DocBlock\TagFactory as TagFactoryInterface;
|
|
||||||
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
|
|
||||||
use phpDocumentor\Reflection\Types\Context as TypeContext;
|
|
||||||
|
|
||||||
class TagFactory implements TagFactoryInterface
|
|
||||||
{
|
|
||||||
public function addParameter(string $name, $value) : void
|
|
||||||
{
|
|
||||||
// TODO: Implement addParameter() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create(string $tagLine, ?TypeContext $context = null) : Tag
|
|
||||||
{
|
|
||||||
return InvalidTag::create($tagLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addService(object $service) : void
|
|
||||||
{
|
|
||||||
// TODO: Implement addService() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
public function registerTagHandler(string $tagName, string $handler) : void
|
|
||||||
{
|
|
||||||
// TODO: Implement registerTagHandler() method.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
<?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;
|
|
||||||
|
|
||||||
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
|
|
||||||
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
|
|
||||||
use phpDocumentor\Reflection\DocBlock\Tags\Param;
|
|
||||||
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
|
|
||||||
use phpDocumentor\Reflection\PhpStan\TagFactory;
|
|
||||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShape;
|
|
||||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem;
|
|
||||||
use phpDocumentor\Reflection\Types\Context;
|
|
||||||
use PHPStan\PhpDoc\Tag\ParamTag;
|
|
||||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
|
||||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
|
||||||
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 Webmozart\Assert\Assert;
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use LogicException;
|
|
||||||
|
|
||||||
final class PhpStanDocblockFactory implements DocBlockFactoryInterface
|
|
||||||
{
|
|
||||||
private PhpDocParser $parser;
|
|
||||||
private Lexer $lexer;
|
|
||||||
private DescriptionFactory $descriptionFactory;
|
|
||||||
private TypeResolver $typeResolver;
|
|
||||||
|
|
||||||
private function __construct()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function createInstance(array $additionalTags = []): DocBlockFactoryInterface
|
|
||||||
{
|
|
||||||
$fqsenResolver = new FqsenResolver();
|
|
||||||
$constExprParser = new ConstExprParser();
|
|
||||||
$self = new self();
|
|
||||||
$self->lexer = new Lexer();
|
|
||||||
$self->parser = new PhpDocParser(
|
|
||||||
new TypeParser($constExprParser),
|
|
||||||
$constExprParser
|
|
||||||
);
|
|
||||||
|
|
||||||
$self->descriptionFactory = new DescriptionFactory(new TagFactory());
|
|
||||||
$self->typeResolver = new TypeResolver($fqsenResolver);
|
|
||||||
|
|
||||||
return $self;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create($docblock, ?Types\Context $context = null, ?Location $location = null): DocBlock
|
|
||||||
{
|
|
||||||
if (is_object($docblock)) {
|
|
||||||
if (!method_exists($docblock, 'getDocComment')) {
|
|
||||||
$exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method';
|
|
||||||
|
|
||||||
throw new InvalidArgumentException($exceptionMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
$docblock = $docblock->getDocComment();
|
|
||||||
Assert::string($docblock);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert::stringNotEmpty($docblock);
|
|
||||||
|
|
||||||
$tokens = $this->lexer->tokenize($docblock);
|
|
||||||
$ast = $this->parser->parse(new TokenIterator($tokens));
|
|
||||||
|
|
||||||
$textNodes = [];
|
|
||||||
|
|
||||||
foreach ($ast->children as $child) {
|
|
||||||
if ($child instanceof PhpDocTextNode) {
|
|
||||||
$textNodes[] = $child->text;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If node is not a text node this is the end of description;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tags = [];
|
|
||||||
foreach ($ast->getTags() as $node) {
|
|
||||||
switch ($node->name) {
|
|
||||||
case '@param':
|
|
||||||
$tag = $node->value;
|
|
||||||
$tags[] = new Param(
|
|
||||||
ltrim($tag->parameterName, '$'),
|
|
||||||
$this->createType($tag->type, $context),
|
|
||||||
$tag->isVariadic,
|
|
||||||
$this->descriptionFactory->create($tag->description),
|
|
||||||
$tag->isReference
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case '@return':
|
|
||||||
$tag = $node->value;
|
|
||||||
$tags[] = new Return_(
|
|
||||||
$this->createType($tag->type, $context),
|
|
||||||
$this->descriptionFactory->create($tag->description)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new DocBlock(
|
|
||||||
'',
|
|
||||||
$this->descriptionFactory->create(
|
|
||||||
implode("\n", $textNodes)
|
|
||||||
),
|
|
||||||
$tags,
|
|
||||||
null,
|
|
||||||
$location
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createType(TypeNode $type, Context $context)
|
|
||||||
{
|
|
||||||
switch (get_class($type)) {
|
|
||||||
case IdentifierTypeNode::class:
|
|
||||||
return $this->typeResolver->resolve($type->name, $context);
|
|
||||||
case ArrayShapeNode::class:
|
|
||||||
return new ArrayShape(
|
|
||||||
... array_map(
|
|
||||||
fn(ArrayShapeItemNode $item) => new ArrayShapeItem(
|
|
||||||
(string) $item->keyName,
|
|
||||||
$this->createType($item->valueType, $context),
|
|
||||||
$item->optional
|
|
||||||
),
|
|
||||||
$type->items
|
|
||||||
)
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,8 @@ use phpDocumentor\Reflection\Types\Array_;
|
|||||||
use phpDocumentor\Reflection\Types\ArrayKey;
|
use phpDocumentor\Reflection\Types\ArrayKey;
|
||||||
use phpDocumentor\Reflection\Types\Mixed_;
|
use phpDocumentor\Reflection\Types\Mixed_;
|
||||||
|
|
||||||
|
use function implode;
|
||||||
|
|
||||||
class ArrayShape implements PseudoType
|
class ArrayShape implements PseudoType
|
||||||
{
|
{
|
||||||
/** @var ArrayShapeItem[] */
|
/** @var ArrayShapeItem[] */
|
||||||
@@ -20,7 +22,7 @@ class ArrayShape implements PseudoType
|
|||||||
$this->items = $items;
|
$this->items = $items;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function underlyingType() : Type
|
public function underlyingType(): Type
|
||||||
{
|
{
|
||||||
return new Array_(new Mixed_(), new ArrayKey());
|
return new Array_(new Mixed_(), new ArrayKey());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ namespace phpDocumentor\Reflection\PseudoTypes;
|
|||||||
|
|
||||||
use phpDocumentor\Reflection\Type;
|
use phpDocumentor\Reflection\Type;
|
||||||
|
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
final class ArrayShapeItem
|
final class ArrayShapeItem
|
||||||
{
|
{
|
||||||
private ?string $key;
|
private ?string $key;
|
||||||
@@ -19,17 +21,17 @@ final class ArrayShapeItem
|
|||||||
$this->optional = $optional;
|
$this->optional = $optional;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getKey() : ?string
|
public function getKey(): ?string
|
||||||
{
|
{
|
||||||
return $this->key;
|
return $this->key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getValue() : Type
|
public function getValue(): Type
|
||||||
{
|
{
|
||||||
return $this->value;
|
return $this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isOptional() : bool
|
public function isOptional(): bool
|
||||||
{
|
{
|
||||||
return $this->optional;
|
return $this->optional;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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()
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
<?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;
|
|
||||||
|
|
||||||
use phpDocumentor\Reflection\DocBlock\Description;
|
|
||||||
use phpDocumentor\Reflection\DocBlock\Tags\Param;
|
|
||||||
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
|
|
||||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShape;
|
|
||||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem;
|
|
||||||
use phpDocumentor\Reflection\Types\Context;
|
|
||||||
use phpDocumentor\Reflection\Types\String_;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class PhpStanDocblockFactoryTest extends TestCase
|
|
||||||
{
|
|
||||||
public function testDocblockIsParsed()
|
|
||||||
{
|
|
||||||
$docblock = new DocBlock(
|
|
||||||
'test summary',
|
|
||||||
new Description(
|
|
||||||
"This description contains tags \n And is multiline"
|
|
||||||
),
|
|
||||||
[
|
|
||||||
new Param('firstParam', new String_(), false, new Description('Some description'), false),
|
|
||||||
new Return_(new ArrayShape(new ArrayShapeItem('foo', new String_(), false)))
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$string = <<<DOC
|
|
||||||
/**
|
|
||||||
* test summary
|
|
||||||
*
|
|
||||||
* THis description contains tags {@see https://phpdoc.org with a description}
|
|
||||||
* And is multi line
|
|
||||||
*
|
|
||||||
* @param string \$firstParam Some description
|
|
||||||
* @return array{foo: string}
|
|
||||||
*/
|
|
||||||
DOC;
|
|
||||||
|
|
||||||
|
|
||||||
$factory = PhpStanDocblockFactory::createInstance();
|
|
||||||
$actual = $factory->create(
|
|
||||||
$string,
|
|
||||||
new Context('/')
|
|
||||||
);
|
|
||||||
|
|
||||||
self::assertEquals($docblock, $actual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?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\TagFactory;
|
||||||
|
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
|
||||||
|
use phpDocumentor\Reflection\Types\Context;
|
||||||
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
||||||
|
|
||||||
|
class CustomTagFactory implements TagFactory
|
||||||
|
{
|
||||||
|
public $class;
|
||||||
|
|
||||||
|
public function addParameter(string $name, $value): void
|
||||||
|
{
|
||||||
|
// TODO: Implement addParameter() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(string $tagLine, ?Context $context = null, CustomServiceClass $class = null): Tag
|
||||||
|
{
|
||||||
|
$this->class = $class;
|
||||||
|
|
||||||
|
return new Generic('custom');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addService(object $service): void
|
||||||
|
{
|
||||||
|
// TODO: Implement addService() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerTagHandler(string $tagName, string $handler): void
|
||||||
|
{
|
||||||
|
// TODO: Implement registerTagHandler() method.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,270 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
|
||||||
|
|
||||||
|
use phpDocumentor\Reflection\Fqsen;
|
||||||
|
use phpDocumentor\Reflection\FqsenResolver;
|
||||||
|
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
|
||||||
|
use phpDocumentor\Reflection\PseudoTypes\List_;
|
||||||
|
use phpDocumentor\Reflection\Type;
|
||||||
|
use phpDocumentor\Reflection\TypeResolver;
|
||||||
|
use phpDocumentor\Reflection\Types\Array_;
|
||||||
|
use phpDocumentor\Reflection\Types\ArrayKey;
|
||||||
|
use phpDocumentor\Reflection\Types\Callable_;
|
||||||
|
use phpDocumentor\Reflection\Types\ClassString;
|
||||||
|
use phpDocumentor\Reflection\Types\Collection;
|
||||||
|
use phpDocumentor\Reflection\Types\Compound;
|
||||||
|
use phpDocumentor\Reflection\Types\Context;
|
||||||
|
use phpDocumentor\Reflection\Types\Float_;
|
||||||
|
use phpDocumentor\Reflection\Types\Integer;
|
||||||
|
use phpDocumentor\Reflection\Types\InterfaceString;
|
||||||
|
use phpDocumentor\Reflection\Types\Intersection;
|
||||||
|
use phpDocumentor\Reflection\Types\Nullable;
|
||||||
|
use phpDocumentor\Reflection\Types\Object_;
|
||||||
|
use phpDocumentor\Reflection\Types\Self_;
|
||||||
|
use phpDocumentor\Reflection\Types\String_;
|
||||||
|
use phpDocumentor\Reflection\Types\This;
|
||||||
|
use PHPStan\PhpDocParser\Lexer\Lexer;
|
||||||
|
use PHPStan\PhpDocParser\Parser\ConstExprParser;
|
||||||
|
use PHPStan\PhpDocParser\Parser\TokenIterator;
|
||||||
|
use PHPStan\PhpDocParser\Parser\TypeParser;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class TypeFactoryTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory::createType
|
||||||
|
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory::createFromGeneric
|
||||||
|
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TypeFactory::createFromCallable
|
||||||
|
* @dataProvider typeProvider
|
||||||
|
* @dataProvider genericsProvider
|
||||||
|
* @dataProvider callableProvider
|
||||||
|
*/
|
||||||
|
public function testTypeBuilding(string $type, Type $expected): void
|
||||||
|
{
|
||||||
|
$lexer = new Lexer();
|
||||||
|
$tokens = $lexer->tokenize($type);
|
||||||
|
$constParser = new ConstExprParser();
|
||||||
|
$parser = new TypeParser($constParser);
|
||||||
|
$ast = $parser->parse(new TokenIterator($tokens));
|
||||||
|
|
||||||
|
$factory = new TypeFactory(new TypeResolver(new FqsenResolver()));
|
||||||
|
$actual = $factory->createType($ast, new Context('phpDocumentor'));
|
||||||
|
|
||||||
|
self::assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function typeProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'string',
|
||||||
|
new String_(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'( string )',
|
||||||
|
new String_(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'\\Foo\Bar\\Baz',
|
||||||
|
new Object_(new Fqsen('\\Foo\Bar\\Baz')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'string|int',
|
||||||
|
new Compound(
|
||||||
|
[
|
||||||
|
new String_(),
|
||||||
|
new Integer(),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'string&int',
|
||||||
|
new Intersection(
|
||||||
|
[
|
||||||
|
new String_(),
|
||||||
|
new Integer(),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'string & (int | float)',
|
||||||
|
new Intersection(
|
||||||
|
[
|
||||||
|
new String_(),
|
||||||
|
new Compound(
|
||||||
|
[
|
||||||
|
new Integer(),
|
||||||
|
new Float_(),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'string[]',
|
||||||
|
new Array_(
|
||||||
|
new String_()
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$this',
|
||||||
|
new This(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'?int',
|
||||||
|
new Nullable(
|
||||||
|
new Integer()
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'self',
|
||||||
|
new Self_(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function genericsProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'array<int, Foo\\Bar>',
|
||||||
|
new Array_(
|
||||||
|
new Object_(new Fqsen('\\phpDocumentor\\Foo\\Bar')),
|
||||||
|
new Integer()
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Collection<array-key, int>[]',
|
||||||
|
new Array_(
|
||||||
|
new Collection(
|
||||||
|
new Fqsen('\\phpDocumentor\\Collection'),
|
||||||
|
new Integer(),
|
||||||
|
new ArrayKey()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'class-string',
|
||||||
|
new ClassString(null),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'class-string<Foo>',
|
||||||
|
new ClassString(new Fqsen('\\phpDocumentor\\Foo')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'interface-string<Foo>',
|
||||||
|
new InterfaceString(new Fqsen('\\phpDocumentor\\Foo')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'List<Foo>',
|
||||||
|
new List_(new Object_(new Fqsen('\\phpDocumentor\\Foo'))),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'int<1, 100>',
|
||||||
|
new IntegerRange('1', '100'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function callableProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'callable',
|
||||||
|
new Callable_(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'callable()',
|
||||||
|
new Callable_(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'callable(): Foo',
|
||||||
|
new Callable_(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'callable(): (Foo&Bar)',
|
||||||
|
new Callable_(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'callable(A&...$a=, B&...=, C): Foo',
|
||||||
|
new Callable_(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function constExpressions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['Foo::FOO_CONSTANT'],
|
||||||
|
[
|
||||||
|
'123',
|
||||||
|
//new ConstTypeNode(new ConstExprIntegerNode('123')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'123.2',
|
||||||
|
//new ConstTypeNode(new ConstExprFloatNode('123.2')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'"bar"',
|
||||||
|
//new ConstTypeNode(new ConstExprStringNode('bar')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Foo::FOO_*',
|
||||||
|
//new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Foo::FOO_*BAR',
|
||||||
|
//new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*BAR')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Foo::*FOO*',
|
||||||
|
//new ConstTypeNode(new ConstFetchNode('Foo', '*FOO*')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Foo::A*B*C',
|
||||||
|
//new ConstTypeNode(new ConstFetchNode('Foo', 'A*B*C')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'self::*BAR',
|
||||||
|
//new ConstTypeNode(new ConstFetchNode('self', '*BAR')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Foo::*',
|
||||||
|
//new ConstTypeNode(new ConstFetchNode('Foo', '*')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Foo::**',
|
||||||
|
//new ConstTypeNode(new ConstFetchNode('Foo', '*')), // fails later in PhpDocParser
|
||||||
|
//Lexer::TOKEN_WILDCARD,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Foo::*a',
|
||||||
|
//new ConstTypeNode(new ConstFetchNode('Foo', '*a')),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'( "foo" | Foo::FOO_* )',
|
||||||
|
// new UnionTypeNode([
|
||||||
|
// new ConstTypeNode(new ConstExprStringNode('foo')),
|
||||||
|
// new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*')),
|
||||||
|
// ]),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'DateTimeImmutable::*|DateTime::*',
|
||||||
|
// new UnionTypeNode([
|
||||||
|
// new ConstTypeNode(new ConstFetchNode('DateTimeImmutable', '*')),
|
||||||
|
// new ConstTypeNode(new ConstFetchNode('DateTime', '*')),
|
||||||
|
// ]),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'ParameterTier::*|null',
|
||||||
|
// new UnionTypeNode([
|
||||||
|
// new ConstTypeNode(new ConstFetchNode('ParameterTier', '*')),
|
||||||
|
// new IdentifierTypeNode('null'),
|
||||||
|
// ]),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user