From 6f01fae13f5c4b4bcf9ce4ece27e288f15a276fa Mon Sep 17 00:00:00 2001 From: Jaapio Date: Sat, 22 Nov 2025 13:56:51 +0100 Subject: [PATCH] Use type resolver to resolve types in expressions --- composer.json | 2 +- composer.lock | 18 +-- .../Php/Expression/ExpressionPrinter.php | 17 ++- tests/integration/ProjectCreationTest.php | 14 +++ tests/integration/data/Luigi/Pizza.php | 5 +- .../Php/Expression/ExpressionPrinterTest.php | 114 ++++++++++++++++++ 6 files changed, 155 insertions(+), 15 deletions(-) create mode 100644 tests/unit/phpDocumentor/Reflection/Php/Expression/ExpressionPrinterTest.php diff --git a/composer.json b/composer.json index 5240c509..b8c35fbd 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "nikic/php-parser": "~4.18 || ^5.0", "phpdocumentor/reflection-common": "^2.1", "phpdocumentor/reflection-docblock": "^5", - "phpdocumentor/type-resolver": "^1.2", + "phpdocumentor/type-resolver": "^1.4", "symfony/polyfill-php80": "^1.28", "webmozart/assert": "^1.7" }, diff --git a/composer.lock b/composer.lock index 5938297f..3762217b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "febb226458a175a8889e92fc89e662ce", + "content-hash": "f86cf0131d7e7e3ff721badc3b7341f7", "packages": [ { "name": "doctrine/deprecations", @@ -231,16 +231,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "8cbe6100e8971efbf8e2e7da3a202ba83eafd5a3" + "reference": "f626740b38009078de0dc8b2b9dc4e7f749c6eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/8cbe6100e8971efbf8e2e7da3a202ba83eafd5a3", - "reference": "8cbe6100e8971efbf8e2e7da3a202ba83eafd5a3", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/f626740b38009078de0dc8b2b9dc4e7f749c6eba", + "reference": "f626740b38009078de0dc8b2b9dc4e7f749c6eba", "shasum": "" }, "require": { @@ -283,9 +283,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.11.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.11.1" }, - "time": "2025-11-19T20:28:58+00:00" + "time": "2025-11-21T11:31:57+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -3074,14 +3074,14 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": "8.1.*|8.2.*|8.3.*|8.4.*", "composer-runtime-api": "^2" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.1.0" }, diff --git a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php index a916a935..6c2d3ae0 100644 --- a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php +++ b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php @@ -17,7 +17,9 @@ use phpDocumentor\Reflection\FqsenResolver; use phpDocumentor\Reflection\Php\Expression; use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; use phpDocumentor\Reflection\Types\Context; +use phpDocumentor\Reflection\Types\Object_; use PhpParser\Node\Expr; use PhpParser\Node\Name; use PhpParser\PrettyPrinter\Standard; @@ -27,14 +29,16 @@ final class ExpressionPrinter extends Standard /** @var array */ private array $parts = []; private Context|null $context = null; - private FqsenResolver $fqsenResolver; + private TypeResolver $typeResolver; /** {@inheritDoc} */ public function __construct(array $options = []) { parent::__construct($options); - $this->fqsenResolver = new FqsenResolver(); + $this->typeResolver = new TypeResolver( + new FqsenResolver(), + ); } protected function resetState(): void @@ -53,9 +57,14 @@ public function prettyPrintExpr(Expr $node, Context|null $context = null): strin protected function pName(Name $node): string { - $renderedName = $this->fqsenResolver->resolve(parent::pName($node), $this->context); + $renderedName = $this->typeResolver->resolve(parent::pName($node), $this->context); $placeholder = Expression::generatePlaceholder((string) $renderedName); - $this->parts[$placeholder] = $renderedName; + + if ($renderedName instanceof Object_ && $renderedName->getFqsen() !== null) { + $this->parts[$placeholder] = $renderedName->getFqsen(); + } else { + $this->parts[$placeholder] = $renderedName; + } return $placeholder; } diff --git a/tests/integration/ProjectCreationTest.php b/tests/integration/ProjectCreationTest.php index fe545d35..7cd1701a 100644 --- a/tests/integration/ProjectCreationTest.php +++ b/tests/integration/ProjectCreationTest.php @@ -20,6 +20,7 @@ use phpDocumentor\Reflection\Php\Function_; use phpDocumentor\Reflection\Php\ProjectFactory; use phpDocumentor\Reflection\Types\Integer; +use phpDocumentor\Reflection\Types\Null_; use phpDocumentor\Reflection\Types\Object_; use phpDocumentor\Reflection\Types\String_; @@ -129,6 +130,19 @@ public function testWithNamespacedClass() : void new Object_(new Fqsen('\\Luigi\\Pizza\Style')), $methods['\\Luigi\\Pizza::__construct()']->getArguments()[0]->getType() ); + + $sauceArgument = $methods['\\Luigi\\Pizza::__construct()']->getArguments()[1]; + $this->assertEquals('sauce', $sauceArgument->getName()); + $this->assertEquals( + new Php\Expression( + '{{ PHPDOC37a6259cc0c1dae299a7866489dff0bd }}', + [ + '{{ PHPDOC37a6259cc0c1dae299a7866489dff0bd }}' => new Null_(), + ], + ), + $sauceArgument->getDefault(false) + ); + } public function testDocblockOfMethodIsProcessed() : void diff --git a/tests/integration/data/Luigi/Pizza.php b/tests/integration/data/Luigi/Pizza.php index 0c7ab328..a25a4d7d 100644 --- a/tests/integration/data/Luigi/Pizza.php +++ b/tests/integration/data/Luigi/Pizza.php @@ -11,6 +11,9 @@ namespace Luigi; +use Luigi\Pizza\Sauce; +use Luigi\Pizza\TomatoSauce; + #[\Food("Pizza")] #[\Food(country: "Italy", originDate: Pizza::class)] class Pizza extends \Pizza @@ -46,7 +49,7 @@ class Pizza extends \Pizza /** @var string $deliveryMethod Is the customer picking this pizza up or must it be delivered? */ $deliveryMethod; - private function __construct(Pizza\Style $style) + private function __construct(Pizza\Style $style, Sauce|null $sauce = null) { $this->style = $style; } diff --git a/tests/unit/phpDocumentor/Reflection/Php/Expression/ExpressionPrinterTest.php b/tests/unit/phpDocumentor/Reflection/Php/Expression/ExpressionPrinterTest.php new file mode 100644 index 00000000..591af00c --- /dev/null +++ b/tests/unit/phpDocumentor/Reflection/Php/Expression/ExpressionPrinterTest.php @@ -0,0 +1,114 @@ + $expectedParts */ + #[DataProvider('argumentProvider')] + public function testArgumentIsParsed(string $code, string $expectedExpression, array $expectedParts): void + { + $parser = (new ParserFactory())->createForHostVersion(); + $node = $parser->parse($code); + $printer = new ExpressionPrinter(); + + $expression = $printer->prettyPrintExpr($node[0]->params[0]->default); + + self::assertSame($expectedExpression, $expression); + self::assertEquals($expectedParts, $printer->getParts()); + } + + /** + * @return array + * }> + */ + public static function argumentProvider(): array + { + return [ + 'myClassDefault' => [ + 'code' => ' 'new {{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}()', + 'expectedParts' => [ + '{{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}' => new Fqsen('\MyClass'), + ], + ], + // Enum case default. + // After first run, replace ENUM_PLACEHOLDER below with the actual placeholder shown in the failure output. + 'enumCaseDefault' => [ + 'code' => ' '{{ PHPDOC8844445ee68bb81ea3fd9529f906598b }}', + 'expectedParts' => [ + '{{ PHPDOC8844445ee68bb81ea3fd9529f906598b }}' => new Fqsen('\MyEnum::CaseA'), + ], + ], + 'classConstantDefault' => [ + 'code' => ' '{{ PHPDOCe54b7c24dd0f847c3193039223751b3d }}', + 'expectedParts' => [ + '{{ PHPDOCe54b7c24dd0f847c3193039223751b3d }}' => new Fqsen('\MyClass::SOME_CONST'), + ], + ], + 'stringDefault' => [ + 'code' => ' "'hello'", + 'expectedParts' => [], + ], + 'intDefault' => [ + 'code' => ' '42', + 'expectedParts' => [], + ], + 'booleanDefault' => [ + 'code' => ' '{{ PHPDOCb326b5062b2f0e69046810717534cb09 }}', + 'expectedParts' => [ + '{{ PHPDOCb326b5062b2f0e69046810717534cb09 }}' => new True_(), + ], + ], + 'nullDefault' => [ + 'code' => ' '{{ PHPDOC37a6259cc0c1dae299a7866489dff0bd }}', + 'expectedParts' => [ + '{{ PHPDOC37a6259cc0c1dae299a7866489dff0bd }}' => new Null_(), + ], + ], + 'emptyArrayDefault' => [ + 'code' => ' '[]', + 'expectedParts' => [], + ], + 'intArrayDefault' => [ + 'code' => ' '[1, 2]', + 'expectedParts' => [], + ], + 'stringArrayDefault' => [ + 'code' => ' "['hello', 'world']", + 'expectedParts' => [], + ], + 'objectArrayDefault' => [ + 'code' => ' '[new {{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}(), new {{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}()]', + 'expectedParts' => [ + '{{ PHPDOC9f1f93179bc4ea54e11fc3cda63a284f }}' => new Fqsen('\MyClass'), + ], + ], + ]; + } +}