diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ae875e..248b88a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ All notable changes to this project will be documented in this file. - Generate PHPDoc for Laravel 8.x factories [\#1074 / ahmed-aliraqi](https://github.com/barryvdh/laravel-ide-helper/pull/1074) - Add a comment to a property like table columns [\#1168 / biiiiiigmonster](https://github.com/barryvdh/laravel-ide-helper/pull/1168) - Added `post_migrate` hook to run commands after a migration [\#1163 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1163) +- Allow for PhpDoc for macros with union types [\#1148 / riesjart](https://github.com/barryvdh/laravel-ide-helper/pull/1148) ### Fixed - Error when generating helper for invokable classes [\#1124 / standaniels](https://github.com/barryvdh/laravel-ide-helper/pull/1124) diff --git a/src/Macro.php b/src/Macro.php index 9de37e1..53421b4 100644 --- a/src/Macro.php +++ b/src/Macro.php @@ -42,8 +42,16 @@ class Macro extends Method // Add macro parameters if they are missed in original docblock if (!$this->phpdoc->hasTag('param')) { foreach ($method->getParameters() as $parameter) { - $type = $parameter->hasType() ? $parameter->getType()->getName() : 'mixed'; - $type .= $parameter->hasType() && $parameter->getType()->allowsNull() ? '|null' : ''; + $reflectionType = $parameter->getType(); + + $type = $this->concatReflectionTypes($reflectionType); + + /** @psalm-suppress UndefinedClass */ + if ($reflectionType && !$reflectionType instanceof \ReflectionUnionType && $reflectionType->allowsNull()) { + $type .= '|null'; + } + + $type = $type ?: 'mixed'; $name = $parameter->isVariadic() ? '...' : ''; $name .= '$' . $parameter->getName(); @@ -57,14 +65,31 @@ class Macro extends Method $builder = EloquentBuilder::class; $return = $method->getReturnType(); - $type = $return->getName(); - $type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : ''; - $type .= $return->allowsNull() ? '|null' : ''; + $type = $this->concatReflectionTypes($return); + + /** @psalm-suppress UndefinedClass */ + if (!$return instanceof \ReflectionUnionType) { + $type .= $this->root === "\\{$builder}" && $return->getName() === $builder ? '|static' : ''; + $type .= $return->allowsNull() ? '|null' : ''; + } $this->phpdoc->appendTag(Tag::createInstance("@return {$type}")); } } + protected function concatReflectionTypes(?\ReflectionType $type): string + { + /** @psalm-suppress UndefinedClass */ + $returnTypes = $type instanceof \ReflectionUnionType + ? $type->getTypes() + : [$type]; + + return Collection::make($returnTypes) + ->filter() + ->map->getName() + ->implode('|'); + } + protected function addLocationToPhpDoc() { if ($this->method->name === '__invoke') { diff --git a/tests/MacroTest.php b/tests/MacroTest.php index 2d18662..a0fa904 100644 --- a/tests/MacroTest.php +++ b/tests/MacroTest.php @@ -188,6 +188,29 @@ class MacroTest extends TestCase $this->assertEquals('@return \stdClass|null rrrrrrr', $this->tagsToString($phpdoc, 'return')); } + public function testInitPhpDocParamsWithUnionTypes(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('This test requires PHP 8.0 or higher'); + } + + $phpdoc = (new MacroMock())->getPhpDoc(eval(<<<'PHP' + return new ReflectionFunction( + /** + * Test docblock. + */ + function (\Stringable|string $a = null): \Stringable|string|null { + return $a; + } + ); + PHP)); + + $this->assertNotNull($phpdoc); + $this->assertStringContainsString('Test docblock', $phpdoc->getText()); + $this->assertEquals('@param \Stringable|string|null $a', $this->tagsToString($phpdoc, 'param')); + $this->assertEquals('@return \Stringable|string|null', $this->tagsToString($phpdoc, 'return')); + } + protected function tagsToString(DocBlock $docBlock, string $name) { $tags = $docBlock->getTagsByName($name);