From e4ac07bc405d1a3765d684317b8c815acbd91849 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 28 Oct 2022 22:24:15 +0200 Subject: [PATCH] Add method support --- src/DocBlock/Tags/Factory/MethodFactory.php | 72 ++++++++++ src/DocBlock/Tags/Factory/TypeFactory.php | 6 +- src/DocBlock/Tags/Method.php | 56 +++++++- src/DocBlock/Tags/MethodParameter.php | 72 ++++++++++ src/DocBlockFactory.php | 1 + .../Tags/Factory/MethodFactoryTest.php | 126 ++++++++++++++++++ 6 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 src/DocBlock/Tags/Factory/MethodFactory.php create mode 100644 src/DocBlock/Tags/MethodParameter.php create mode 100644 tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php diff --git a/src/DocBlock/Tags/Factory/MethodFactory.php b/src/DocBlock/Tags/Factory/MethodFactory.php new file mode 100644 index 0000000..cb2d463 --- /dev/null +++ b/src/DocBlock/Tags/Factory/MethodFactory.php @@ -0,0 +1,72 @@ +typeFactory = $typeFactory; + $this->descriptionFactory = $descriptionFactory; + } + + public function create(PhpDocTagNode $node, ?Context $context): Tag + { + $tagValue = $node->value; + Assert::isInstanceOf($tagValue, MethodTagValueNode::class); + + return new Method( + $tagValue->methodName, + [], + $this->createReturnType($tagValue, $context), + $tagValue->isStatic, + $this->descriptionFactory->create($tagValue->description, $context), + false, + array_map( + function (MethodTagValueParameterNode $param) use ($context) { + return new MethodParameter( + trim($param->parameterName, '$'), + $this->typeFactory->createType($param->type, $context), + $param->isReference, + $param->isVariadic, + (string) $param->defaultValue + ); + }, + $tagValue->parameters + ), + ); + } + + public function supports(PhpDocTagNode $node, ?Context $context): bool + { + return $node->value instanceof MethodTagValueNode; + } + + private function createReturnType(MethodTagValueNode $tagValue, ?Context $context): Type + { + return $this->typeFactory->createType($tagValue->returnType, $context) ?? new Void_(); + } +} diff --git a/src/DocBlock/Tags/Factory/TypeFactory.php b/src/DocBlock/Tags/Factory/TypeFactory.php index 469930c..6cf20a3 100644 --- a/src/DocBlock/Tags/Factory/TypeFactory.php +++ b/src/DocBlock/Tags/Factory/TypeFactory.php @@ -54,8 +54,12 @@ final class TypeFactory $this->resolver = $resolver; } - public function createType(TypeNode $type, ?Context $context): ?Type + public function createType(?TypeNode $type, ?Context $context): ?Type { + if ($type === null) { + return null; + } + switch (get_class($type)) { case ArrayTypeNode::class: return new Array_( diff --git a/src/DocBlock/Tags/Method.php b/src/DocBlock/Tags/Method.php index 10a0fbc..e97df45 100644 --- a/src/DocBlock/Tags/Method.php +++ b/src/DocBlock/Tags/Method.php @@ -24,6 +24,7 @@ use phpDocumentor\Reflection\Types\Void_; use Webmozart\Assert\Assert; use function array_keys; +use function array_map; use function explode; use function implode; use function is_string; @@ -63,6 +64,9 @@ final class Method extends BaseTag implements Factory\StaticMethod /** @var bool */ private $returnsReference; + /** @var MethodParameter[] */ + private array $parameters; + /** * @param array> $arguments * @phpstan-param array $arguments @@ -73,7 +77,8 @@ final class Method extends BaseTag implements Factory\StaticMethod ?Type $returnType = null, bool $static = false, ?Description $description = null, - bool $returnsReference = false + bool $returnsReference = false, + ?array $parameters = null ) { Assert::stringNotEmpty($methodName); @@ -81,12 +86,15 @@ final class Method extends BaseTag implements Factory\StaticMethod $returnType = new Void_(); } + $arguments = $this->filterArguments($arguments); + $this->methodName = $methodName; - $this->arguments = $this->filterArguments($arguments); + $this->arguments = $arguments; $this->returnType = $returnType; $this->isStatic = $static; $this->description = $description; $this->returnsReference = $returnsReference; + $this->parameters = $parameters ?? $this->fromLegacyArguments($arguments); } public static function create( @@ -190,7 +198,14 @@ 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 + ); } /** @@ -210,6 +225,12 @@ final class Method extends BaseTag implements Factory\StaticMethod return $this->arguments; } + /** @return MethodParameter[] */ + public function getParameters(): array + { + return $this->parameters; + } + /** * Checks whether the method tag describes a static method or not. * @@ -233,8 +254,11 @@ final class Method extends BaseTag implements Factory\StaticMethod public function __toString(): string { $arguments = []; - foreach ($this->arguments as $argument) { - $arguments[] = $argument['type'] . ' $' . $argument['name']; + foreach ($this->parameters as $parameter) { + $arguments[] = ($parameter->getType() ?? new Mixed_()) . ' ' . + ($parameter->isReference() ? '&' : '') . + ($parameter->isVariadic() ? '...' : '') . + '$' . $parameter->getName(); } $argumentStr = '(' . implode(', ', $arguments) . ')'; @@ -301,4 +325,26 @@ final class Method extends BaseTag implements Factory\StaticMethod return $argument; } + + /** + * @param array{name: string, type: Type} $arguments + * @return MethodParameter[] + */ + private function fromLegacyArguments(array $arguments): array + { + trigger_error( + 'Create method parameters via legacy format is deprecated add parameters via the constructor', + E_USER_DEPRECATED + ); + + return array_map( + static function ($arg) { + return new MethodParameter( + $arg['name'], + $arg['type'] + ); + }, + $arguments + ); + } } diff --git a/src/DocBlock/Tags/MethodParameter.php b/src/DocBlock/Tags/MethodParameter.php new file mode 100644 index 0000000..15780a6 --- /dev/null +++ b/src/DocBlock/Tags/MethodParameter.php @@ -0,0 +1,72 @@ +type = $type; + $this->isReference = $isReference; + $this->isVariadic = $isVariadic; + $this->name = $name; + $this->defaultValue = $defaultValue; + } + + public function getName(): string + { + return $this->name; + } + + public function getType(): Type + { + return $this->type; + } + + public function isReference(): bool + { + return $this->isReference; + } + + public function isVariadic(): bool + { + return $this->isVariadic; + } + + public function getDefaultValue(): ?string + { + return $this->defaultValue; + } +} diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index a08bdb1..4c0d58f 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -89,6 +89,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface $tagFactory->registerTagHandler('property', $phpstanTagFactory); $tagFactory->registerTagHandler('property-read', $phpstanTagFactory); $tagFactory->registerTagHandler('property-write', $phpstanTagFactory); + $tagFactory->registerTagHandler('method', $phpstanTagFactory); $docBlockFactory = new self($descriptionFactory, $tagFactory); foreach ($additionalTags as $tagName => $tagHandler) { diff --git a/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php new file mode 100644 index 0000000..c29f969 --- /dev/null +++ b/tests/unit/DocBlock/Tags/Factory/MethodFactoryTest.php @@ -0,0 +1,126 @@ +parseTag($tagLine); + $factory = new MethodFactory($this->giveTypeFactory(), $this->givenDescriptionFactory()); + $context = new Context('global'); + + self::assertTrue($factory->supports($ast, $context)); + self::assertEquals( + $tag, + $factory->create($ast, $context) + ); + } + + /** @return array> */ + public function tagProvider(): array + { + return [ + [ + '@method static string myMethod()', + new Method( + 'myMethod', + [], + new String_(), + true, + new Description(''), + false, + [] + ), + ], + [ + '@method string myMethod()', + new Method( + 'myMethod', + [], + new String_(), + false, + new Description(''), + false, + [] + ), + ], + [ + '@method myMethod()', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [] + ), + ], + [ + '@method myMethod(int $a = 1)', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [new MethodParameter('a', new Integer(), false, false, '1')] + ), + ], + [ + '@method myMethod(int ...$a)', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [new MethodParameter('a', new Integer(), false, true)] + ), + ], + [ + '@method myMethod(int &$a, string $b)', + new Method( + 'myMethod', + [], + new Void_(), + false, + new Description(''), + false, + [ + new MethodParameter('a', new Integer(), true, false), + new MethodParameter('b', new String_(), false, false), + ] + ), + ], + ]; + } +}