diff --git a/src/DocBlock/StandardTagFactory.php b/src/DocBlock/StandardTagFactory.php index eb0c75b..fa724aa 100644 --- a/src/DocBlock/StandardTagFactory.php +++ b/src/DocBlock/StandardTagFactory.php @@ -76,7 +76,7 @@ final class StandardTagFactory implements TagFactory public const REGEX_TAGNAME = '[\w\-\_\\\\:]+'; /** - * @var array> An array with a tag as a key, and an + * @var array|Factory> An array with a tag as a key, and an * FQCN to a class that handles it as an array value. */ private $tagHandlerMappings = [ @@ -177,7 +177,7 @@ final class StandardTagFactory implements TagFactory } if (is_object($handler)) { - Assert::implementsInterface($handler, TagFactory::class); + Assert::implementsInterface($handler, Factory::class); $this->tagHandlerMappings[$tagName] = $handler; return; @@ -283,12 +283,12 @@ final class StandardTagFactory implements TagFactory } } + $parameterName = $parameter->getName(); if (isset($locator[$typeHint])) { - $arguments[] = $locator[$typeHint]; + $arguments[$parameterName] = $locator[$typeHint]; continue; } - $parameterName = $parameter->getName(); if (isset($locator[$parameterName])) { $arguments[$parameterName] = $locator[$parameterName]; continue; diff --git a/src/DocBlock/Tags/Factory/PHPStanFactory.php b/src/DocBlock/Tags/Factory/PHPStanFactory.php index 4e9f77e..8f352df 100644 --- a/src/DocBlock/Tags/Factory/PHPStanFactory.php +++ b/src/DocBlock/Tags/Factory/PHPStanFactory.php @@ -10,7 +10,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; interface PHPStanFactory { - public function create(PhpDocTagNode $node, Context $context): Tag; + public function create(PhpDocTagNode $node, ?Context $context): Tag; public function supports(PhpDocTagNode $node, ?Context $context): bool; } diff --git a/src/DocBlock/Tags/Factory/ParamFactory.php b/src/DocBlock/Tags/Factory/ParamFactory.php index de5713d..6dbab3d 100644 --- a/src/DocBlock/Tags/Factory/ParamFactory.php +++ b/src/DocBlock/Tags/Factory/ParamFactory.php @@ -28,7 +28,7 @@ final class ParamFactory implements PHPStanFactory $this->descriptionFactory = $descriptionFactory; } - public function create(PhpDocTagNode $node, Context $context): Tag + public function create(PhpDocTagNode $node, ?Context $context): Tag { $tagValue = $node->value; Assert::isInstanceOf($tagValue, ParamTagValueNode::class); diff --git a/src/DocBlock/Tags/Factory/TypeFactory.php b/src/DocBlock/Tags/Factory/TypeFactory.php index 6c41c1e..51fa527 100644 --- a/src/DocBlock/Tags/Factory/TypeFactory.php +++ b/src/DocBlock/Tags/Factory/TypeFactory.php @@ -44,7 +44,7 @@ use function strtolower; /** * @internal This class is not part of the BC promise of this library. */ -class TypeFactory +final class TypeFactory { private TypeResolver $resolver; @@ -53,7 +53,7 @@ class TypeFactory $this->resolver = $resolver; } - public function createType(TypeNode $type, Context $context): ?Type + public function createType(TypeNode $type, ?Context $context): ?Type { switch (get_class($type)) { case ArrayTypeNode::class: @@ -77,6 +77,7 @@ class TypeFactory return $this->createFromCallable($type, $context); case ConstTypeNode::class: + return null; case GenericTypeNode::class: return $this->createFromGeneric($type, $context); @@ -85,22 +86,29 @@ class TypeFactory case IntersectionTypeNode::class: return new Intersection( - array_map( - fn (TypeNode $nestedType) => $this->createType($nestedType, $context), - $type->types + array_filter( + array_map( + fn (TypeNode $nestedType) => $this->createType($nestedType, $context), + $type->types + ) ) ); case NullableTypeNode::class: - return new Nullable( - $this->createType($type->type, $context) - ); + $nestedType = $this->createType($type->type, $context); + if ($nestedType === null) { + return null; + } + + return new Nullable($nestedType); case UnionTypeNode::class: return new Compound( - array_map( - fn (TypeNode $nestedType) => $this->createType($nestedType, $context), - $type->types + array_filter( + array_map( + fn (TypeNode $nestedType) => $this->createType($nestedType, $context), + $type->types + ) ) ); @@ -115,7 +123,7 @@ class TypeFactory } } - private function createFromGeneric(GenericTypeNode $type, Context $context): Type + private function createFromGeneric(GenericTypeNode $type, ?Context $context): Type { switch (strtolower($type->type->name)) { case 'array': @@ -162,7 +170,7 @@ class TypeFactory } } - private function createFromCallable(CallableTypeNode $type, Context $context): Callable_ + private function createFromCallable(CallableTypeNode $type, ?Context $context): Callable_ { return new Callable_(); } diff --git a/src/DocBlock/Tags/Factory/VarFactory.php b/src/DocBlock/Tags/Factory/VarFactory.php new file mode 100644 index 0000000..1af6f76 --- /dev/null +++ b/src/DocBlock/Tags/Factory/VarFactory.php @@ -0,0 +1,47 @@ +typeFactory = $typeFactory; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, ?Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, VarTagValueNode::class); + + return new Var_( + trim($tagValue->variableName, '$'), + $this->typeFactory->createType($tagValue->type, $context), + $this->descriptionFactory->create($tagValue->description, $context) + ); + } + + public function supports(PhpDocTagNode $node, ?Context $context): bool + { + return $node->value instanceof VarTagValueNode; + } +} diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index 2599e54..299fd6d 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -42,13 +42,13 @@ final class DocBlockFactory implements DocBlockFactoryInterface /** @var DocBlock\DescriptionFactory */ private $descriptionFactory; - /** @var Factory */ + /** @var TagFactory */ private $tagFactory; /** * Initializes this factory with the required subcontractors. */ - public function __construct(DescriptionFactory $descriptionFactory, Factory $tagFactory) + public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory) { $this->descriptionFactory = $descriptionFactory; $this->tagFactory = $tagFactory; @@ -57,7 +57,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface /** * Factory method for easy instantiation. * - * @param array|TagFactory> $additionalTags + * @param array|Factory> $additionalTags */ public static function createInstance(array $additionalTags = []): self { @@ -74,6 +74,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface $tagFactory->addService($descriptionFactory); $tagFactory->addService($typeResolver); $tagFactory->registerTagHandler('param', $phpstanTagFactory); + $tagFactory->registerTagHandler('var', $phpstanTagFactory); $docBlockFactory = new self($descriptionFactory, $tagFactory); foreach ($additionalTags as $tagName => $tagHandler) { @@ -122,9 +123,9 @@ final class DocBlockFactory implements DocBlockFactoryInterface } /** - * @param class-string $handler + * @param class-string|Factory $handler */ - public function registerTagHandler(string $tagName, string $handler): void + public function registerTagHandler(string $tagName, $handler): void { $this->tagFactory->registerTagHandler($tagName, $handler); } diff --git a/src/PseudoTypes/ArrayShapeItem.php b/src/PseudoTypes/ArrayShapeItem.php index 2480f3b..5ff245e 100644 --- a/src/PseudoTypes/ArrayShapeItem.php +++ b/src/PseudoTypes/ArrayShapeItem.php @@ -6,6 +6,7 @@ namespace phpDocumentor\Reflection\PseudoTypes; use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\Types\Mixed_; use function sprintf; final class ArrayShapeItem @@ -14,10 +15,10 @@ final class ArrayShapeItem private Type $value; private bool $optional; - public function __construct(?string $key, Type $value, bool $optional) + public function __construct(?string $key, ?Type $value, bool $optional) { $this->key = $key; - $this->value = $value; + $this->value = $value ?? new Mixed_(); $this->optional = $optional; } @@ -36,7 +37,7 @@ final class ArrayShapeItem return $this->optional; } - public function __toString() + public function __toString(): string { if ($this->key !== null) { return sprintf( diff --git a/tests/unit/Assets/CustomTagFactory.php b/tests/unit/Assets/CustomTagFactory.php index ad054f9..df4a3f6 100644 --- a/tests/unit/Assets/CustomTagFactory.php +++ b/tests/unit/Assets/CustomTagFactory.php @@ -14,34 +14,18 @@ declare(strict_types=1); namespace phpDocumentor\Reflection\Assets; use phpDocumentor\Reflection\DocBlock\Tag; -use phpDocumentor\Reflection\DocBlock\TagFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory; use phpDocumentor\Reflection\DocBlock\Tags\Generic; use phpDocumentor\Reflection\Types\Context; -use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; -class CustomTagFactory implements TagFactory +class CustomTagFactory implements Factory { public $class; - public function addParameter(string $name, $value): void - { - // TODO: Implement addParameter() method. - } - public function create(string $tagLine, ?Context $context = null, CustomServiceClass $class = null): Tag { $this->class = $class; return new Generic('custom'); } - - public function addService(object $service): void - { - // TODO: Implement addService() method. - } - - public function registerTagHandler(string $tagName, string $handler): void - { - // TODO: Implement registerTagHandler() method. - } } diff --git a/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php new file mode 100644 index 0000000..86b0f6f --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/ParamFactoryTest.php @@ -0,0 +1,46 @@ +parseTag('@param string $var'); + $factory = new ParamFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + new Param( + 'var', + new String_(), + false, + new Description(''), + false + ), + $factory->create($ast, $context) + ); + } +} diff --git a/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php new file mode 100644 index 0000000..625958f --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/TagFactoryTestCase.php @@ -0,0 +1,62 @@ +tokenize($tag); + $constParser = new ConstExprParser(); + + return (new PhpDocParser(new TypeParser($constParser), $constParser))->parseTag(new TokenIterator($tokens)); + } + + public function giveTypeFactory(): TypeFactory + { + return new TypeFactory(new TypeResolver(new FqsenResolver())); + } + + public function givenDescriptionFactory(): DescriptionFactory + { + $factory = m::mock(DescriptionFactory::class); + $factory->shouldReceive('create')->andReturn(new Description('')); + + return $factory; + } + + /** + * Call Mockery::close after each test. + * + * @after + */ + public function closeMockery(): void + { + m::close(); + } +} diff --git a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php index 8958389..ffec35a 100644 --- a/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php +++ b/tests/unit/DocBlock/Tags/Factory/TypeFactoryTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of phpDocumentor. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @link http://phpdoc.org + */ + namespace phpDocumentor\Reflection\DocBlock\Tags\Factory; use phpDocumentor\Reflection\Fqsen; @@ -56,6 +65,9 @@ final class TypeFactoryTest extends TestCase self::assertEquals($expected, $actual); } + /** + * @return array + */ public function typeProvider(): array { return [ @@ -126,6 +138,9 @@ final class TypeFactoryTest extends TestCase ]; } + /** + * @return array + */ public function genericsProvider(): array { return [ @@ -169,6 +184,9 @@ final class TypeFactoryTest extends TestCase ]; } + /** + * @return array + */ public function callableProvider(): array { return [ @@ -195,6 +213,9 @@ final class TypeFactoryTest extends TestCase ]; } + /** + * @return array + */ public function constExpressions(): array { return [ diff --git a/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php new file mode 100644 index 0000000..a25058b --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/VarFactoryTest.php @@ -0,0 +1,44 @@ +parseTag('@var string $var'); + $factory = new VarFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + new Var_( + 'var', + new String_(), + new Description('') + ), + $factory->create($ast, $context) + ); + } +}