From 2cb3adf29235aefcbb2306b39eae9565a9a8ba40 Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Fri, 9 Dec 2022 22:57:48 +0100 Subject: [PATCH 01/10] Started reworking expression strings into Expression objects Values of constants, named arguments, etc, should be parsed to include types and FQSENs that can be used in phpDocumentor to display links with. Since these are now simple strings, I have refactored this into a template string system with placeholder values that a consumer could replace with its own values. --- src/phpDocumentor/Reflection/Php/Argument.php | 23 +++++++- src/phpDocumentor/Reflection/Php/Constant.php | 24 +++++++-- src/phpDocumentor/Reflection/Php/EnumCase.php | 24 ++++++++- .../Reflection/Php/Expression.php | 42 +++++++++++++++ .../Php/Expression/ExpressionPrinter.php | 43 +++++++++++++++ .../Reflection/Php/Factory/Argument.php | 0 .../Reflection/Php/Factory/ClassConstant.php | 17 +++++- .../Reflection/Php/Factory/Define.php | 14 ++++- .../Reflection/Php/Factory/EnumCase.php | 9 +++- .../Reflection/Php/Factory/GlobalConstant.php | 17 +++++- .../Reflection/Php/Factory/Property.php | 10 +++- .../Reflection/Php/ProjectFactory.php | 6 ++- src/phpDocumentor/Reflection/Php/Property.php | 24 +++++++-- .../PHP8/ConstructorPromotionTest.php | 54 +++++++++++++++---- .../data/PHP8/ConstructorPromotion.php | 4 ++ 15 files changed, 283 insertions(+), 28 deletions(-) create mode 100644 src/phpDocumentor/Reflection/Php/Expression.php create mode 100644 src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php create mode 100644 src/phpDocumentor/Reflection/Php/Factory/Argument.php diff --git a/src/phpDocumentor/Reflection/Php/Argument.php b/src/phpDocumentor/Reflection/Php/Argument.php index 25fb6010..7a674f60 100644 --- a/src/phpDocumentor/Reflection/Php/Argument.php +++ b/src/phpDocumentor/Reflection/Php/Argument.php @@ -34,7 +34,7 @@ public function __construct( private readonly string $name, Type|null $type = null, /** @var string|null the default value for an argument or null if none is provided */ - private readonly string|null $default = null, + private Expression|string|null $default = null, /** @var bool whether the argument passes the parameter by reference instead of by value */ private readonly bool $byReference = false, /** @var bool Determines if this Argument represents a variadic argument */ @@ -44,6 +44,15 @@ public function __construct( $type = new Mixed_(); } + if (is_string($this->default)) { + trigger_error( + 'Default values for arguments should be of type Expression, support for strings will be ' + . 'removed in 7.x', + E_USER_DEPRECATED + ); + $this->default = new Expression($this->default, []); + } + $this->type = $type; } @@ -60,8 +69,18 @@ public function getType(): Type|null return $this->type; } - public function getDefault(): string|null + /** */ + public function getDefault(bool $asString = true): string|null { + if ($asString) { + trigger_error( + 'The Default value will become of type Expression by default', + E_USER_DEPRECATED + ); + + return (string) $this->default; + } + return $this->default; } diff --git a/src/phpDocumentor/Reflection/Php/Constant.php b/src/phpDocumentor/Reflection/Php/Constant.php index 3d9bf700..9af7fb36 100644 --- a/src/phpDocumentor/Reflection/Php/Constant.php +++ b/src/phpDocumentor/Reflection/Php/Constant.php @@ -42,7 +42,7 @@ final class Constant implements Element, MetaDataContainerInterface, AttributeCo public function __construct( private readonly Fqsen $fqsen, private readonly DocBlock|null $docBlock = null, - private readonly string|null $value = null, + private Expression|string|null $value = null, Location|null $location = null, Location|null $endLocation = null, Visibility|null $visibility = null, @@ -51,13 +51,31 @@ public function __construct( $this->location = $location ?: new Location(-1); $this->endLocation = $endLocation ?: new Location(-1); $this->visibility = $visibility ?: new Visibility(Visibility::PUBLIC_); + + if (is_string($this->value)) { + trigger_error( + 'Constant values should be of type Expression, support for strings will be ' + . 'removed in 6.x', + E_USER_DEPRECATED + ); + $this->value = new Expression($this->value, []); + } } /** - * Returns the value of this constant. + * Returns the expression value for this constant. */ - public function getValue(): string|null + public function getValue(bool $asString = true): Expression|string|null { + if ($asString) { + trigger_error( + 'The expression value will become of type Expression by default', + E_USER_DEPRECATED + ); + + return (string) $this->value; + } + return $this->value; } diff --git a/src/phpDocumentor/Reflection/Php/EnumCase.php b/src/phpDocumentor/Reflection/Php/EnumCase.php index 1d30fc36..00bb859e 100644 --- a/src/phpDocumentor/Reflection/Php/EnumCase.php +++ b/src/phpDocumentor/Reflection/Php/EnumCase.php @@ -30,7 +30,7 @@ public function __construct( private readonly DocBlock|null $docBlock, Location|null $location = null, Location|null $endLocation = null, - private readonly string|null $value = null, + private Expression|string|null $value = null, ) { if ($location === null) { $location = new Location(-1); @@ -42,6 +42,14 @@ public function __construct( $this->location = $location; $this->endLocation = $endLocation; + if (is_string($this->value)) { + trigger_error( + 'Expression values for enum cases should be of type Expression, support for strings will be ' + . 'removed in 7.x', + E_USER_DEPRECATED + ); + $this->value = new Expression($this->value, []); + } } #[Override] @@ -71,8 +79,20 @@ public function getEndLocation(): Location return $this->endLocation; } - public function getValue(): string|null + /** + * Returns the value for this enum case. + */ + public function getValue(bool $asString = true): Expression|string|null { + if ($asString) { + trigger_error( + 'The enum case value will become of type Expression by default', + E_USER_DEPRECATED + ); + + return (string) $this->value; + } + return $this->value; } } diff --git a/src/phpDocumentor/Reflection/Php/Expression.php b/src/phpDocumentor/Reflection/Php/Expression.php new file mode 100644 index 00000000..cacfe385 --- /dev/null +++ b/src/phpDocumentor/Reflection/Php/Expression.php @@ -0,0 +1,42 @@ + */ + private array $parts; + + public function __construct(string $expression, array $parts) + { + $this->expression = $expression; + $this->parts = $parts; + } + + public function getExpression(): string + { + return $this->expression; + } + + public function getParts(): array + { + return $this->parts; + } + + public function __toString(): string + { + $valuesAsStrings = array_map( + static fn(object $part): string => (string)$part, + $this->parts + ); + + return str_replace(array_keys($this->parts), $valuesAsStrings, $this->expression); + } +} diff --git a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php new file mode 100644 index 00000000..aa0a4c35 --- /dev/null +++ b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php @@ -0,0 +1,43 @@ + + */ + private array $parts = []; + + protected function resetState(): void + { + parent::resetState(); + + $this->parts = []; + } + + protected function pName(Name $node): string + { + $renderedName = parent::pName($node); + $code = md5($renderedName); + + $placeholder = '{{ PHPDOC' . $code . ' }}'; + $this->parts[$placeholder] = new Fqsen('\\' . $node); + + return $placeholder; + } + + /** + * @return array + */ + public function getParts(): array + { + return $this->parts; + } +} diff --git a/src/phpDocumentor/Reflection/Php/Factory/Argument.php b/src/phpDocumentor/Reflection/Php/Factory/Argument.php new file mode 100644 index 00000000..e69de29b diff --git a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php index 9c594109..13d8abe1 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php +++ b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php @@ -20,6 +20,8 @@ use phpDocumentor\Reflection\Php\Constant as ConstantElement; use phpDocumentor\Reflection\Php\Enum_; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; +use phpDocumentor\Reflection\Php\Expression; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\Interface_; use phpDocumentor\Reflection\Php\StrategyContainer; use phpDocumentor\Reflection\Php\Trait_; @@ -84,7 +86,7 @@ protected function doCreate( $constant = new ConstantElement( $const->getFqsen(), $this->createDocBlock($const->getDocComment(), $context->getTypeContext()), - $const->getValue() !== null ? $this->valueConverter->prettyPrintExpr($const->getValue()) : null, + $this->determineValue($const), new Location($const->getLine()), new Location($const->getEndLine()), $this->buildVisibility($const), @@ -105,6 +107,19 @@ protected function doCreate( return null; } + private function determineValue(ClassConstantIterator $value): null|Expression + { + $expression = $value->getValue() !== null ? $this->valueConverter->prettyPrintExpr($value->getValue()) : null; + if ($this->valueConverter instanceof ExpressionPrinter) { + $expression = new Expression($expression, $this->valueConverter->getParts()); + } + if (is_string($expression)) { + $expression = new Expression($expression, []); + } + + return $expression; + } + /** * Converts the visibility of the constant to a valid Visibility object. */ diff --git a/src/phpDocumentor/Reflection/Php/Factory/Define.php b/src/phpDocumentor/Reflection/Php/Factory/Define.php index 28a4fe36..451342c9 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Define.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Define.php @@ -18,6 +18,8 @@ use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Php\Constant as ConstantElement; +use phpDocumentor\Reflection\Php\Expression as ValueExpression; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\File as FileElement; use phpDocumentor\Reflection\Php\StrategyContainer; use phpDocumentor\Reflection\Php\ValueEvaluator\ConstantEvaluator; @@ -118,13 +120,21 @@ protected function doCreate( return $constant; } - private function determineValue(Arg|null $value): string|null + private function determineValue(Arg|null $value): ValueExpression|null { if ($value === null) { return null; } - return $this->valueConverter->prettyPrintExpr($value->value); + $expression = $value->value !== null ? $this->valueConverter->prettyPrintExpr($value->value) : null; + if ($this->valueConverter instanceof ExpressionPrinter) { + $expression = new ValueExpression($expression, $this->valueConverter->getParts()); + } + if (is_string($expression)) { + $expression = new ValueExpression($expression, []); + } + + return $expression; } private function determineFqsen(Arg $name, ContextStack $context): Fqsen|null diff --git a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php index 14338d94..90726f29 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php +++ b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php @@ -10,6 +10,8 @@ use phpDocumentor\Reflection\Php\Enum_ as EnumElement; use phpDocumentor\Reflection\Php\EnumCase as EnumCaseElement; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; +use phpDocumentor\Reflection\Php\Expression; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\StrategyContainer; use PhpParser\Node\Stmt\EnumCase as EnumCaseNode; use PhpParser\PrettyPrinter\Standard as PrettyPrinter; @@ -41,12 +43,17 @@ protected function doCreate(ContextStack $context, object $object, StrategyConta $enum = $context->peek(); assert($enum instanceof EnumElement); + $value = $object->expr !== null ? $this->prettyPrinter->prettyPrintExpr($object->expr) : null; + if ($this->prettyPrinter instanceof ExpressionPrinter) { + $value = new Expression($value, $this->prettyPrinter->getParts()); + } + $case = new EnumCaseElement( $object->getAttribute('fqsen'), $docBlock, new Location($object->getLine()), new Location($object->getEndLine()), - $object->expr !== null ? $this->prettyPrinter->prettyPrintExpr($object->expr) : null, + $value, ); $enum->addCase($case); diff --git a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php index fc1e04ac..236c50db 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php +++ b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php @@ -17,6 +17,8 @@ use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Php\Constant as ConstantElement; +use phpDocumentor\Reflection\Php\Expression; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\File as FileElement; use phpDocumentor\Reflection\Php\StrategyContainer; use PhpParser\Node\Stmt\Const_; @@ -70,7 +72,7 @@ protected function doCreate( new ConstantElement( $const->getFqsen(), $this->createDocBlock($const->getDocComment(), $context->getTypeContext()), - $const->getValue() !== null ? $this->valueConverter->prettyPrintExpr($const->getValue()) : null, + $this->determineValue($const), new Location($const->getLine()), new Location($const->getEndLine()), ), @@ -79,4 +81,17 @@ protected function doCreate( return null; } + + private function determineValue(GlobalConstantIterator $value): ?Expression + { + $expression = $value->getValue() !== null ? $this->valueConverter->prettyPrintExpr($value->getValue()) : null; + if ($this->valueConverter instanceof ExpressionPrinter) { + $expression = new Expression($expression, $this->valueConverter->getParts()); + } + if (is_string($expression)) { + $expression = new Expression($expression, []); + } + + return $expression; + } } diff --git a/src/phpDocumentor/Reflection/Php/Factory/Property.php b/src/phpDocumentor/Reflection/Php/Factory/Property.php index 4c146677..253c28d0 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Property.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Property.php @@ -18,6 +18,9 @@ use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Php\Class_; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; +use phpDocumentor\Reflection\Php\Expression; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; +use phpDocumentor\Reflection\Php\ProjectFactoryStrategy; use phpDocumentor\Reflection\Php\Property as PropertyDescriptor; use phpDocumentor\Reflection\Php\StrategyContainer; use phpDocumentor\Reflection\Php\Trait_; @@ -74,6 +77,11 @@ protected function doCreate( $iterator = new PropertyIterator($object); foreach ($iterator as $stmt) { + $default = $object->default !== null ? $this->valueConverter->prettyPrintExpr($object->default) : null; + if ($this->valueConverter instanceof ExpressionPrinter) { + $default = new Expression($default, $this->valueConverter->getParts()); + } + $property = PropertyBuilder::create( $this->valueConverter, $this->docBlockFactory, @@ -84,7 +92,7 @@ protected function doCreate( ->visibility($stmt) ->type($stmt->getType()) ->docblock($stmt->getDocComment()) - ->default($iterator->getDefault()) + ->default($default) ->static($stmt->isStatic()) ->startLocation(new Location($stmt->getLine())) ->endLocation(new Location($stmt->getEndLine())) diff --git a/src/phpDocumentor/Reflection/Php/ProjectFactory.php b/src/phpDocumentor/Reflection/Php/ProjectFactory.php index dd461b76..7d7a34bd 100644 --- a/src/phpDocumentor/Reflection/Php/ProjectFactory.php +++ b/src/phpDocumentor/Reflection/Php/ProjectFactory.php @@ -18,6 +18,7 @@ use phpDocumentor\Reflection\Exception; use phpDocumentor\Reflection\File as SourceFile; use phpDocumentor\Reflection\Fqsen; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\Factory\Class_; use phpDocumentor\Reflection\Php\Factory\ClassConstant; use phpDocumentor\Reflection\Php\Factory\ConstructorPromotion; @@ -67,6 +68,7 @@ public function __construct(array|ProjectFactoryStrategies $strategies) public static function createInstance(): self { $docblockFactory = DocBlockFactory::createInstance(); + $expressionPrinter = new ExpressionPrinter(); $attributeReducer = new Attribute(); $parameterReducer = new Parameter(new PrettyPrinter()); @@ -86,7 +88,7 @@ public static function createInstance(): self new Function_($docblockFactory, [$attributeReducer, $parameterReducer]), new Interface_($docblockFactory, [$attributeReducer]), $methodStrategy, - new Property($docblockFactory, new PrettyPrinter(), [$attributeReducer, $parameterReducer]), + new Property($docblockFactory, $expressionPrinter, [$attributeReducer, $parameterReducer]), new Trait_($docblockFactory, [$attributeReducer]), new IfStatement(), @@ -95,7 +97,7 @@ public static function createInstance(): self ); $strategies->addStrategy( - new ConstructorPromotion($methodStrategy, $docblockFactory, new PrettyPrinter(), [$attributeReducer, $parameterReducer]), + new ConstructorPromotion($methodStrategy, $docblockFactory, $expressionPrinter, [$attributeReducer, $parameterReducer]), 1100, ); $strategies->addStrategy(new Noop(), -PHP_INT_MAX); diff --git a/src/phpDocumentor/Reflection/Php/Property.php b/src/phpDocumentor/Reflection/Php/Property.php index 2f72d5f0..68868026 100644 --- a/src/phpDocumentor/Reflection/Php/Property.php +++ b/src/phpDocumentor/Reflection/Php/Property.php @@ -48,7 +48,7 @@ public function __construct( private readonly Fqsen $fqsen, Visibility|null $visibility = null, private readonly DocBlock|null $docBlock = null, - private readonly string|null $default = null, + private Expression|string|null $default = null, private readonly bool $static = false, Location|null $location = null, Location|null $endLocation = null, @@ -60,13 +60,31 @@ public function __construct( $this->visibility = $visibility ?: new Visibility('public'); $this->location = $location ?: new Location(-1); $this->endLocation = $endLocation ?: new Location(-1); + + if (is_string($this->default)) { + trigger_error( + 'Default values for properties should be of type Expression, support for strings will be ' + . 'removed in 7.x', + E_USER_DEPRECATED + ); + $this->default = new Expression($this->default, []); + } } /** - * returns the default value of this property. + * Returns the default value for this property. */ - public function getDefault(): string|null + public function getDefault(bool $asString = true): Expression|string|null { + if ($asString) { + trigger_error( + 'The Default value will become of type Expression by default', + E_USER_DEPRECATED + ); + + return (string) $this->default; + } + return $this->default; } diff --git a/tests/integration/PHP8/ConstructorPromotionTest.php b/tests/integration/PHP8/ConstructorPromotionTest.php index 46e3b84f..46c035f8 100644 --- a/tests/integration/PHP8/ConstructorPromotionTest.php +++ b/tests/integration/PHP8/ConstructorPromotionTest.php @@ -4,6 +4,8 @@ namespace integration\PHP8; +use DateTimeImmutable; +use DateTimeImmutable; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Tags\Param; use phpDocumentor\Reflection\DocBlock\Tags\Var_; @@ -16,6 +18,7 @@ use phpDocumentor\Reflection\Php\Project; use phpDocumentor\Reflection\Php\Property; use phpDocumentor\Reflection\Php\Visibility; +use phpDocumentor\Reflection\Types\Array_; use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\Object_; use phpDocumentor\Reflection\Types\String_; @@ -52,22 +55,26 @@ public function testPropertiesAreCreated() : void $constructor = $this->expectedContructorMethod(); $constructor->addArgument(new Argument('name', new String_(), "'default name'")); $constructor->addArgument(new Argument('email', new Object_(new Fqsen('\\PHP8\\Email')))); - $constructor->addArgument(new Argument('birth_date', new Object_(new Fqsen('\\' . \DateTimeImmutable::class)))); + $constructor->addArgument(new Argument('birth_date', new Object_(new Fqsen('\\' . DateTimeImmutable::class)))); + $constructor->addArgument(new Argument('created_at', new Object_(new Fqsen('\\' . DateTimeImmutable::class)))); + $constructor->addArgument(new Argument('uses_constants', new Array_())); self::assertEquals($constructor, $class->getMethods()['\PHP8\ConstructorPromotion::__construct()']); self::assertEquals( [ '\PHP8\ConstructorPromotion::$name' => $this->expectedNameProperty(), '\PHP8\ConstructorPromotion::$email' => $this->expectedEmailProperty(), - '\PHP8\ConstructorPromotion::$birth_date' => $this->expectedBirthDateProperty() + '\PHP8\ConstructorPromotion::$birth_date' => $this->expectedBirthDateProperty(), + '\PHP8\ConstructorPromotion::$created_at' => $this->expectedCreatedAtProperty(), + '\PHP8\ConstructorPromotion::$uses_constants' => $this->expectedUsesConstantsProperty(), ], $class->getProperties() ); } - private function expectedContructorMethod(): Method + private function expectedConstructorMethod(): Method { - $constructor = new Method( + return new Method( new Fqsen('\PHP8\ConstructorPromotion::__construct()'), new Visibility(Visibility::PUBLIC_), new DocBlock( @@ -86,10 +93,9 @@ private function expectedContructorMethod(): Method false, false, false, - new Location(16, 218), - new Location(27, 517) + new Location(18, 218), + new Location(31, 522) ); - return $constructor; } private function expectedNameProperty(): Property @@ -137,10 +143,38 @@ private function expectedBirthDateProperty(): Property null, null, false, - new Location(26, 471), - new Location(26, 507), - new Object_(new Fqsen('\\' . \DateTimeImmutable::class)) + new Location(26), + new Location(26), + new Object_(new Fqsen('\\' . DateTimeImmutable::class)) ); return $birthDate; } + + private function expectedCreatedAtProperty(): Property + { + return new Property( + new Fqsen('\PHP8\ConstructorPromotion::$created_at'), + new Visibility(Visibility::PRIVATE_), + null, + null, + false, + new Location(26), + new Location(26), + new Object_(new Fqsen('\\' . DateTimeImmutable::class)) + ); + } + + private function expectedUsesConstantsProperty(): Property + { + return new Property( + new Fqsen('\PHP8\ConstructorPromotion::$uses_constants'), + new Visibility(Visibility::PRIVATE_), + null, + null, + false, + new Location(26), + new Location(26), + new Array_() + ); + } } diff --git a/tests/integration/data/PHP8/ConstructorPromotion.php b/tests/integration/data/PHP8/ConstructorPromotion.php index 494ddbe0..23eebcf4 100644 --- a/tests/integration/data/PHP8/ConstructorPromotion.php +++ b/tests/integration/data/PHP8/ConstructorPromotion.php @@ -8,6 +8,8 @@ class ConstructorPromotion { + private const DEFAULT_VALUE = 'default'; + /** * Constructor with promoted properties * @@ -24,5 +26,7 @@ public function __construct( public string $name = 'default name', protected Email $email, private DateTimeImmutable $birth_date, + private DateTimeImmutable $created_at = new DateTimeImmutable('now'), + private array $uses_constants = [self::DEFAULT_VALUE], ) {} } From fc9863f85f686bc6318f16c3223f78d60c2e2a21 Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Sat, 10 Dec 2022 22:03:27 +0100 Subject: [PATCH 02/10] Type juggle a bit more in the factories The conversion from PHP-Parser's node to an Expression needs to take into account whether the provided parser is an Expression parser or the usual Standard parser. If it is an ExpressionParser, we can create a real Expression out of it --- src/phpDocumentor/Reflection/Php/Argument.php | 4 +++ src/phpDocumentor/Reflection/Php/Constant.php | 4 +++ src/phpDocumentor/Reflection/Php/EnumCase.php | 4 +++ .../Reflection/Php/Factory/Property.php | 26 +++++++++++++------ src/phpDocumentor/Reflection/Php/Property.php | 4 +++ .../Reflection/Php/Factory/PropertyTest.php | 4 +-- 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/phpDocumentor/Reflection/Php/Argument.php b/src/phpDocumentor/Reflection/Php/Argument.php index 7a674f60..6261b59d 100644 --- a/src/phpDocumentor/Reflection/Php/Argument.php +++ b/src/phpDocumentor/Reflection/Php/Argument.php @@ -72,6 +72,10 @@ public function getType(): Type|null /** */ public function getDefault(bool $asString = true): string|null { + if ($this->default === null) { + return null; + } + if ($asString) { trigger_error( 'The Default value will become of type Expression by default', diff --git a/src/phpDocumentor/Reflection/Php/Constant.php b/src/phpDocumentor/Reflection/Php/Constant.php index 9af7fb36..742a53af 100644 --- a/src/phpDocumentor/Reflection/Php/Constant.php +++ b/src/phpDocumentor/Reflection/Php/Constant.php @@ -67,6 +67,10 @@ public function __construct( */ public function getValue(bool $asString = true): Expression|string|null { + if ($this->value === null) { + return null; + } + if ($asString) { trigger_error( 'The expression value will become of type Expression by default', diff --git a/src/phpDocumentor/Reflection/Php/EnumCase.php b/src/phpDocumentor/Reflection/Php/EnumCase.php index 00bb859e..c0bf089e 100644 --- a/src/phpDocumentor/Reflection/Php/EnumCase.php +++ b/src/phpDocumentor/Reflection/Php/EnumCase.php @@ -84,6 +84,10 @@ public function getEndLocation(): Location */ public function getValue(bool $asString = true): Expression|string|null { + if ($this->value === null) { + return null; + } + if ($asString) { trigger_error( 'The enum case value will become of type Expression by default', diff --git a/src/phpDocumentor/Reflection/Php/Factory/Property.php b/src/phpDocumentor/Reflection/Php/Factory/Property.php index 253c28d0..60628e02 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Property.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Property.php @@ -20,7 +20,6 @@ use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; use phpDocumentor\Reflection\Php\Expression; use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; -use phpDocumentor\Reflection\Php\ProjectFactoryStrategy; use phpDocumentor\Reflection\Php\Property as PropertyDescriptor; use phpDocumentor\Reflection\Php\StrategyContainer; use phpDocumentor\Reflection\Php\Trait_; @@ -77,11 +76,6 @@ protected function doCreate( $iterator = new PropertyIterator($object); foreach ($iterator as $stmt) { - $default = $object->default !== null ? $this->valueConverter->prettyPrintExpr($object->default) : null; - if ($this->valueConverter instanceof ExpressionPrinter) { - $default = new Expression($default, $this->valueConverter->getParts()); - } - $property = PropertyBuilder::create( $this->valueConverter, $this->docBlockFactory, @@ -92,7 +86,7 @@ protected function doCreate( ->visibility($stmt) ->type($stmt->getType()) ->docblock($stmt->getDocComment()) - ->default($default) + ->default($this->determineDefault($stmt)) ->static($stmt->isStatic()) ->startLocation(new Location($stmt->getLine())) ->endLocation(new Location($stmt->getEndLine())) @@ -109,8 +103,24 @@ protected function doCreate( } $propertyContainer->addProperty($property); + + } + } + + private function determineDefault(PropertyIterator $value): Expression|null + { + $expression = $value->getDefault() !== null + ? $this->valueConverter->prettyPrintExpr($value->getDefault()) + : null; + + if ($this->valueConverter instanceof ExpressionPrinter) { + $expression = new Expression($expression, $this->valueConverter->getParts()); + } + + if (is_string($expression)) { + $expression = new Expression($expression, []); } - return null; + return $expression; } } diff --git a/src/phpDocumentor/Reflection/Php/Property.php b/src/phpDocumentor/Reflection/Php/Property.php index 68868026..6a8e38bb 100644 --- a/src/phpDocumentor/Reflection/Php/Property.php +++ b/src/phpDocumentor/Reflection/Php/Property.php @@ -76,6 +76,10 @@ public function __construct( */ public function getDefault(bool $asString = true): Expression|string|null { + if ($this->default === null) { + return null; + } + if ($asString) { trigger_error( 'The Default value will become of type Expression by default', diff --git a/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyTest.php b/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyTest.php index 42c5a722..dee73daf 100644 --- a/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyTest.php +++ b/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyTest.php @@ -63,9 +63,9 @@ public function testMatches(): void #[DataProvider('visibilityProvider')] public function testCreateWithVisibility(int $input, string $expectedVisibility): void { - $constantStub = $this->buildPropertyMock($input); + $propertyStub = $this->buildPropertyMock($input); - $class = $this->performCreate($constantStub); + $class = $this->performCreate($propertyStub); $property = current($class->getProperties()); $this->assertProperty($property, $expectedVisibility); From 435f6a3e3bf44cd5269585fd7a0b53b14fae380b Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Sat, 10 Dec 2022 22:28:08 +0100 Subject: [PATCH 03/10] Update docs and removed useless files too --- docs/expressions.rst | 26 +++++++++++++++++++ docs/index.rst | 1 + src/phpDocumentor/Reflection/Php/Argument.php | 5 ++++ src/phpDocumentor/Reflection/Php/Constant.php | 5 ++++ src/phpDocumentor/Reflection/Php/EnumCase.php | 5 ++++ .../Reflection/Php/Expression.php | 6 ++++- .../Php/Expression/ExpressionPrinter.php | 6 ++--- .../Reflection/Php/Factory/ClassConstant.php | 3 +++ .../Reflection/Php/Factory/Define.php | 2 ++ .../Reflection/Php/Factory/GlobalConstant.php | 3 +++ .../Reflection/Php/Factory/Property.php | 2 ++ .../Reflection/Php/ProjectFactory.php | 1 - src/phpDocumentor/Reflection/Php/Property.php | 5 ++++ 13 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 docs/expressions.rst diff --git a/docs/expressions.rst b/docs/expressions.rst new file mode 100644 index 00000000..b14be356 --- /dev/null +++ b/docs/expressions.rst @@ -0,0 +1,26 @@ +Expressions +=========== + +Starting with version 5.4, we now support parsing expressions and extracting types and references to elements from them. + +.. info:: + + An expression is, for example, the default value for a property or argument, the definition of an enum case or a + constant value. These are called expressions and can contain more complex combinations of operators and values. + +As this library revolves around reflecting Static information, most parts of an expression are considered irrelevant; +except for type information -such as type hints- and references to other elements, such as constants. As such, whenever +an expression is interpreted, it will result in a string containing placeholders and an array containing the reflected +parts -such as FQSENs-. + +This means that the getters like ``getDefault()`` will return a string or when you provide the optional argument +$isString as being false, it will return an Expression object; which, when cast to string, will provide the same result. + +.. warning:: + + Deprecation: In version 6, we will remove the optional argument and always return an Expression object. When the + result was used as a string nothing will change, but code that checks if the output is a string will no longer + function starting from that version. + +This will allow consumers to be able to extract types and links to elements from expressions. This allows consumers to, +for example, interpret the default value for a constructor promoted properties when it directly instantiates an object. diff --git a/docs/index.rst b/docs/index.rst index 52b3506e..64982f17 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,4 +25,5 @@ are however several advantages to using this library: getting-started reflection-structure + expressions extending/index diff --git a/src/phpDocumentor/Reflection/Php/Argument.php b/src/phpDocumentor/Reflection/Php/Argument.php index 6261b59d..23f520a0 100644 --- a/src/phpDocumentor/Reflection/Php/Argument.php +++ b/src/phpDocumentor/Reflection/Php/Argument.php @@ -16,6 +16,11 @@ use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Types\Mixed_; +use function is_string; +use function trigger_error; + +use const E_USER_DEPRECATED; + /** * Descriptor representing a single Argument of a method or function. * diff --git a/src/phpDocumentor/Reflection/Php/Constant.php b/src/phpDocumentor/Reflection/Php/Constant.php index 742a53af..a134f144 100644 --- a/src/phpDocumentor/Reflection/Php/Constant.php +++ b/src/phpDocumentor/Reflection/Php/Constant.php @@ -20,6 +20,11 @@ use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface; +use function is_string; +use function trigger_error; + +use const E_USER_DEPRECATED; + /** * Descriptor representing a constant * diff --git a/src/phpDocumentor/Reflection/Php/EnumCase.php b/src/phpDocumentor/Reflection/Php/EnumCase.php index c0bf089e..1fcb2fe8 100644 --- a/src/phpDocumentor/Reflection/Php/EnumCase.php +++ b/src/phpDocumentor/Reflection/Php/EnumCase.php @@ -11,6 +11,11 @@ use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface; +use function is_string; +use function trigger_error; + +use const E_USER_DEPRECATED; + /** * Represents a case in an Enum. * diff --git a/src/phpDocumentor/Reflection/Php/Expression.php b/src/phpDocumentor/Reflection/Php/Expression.php index cacfe385..c2627ddf 100644 --- a/src/phpDocumentor/Reflection/Php/Expression.php +++ b/src/phpDocumentor/Reflection/Php/Expression.php @@ -7,6 +7,10 @@ use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\Type; +use function array_keys; +use function array_map; +use function str_replace; + final class Expression { private string $expression; @@ -33,7 +37,7 @@ public function getParts(): array public function __toString(): string { $valuesAsStrings = array_map( - static fn(object $part): string => (string)$part, + static fn (object $part): string => (string) $part, $this->parts ); diff --git a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php index aa0a4c35..a7d91d79 100644 --- a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php +++ b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php @@ -8,11 +8,11 @@ use PhpParser\Node\Name; use PhpParser\PrettyPrinter\Standard; +use function md5; + final class ExpressionPrinter extends Standard { - /** - * @var array - */ + /** @var array */ private array $parts = []; protected function resetState(): void diff --git a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php index 13d8abe1..5a79b055 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php +++ b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php @@ -30,6 +30,8 @@ use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use Webmozart\Assert\Assert; +use function is_string; + /** * Strategy to convert ClassConstantIterator to ConstantElement * @@ -113,6 +115,7 @@ private function determineValue(ClassConstantIterator $value): null|Expression if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new Expression($expression, $this->valueConverter->getParts()); } + if (is_string($expression)) { $expression = new Expression($expression, []); } diff --git a/src/phpDocumentor/Reflection/Php/Factory/Define.php b/src/phpDocumentor/Reflection/Php/Factory/Define.php index 451342c9..2228b8fa 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Define.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Define.php @@ -33,6 +33,7 @@ use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use function assert; +use function is_string; use function sprintf; use function str_starts_with; @@ -130,6 +131,7 @@ private function determineValue(Arg|null $value): ValueExpression|null if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new ValueExpression($expression, $this->valueConverter->getParts()); } + if (is_string($expression)) { $expression = new ValueExpression($expression, []); } diff --git a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php index 236c50db..683b2c87 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php +++ b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php @@ -25,6 +25,8 @@ use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use Webmozart\Assert\Assert; +use function is_string; + /** * Strategy to convert GlobalConstantIterator to ConstantElement * @@ -88,6 +90,7 @@ private function determineValue(GlobalConstantIterator $value): ?Expression if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new Expression($expression, $this->valueConverter->getParts()); } + if (is_string($expression)) { $expression = new Expression($expression, []); } diff --git a/src/phpDocumentor/Reflection/Php/Factory/Property.php b/src/phpDocumentor/Reflection/Php/Factory/Property.php index 60628e02..17741c49 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Property.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Property.php @@ -27,6 +27,8 @@ use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use Webmozart\Assert\Assert; +use function is_string; + /** * Strategy to convert PropertyIterator to PropertyDescriptor * diff --git a/src/phpDocumentor/Reflection/Php/ProjectFactory.php b/src/phpDocumentor/Reflection/Php/ProjectFactory.php index 7d7a34bd..07b2e3d6 100644 --- a/src/phpDocumentor/Reflection/Php/ProjectFactory.php +++ b/src/phpDocumentor/Reflection/Php/ProjectFactory.php @@ -39,7 +39,6 @@ use phpDocumentor\Reflection\Php\Factory\TraitUse; use phpDocumentor\Reflection\Project as ProjectInterface; use phpDocumentor\Reflection\ProjectFactory as ProjectFactoryInterface; -use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use function is_array; diff --git a/src/phpDocumentor/Reflection/Php/Property.php b/src/phpDocumentor/Reflection/Php/Property.php index 6a8e38bb..58f0d831 100644 --- a/src/phpDocumentor/Reflection/Php/Property.php +++ b/src/phpDocumentor/Reflection/Php/Property.php @@ -21,6 +21,11 @@ use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface; use phpDocumentor\Reflection\Type; +use function is_string; +use function trigger_error; + +use const E_USER_DEPRECATED; + /** * Descriptor representing a property. * From 3f8d56d2a4ac995d65b9ebe6b7aafa8364f1d274 Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Sun, 11 Dec 2022 07:40:16 +0100 Subject: [PATCH 04/10] Update tests --- .../Reflection/Php/Expression.php | 6 ++ .../Reflection/Php/Factory/ClassConstant.php | 4 ++ .../Php/Factory/ConstructorPromotion.php | 24 ++++++- .../Reflection/Php/Factory/Define.php | 4 ++ .../Reflection/Php/Factory/EnumCase.php | 31 ++++++--- .../Reflection/Php/Factory/GlobalConstant.php | 4 ++ .../Reflection/Php/Factory/Property.php | 4 ++ .../PHP8/ConstructorPromotionTest.php | 66 +++++-------------- .../data/PHP8/ConstructorPromotion.php | 2 - 9 files changed, 81 insertions(+), 64 deletions(-) diff --git a/src/phpDocumentor/Reflection/Php/Expression.php b/src/phpDocumentor/Reflection/Php/Expression.php index c2627ddf..b3e1302b 100644 --- a/src/phpDocumentor/Reflection/Php/Expression.php +++ b/src/phpDocumentor/Reflection/Php/Expression.php @@ -18,6 +18,9 @@ final class Expression /** @var array */ private array $parts; + /** + * @param array $parts + */ public function __construct(string $expression, array $parts) { $this->expression = $expression; @@ -29,6 +32,9 @@ public function getExpression(): string return $this->expression; } + /** + * @return array + */ public function getParts(): array { return $this->parts; diff --git a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php index 5a79b055..37d8efaf 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php +++ b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php @@ -112,6 +112,10 @@ protected function doCreate( private function determineValue(ClassConstantIterator $value): null|Expression { $expression = $value->getValue() !== null ? $this->valueConverter->prettyPrintExpr($value->getValue()) : null; + if ($expression === null) { + return null; + } + if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new Expression($expression, $this->valueConverter->getParts()); } diff --git a/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php b/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php index ef8754e8..0469987e 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php +++ b/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php @@ -11,6 +11,8 @@ use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Php\Class_ as ClassElement; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; +use phpDocumentor\Reflection\Php\Expression; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\ProjectFactoryStrategy; use phpDocumentor\Reflection\Php\StrategyContainer; use PhpParser\Modifiers; @@ -20,6 +22,8 @@ use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use Webmozart\Assert\Assert; +use function is_string; + final class ConstructorPromotion extends AbstractFactory { /** @param iterable $reducers */ @@ -76,7 +80,7 @@ private function promoteParameterToProperty(ContextStack $context, StrategyConta ->visibility($param) ->type($param->type) ->docblock($param->getDocComment()) - ->default($param->default) + ->default($this->determineDefault($param)) ->readOnly($this->readOnly($param->flags)) ->static(false) ->startLocation(new Location($param->getLine(), $param->getStartFilePos())) @@ -95,6 +99,24 @@ private function promoteParameterToProperty(ContextStack $context, StrategyConta $methodContainer->addProperty($property); } + private function determineDefault(Param $value): Expression|null + { + $expression = $value->default !== null ? $this->valueConverter->prettyPrintExpr($value->default) : null; + if ($expression === null) { + return null; + } + + if ($this->valueConverter instanceof ExpressionPrinter) { + $expression = new Expression($expression, $this->valueConverter->getParts()); + } + + if (is_string($expression)) { + $expression = new Expression($expression, []); + } + + return $expression; + } + private function readOnly(int $flags): bool { return (bool) ($flags & Modifiers::READONLY) === true; diff --git a/src/phpDocumentor/Reflection/Php/Factory/Define.php b/src/phpDocumentor/Reflection/Php/Factory/Define.php index 2228b8fa..a6bf8eda 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Define.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Define.php @@ -128,6 +128,10 @@ private function determineValue(Arg|null $value): ValueExpression|null } $expression = $value->value !== null ? $this->valueConverter->prettyPrintExpr($value->value) : null; + if ($expression === null) { + return null; + } + if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new ValueExpression($expression, $this->valueConverter->getParts()); } diff --git a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php index 90726f29..5790cb27 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php +++ b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php @@ -11,12 +11,14 @@ use phpDocumentor\Reflection\Php\EnumCase as EnumCaseElement; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; use phpDocumentor\Reflection\Php\Expression; +use phpDocumentor\Reflection\Php\Expression as ValueExpression; use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\StrategyContainer; use PhpParser\Node\Stmt\EnumCase as EnumCaseNode; use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use function assert; +use function is_string; final class EnumCase extends AbstractFactory { @@ -43,21 +45,30 @@ protected function doCreate(ContextStack $context, object $object, StrategyConta $enum = $context->peek(); assert($enum instanceof EnumElement); - $value = $object->expr !== null ? $this->prettyPrinter->prettyPrintExpr($object->expr) : null; - if ($this->prettyPrinter instanceof ExpressionPrinter) { - $value = new Expression($value, $this->prettyPrinter->getParts()); - } - - $case = new EnumCaseElement( + $enum->addCase(new EnumCaseElement( $object->getAttribute('fqsen'), $docBlock, new Location($object->getLine()), new Location($object->getEndLine()), - $value, - ); + $this->determineValue($object), + )); + } - $enum->addCase($case); + private function determineValue(EnumCaseNode $value): ValueExpression|null + { + $expression = $value->expr !== null ? $this->prettyPrinter->prettyPrintExpr($value->expr) : null; + if ($expression === null) { + return null; + } + + if ($this->prettyPrinter instanceof ExpressionPrinter) { + $expression = new ValueExpression($expression, $this->prettyPrinter->getParts()); + } + + if (is_string($expression)) { + $expression = new ValueExpression($expression, []); + } - return $case; + return $expression; } } diff --git a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php index 683b2c87..b5293e9f 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php +++ b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php @@ -87,6 +87,10 @@ protected function doCreate( private function determineValue(GlobalConstantIterator $value): ?Expression { $expression = $value->getValue() !== null ? $this->valueConverter->prettyPrintExpr($value->getValue()) : null; + if ($expression === null) { + return null; + } + if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new Expression($expression, $this->valueConverter->getParts()); } diff --git a/src/phpDocumentor/Reflection/Php/Factory/Property.php b/src/phpDocumentor/Reflection/Php/Factory/Property.php index 17741c49..82d4c0d9 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Property.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Property.php @@ -115,6 +115,10 @@ private function determineDefault(PropertyIterator $value): Expression|null ? $this->valueConverter->prettyPrintExpr($value->getDefault()) : null; + if ($expression === null) { + return null; + } + if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new Expression($expression, $this->valueConverter->getParts()); } diff --git a/tests/integration/PHP8/ConstructorPromotionTest.php b/tests/integration/PHP8/ConstructorPromotionTest.php index 46c035f8..8e33106a 100644 --- a/tests/integration/PHP8/ConstructorPromotionTest.php +++ b/tests/integration/PHP8/ConstructorPromotionTest.php @@ -13,6 +13,7 @@ use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Php\Argument; +use phpDocumentor\Reflection\Php\Expression; use phpDocumentor\Reflection\Php\Method; use phpDocumentor\Reflection\Php\ProjectFactory; use phpDocumentor\Reflection\Php\Project; @@ -29,12 +30,10 @@ */ class ConstructorPromotionTest extends TestCase { - const FILE = __DIR__ . '/../data/PHP8/ConstructorPromotion.php'; - /** @var ProjectFactory */ - private $fixture; + private const FILE = __DIR__ . '/../data/PHP8/ConstructorPromotion.php'; - /** @var Project */ - private $project; + private ProjectFactory $fixture; + private Project $project; protected function setUp() : void { @@ -56,8 +55,6 @@ public function testPropertiesAreCreated() : void $constructor->addArgument(new Argument('name', new String_(), "'default name'")); $constructor->addArgument(new Argument('email', new Object_(new Fqsen('\\PHP8\\Email')))); $constructor->addArgument(new Argument('birth_date', new Object_(new Fqsen('\\' . DateTimeImmutable::class)))); - $constructor->addArgument(new Argument('created_at', new Object_(new Fqsen('\\' . DateTimeImmutable::class)))); - $constructor->addArgument(new Argument('uses_constants', new Array_())); self::assertEquals($constructor, $class->getMethods()['\PHP8\ConstructorPromotion::__construct()']); self::assertEquals( @@ -65,8 +62,6 @@ public function testPropertiesAreCreated() : void '\PHP8\ConstructorPromotion::$name' => $this->expectedNameProperty(), '\PHP8\ConstructorPromotion::$email' => $this->expectedEmailProperty(), '\PHP8\ConstructorPromotion::$birth_date' => $this->expectedBirthDateProperty(), - '\PHP8\ConstructorPromotion::$created_at' => $this->expectedCreatedAtProperty(), - '\PHP8\ConstructorPromotion::$uses_constants' => $this->expectedUsesConstantsProperty(), ], $class->getProperties() ); @@ -93,14 +88,14 @@ private function expectedConstructorMethod(): Method false, false, false, - new Location(18, 218), - new Location(31, 522) + new Location(18, 264), + new Location(29, 568) ); } private function expectedNameProperty(): Property { - $name = new Property( + return new Property( new Fqsen('\PHP8\ConstructorPromotion::$name'), new Visibility(Visibility::PUBLIC_), new DocBlock( @@ -113,68 +108,37 @@ private function expectedNameProperty(): Property ), "'default name'", false, - new Location(24, 393), - new Location(24, 428), + new Location(26, 393), + new Location(26, 428), new String_() ); - return $name; } private function expectedEmailProperty(): Property { - $email = new Property( + return new Property( new Fqsen('\PHP8\ConstructorPromotion::$email'), new Visibility(Visibility::PROTECTED_), null, null, false, - new Location(25, 439), - new Location(25, 460), + new Location(27, 439), + new Location(27, 460), new Object_(new Fqsen('\\PHP8\\Email')) ); - return $email; } private function expectedBirthDateProperty(): Property - { - $birthDate = new Property( - new Fqsen('\PHP8\ConstructorPromotion::$birth_date'), - new Visibility(Visibility::PRIVATE_), - null, - null, - false, - new Location(26), - new Location(26), - new Object_(new Fqsen('\\' . DateTimeImmutable::class)) - ); - return $birthDate; - } - - private function expectedCreatedAtProperty(): Property { return new Property( - new Fqsen('\PHP8\ConstructorPromotion::$created_at'), + new Fqsen('\PHP8\ConstructorPromotion::$birth_date'), new Visibility(Visibility::PRIVATE_), null, null, false, - new Location(26), - new Location(26), + new Location(28), + new Location(28), new Object_(new Fqsen('\\' . DateTimeImmutable::class)) ); } - - private function expectedUsesConstantsProperty(): Property - { - return new Property( - new Fqsen('\PHP8\ConstructorPromotion::$uses_constants'), - new Visibility(Visibility::PRIVATE_), - null, - null, - false, - new Location(26), - new Location(26), - new Array_() - ); - } } diff --git a/tests/integration/data/PHP8/ConstructorPromotion.php b/tests/integration/data/PHP8/ConstructorPromotion.php index 23eebcf4..ccf072c3 100644 --- a/tests/integration/data/PHP8/ConstructorPromotion.php +++ b/tests/integration/data/PHP8/ConstructorPromotion.php @@ -26,7 +26,5 @@ public function __construct( public string $name = 'default name', protected Email $email, private DateTimeImmutable $birth_date, - private DateTimeImmutable $created_at = new DateTimeImmutable('now'), - private array $uses_constants = [self::DEFAULT_VALUE], ) {} } From a5370572af2572e68de51332d5166425b2060f97 Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Sun, 11 Dec 2022 11:51:28 +0100 Subject: [PATCH 05/10] Expand docs and fix linting and analysis issues --- docs/expressions.rst | 34 ++++++ psalm.xml | 5 + src/phpDocumentor/Reflection/Php/Constant.php | 2 + .../Reflection/Php/Expression.php | 109 ++++++++++++++++-- .../Php/Expression/ExpressionPrinter.php | 22 ++-- .../Reflection/Php/Factory/ClassConstant.php | 8 +- .../Reflection/Php/Factory/Define.php | 6 +- .../Reflection/Php/Factory/EnumCase.php | 1 - .../Reflection/Php/Factory/GlobalConstant.php | 8 +- .../Reflection/Php/Factory/Property.php | 6 +- 10 files changed, 165 insertions(+), 36 deletions(-) diff --git a/docs/expressions.rst b/docs/expressions.rst index b14be356..679e6d40 100644 --- a/docs/expressions.rst +++ b/docs/expressions.rst @@ -24,3 +24,37 @@ $isString as being false, it will return an Expression object; which, when cast This will allow consumers to be able to extract types and links to elements from expressions. This allows consumers to, for example, interpret the default value for a constructor promoted properties when it directly instantiates an object. + +Creating expressions +-------------------- + +.. hint:: + + The description below is only for internal usage and to understand how expressions work, this library deals with + this by default. + +In this library, we use the ExpressionPrinter to convert a PHP-Parser node -or expression- into an expression +like this:: + + $printer = new ExpressionPrinter(); + $expressionTemplate = $printer->prettyPrintExpr($phpParserNode); + $expression = new Expression($expressionTemplate, $printer->getParts()); + +In the example above we assume that there is a PHP-Parser node representing an expression; this node is passed to the +ExpressionPrinter -which is an adapted PrettyPrinter from PHP-Parser- which will render the expression as a readable +template string containing placeholders, and a list of parts that can slot into the placeholders. + +Consuming expressions +--------------------- + +When using this library, you can consume these expression objects either by + +1. Directly casting them to a string - this will replace all placeholders with the stringified version of the parts +2. Use the render function - this will do the same as the previous methods but you can specify one or more overrides + for the placeholders in the expression + +The second method can be used to create your own string values from the given parts and render, for example, links in +these locations. + +Another way to use these expressions is to interpret the parts array, and through that way know which elements and +types are referred to in that expression. diff --git a/psalm.xml b/psalm.xml index bdac9dad..b72053c2 100644 --- a/psalm.xml +++ b/psalm.xml @@ -9,6 +9,11 @@ + + diff --git a/src/phpDocumentor/Reflection/Php/Constant.php b/src/phpDocumentor/Reflection/Php/Constant.php index a134f144..9d87a5cc 100644 --- a/src/phpDocumentor/Reflection/Php/Constant.php +++ b/src/phpDocumentor/Reflection/Php/Constant.php @@ -43,6 +43,8 @@ final class Constant implements Element, MetaDataContainerInterface, AttributeCo /** * Initializes the object. + * + * @param Expression|string|null $value */ public function __construct( private readonly Fqsen $fqsen, diff --git a/src/phpDocumentor/Reflection/Php/Expression.php b/src/phpDocumentor/Reflection/Php/Expression.php index b3e1302b..236e91da 100644 --- a/src/phpDocumentor/Reflection/Php/Expression.php +++ b/src/phpDocumentor/Reflection/Php/Expression.php @@ -5,34 +5,106 @@ namespace phpDocumentor\Reflection\Php; use phpDocumentor\Reflection\Fqsen; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Type; +use Webmozart\Assert\Assert; use function array_keys; -use function array_map; +use function md5; use function str_replace; +/** + * Represents an expression with a define statement, constant, property, enum case and any other location. + * + * Certain expressions contain useful references to other elements or types. Examples of these are: + * + * - Define statements that use an expression to refer to a class or function + * - Properties whose default value refers to a constant + * - Arguments whose default value initialize an object + * - Enum Cases that refer to a function or constant + * + * This class represents every location where an expression is used and contains 2 pieces of information: + * + * - The expression string containing placeholders linking to useful information + * - An array of 'parts' whose keys equal the placeholders in the expression string and whose values is the extracted + * information, such as an {@see FQSEN} or {@see Type}. + * + * In a way, the expression string is similar in nature to a URI Template (see links) where you have a string containing + * variables that can be replaced. These variables are delimited by `{{` and `}}`, and are build up of the prefix PHPDOC + * and then an MD5 hash of the name of the extracted information. + * + * It is not necessary for a consumer to interpret the information when they do not need it, a {@see self::__toString()} + * magic method is provided that will replace the placeholders with the `toString()` output of each part. + * + * @link https://github.com/php/php-langspec/blob/master/spec/10-expressions.md + * for the definition of expressions in PHP. + * @link https://www.rfc-editor.org/rfc/rfc6570 for more information on URI Templates. + * @see ExpressionPrinter how an expression coming from PHP-Parser is transformed into an expression. + */ final class Expression { + /** @var string The expression string containing placeholders for any extracted Types or FQSENs. */ private string $expression; - /** @var array */ + /** + * The collection of placeholders with the value that their holding. + * + * In the expression string there can be several placeholders, this array contains a placeholder => value pair + * that can be used by consumers to map the data to another formatting, adding links for example, and then render + * the expression. + * + * @var array + */ private array $parts; + /** + * Returns the recommended placeholder string format given a name. + * + * Consumers can use their own formats when needed, the placeholders are all keys in the {@see self::$parts} array + * and not interpreted by this class. However, to prevent collisions it is recommended to use this method to + * generate a placeholder. + * + * @param string $name a string identifying the element for which the placeholder is generated. + */ + public static function generatePlaceholder(string $name): string + { + Assert::notEmpty($name); + + return '{{ PHPDOC' . md5($name) . ' }}'; + } + /** * @param array $parts */ public function __construct(string $expression, array $parts) { + Assert::notEmpty($expression); + Assert::allIsInstanceOfAny($parts, [Fqsen::class, Type::class]); + $this->expression = $expression; $this->parts = $parts; } + /** + * The raw expression string containing placeholders for any extracted Types or FQSENs. + * + * @see self::render() to render a human-readable expression and to replace some parts with custom values. + * @see self::__toString() to render a human-readable expression with the previously extracted parts. + */ public function getExpression(): string { return $this->expression; } /** + * A list of extracted parts for which placeholders exist in the expression string. + * + * The returned array will have the placeholders of the expression string as keys, and the related FQSEN or Type as + * value. This can be used as a basis for doing your own transformations to {@see self::render()} the expression + * in a custom way; or to extract type information from an expression and use that elsewhere in your application. + * + * @see ExpressionPrinter to transform a PHP-Parser expression into an expression string and list of parts. + * * @return array */ public function getParts(): array @@ -40,13 +112,36 @@ public function getParts(): array return $this->parts; } - public function __toString(): string + /** + * Renders the expression as a string and replaces all placeholders with either a provided value, or the + * stringified value from the parts in this expression. + * + * The keys of the replacement parts should match those of {@see self::getParts()}, any unrecognized key is not + * handled. + * + * @param array $replacementParts + */ + public function render(array $replacementParts = []): string { - $valuesAsStrings = array_map( - static fn (object $part): string => (string) $part, - $this->parts - ); + Assert::allStringNotEmpty($replacementParts); + + $valuesAsStrings = []; + foreach ($this->parts as $placeholder => $part) { + $valuesAsStrings[$placeholder] = $replacementParts[$placeholder] ?? (string) $part; + } return str_replace(array_keys($this->parts), $valuesAsStrings, $this->expression); } + + /** + * Returns a rendered version of the expression string where all placeholders are replaced by the stringified + * versions of the included parts. + * + * @see self::$parts for the list of parts used in rendering + * @see self::render() to influence rendering of the expression. + */ + public function __toString(): string + { + return $this->render(); + } } diff --git a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php index a7d91d79..32e510f6 100644 --- a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php +++ b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php @@ -5,14 +5,14 @@ namespace phpDocumentor\Reflection\Php\Expression; use phpDocumentor\Reflection\Fqsen; +use phpDocumentor\Reflection\Php\Expression; +use phpDocumentor\Reflection\Type; use PhpParser\Node\Name; use PhpParser\PrettyPrinter\Standard; -use function md5; - final class ExpressionPrinter extends Standard { - /** @var array */ + /** @var array */ private array $parts = []; protected function resetState(): void @@ -25,16 +25,24 @@ protected function resetState(): void protected function pName(Name $node): string { $renderedName = parent::pName($node); - $code = md5($renderedName); + $placeholder = Expression::generatePlaceholder($renderedName); + $this->parts[$placeholder] = new Fqsen('\\' . $renderedName); - $placeholder = '{{ PHPDOC' . $code . ' }}'; - $this->parts[$placeholder] = new Fqsen('\\' . $node); + return $placeholder; + } + + // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps + protected function pName_FullyQualified(Name\FullyQualified $node): string + { + $renderedName = parent::pName_FullyQualified($node); + $placeholder = Expression::generatePlaceholder($renderedName); + $this->parts[$placeholder] = new Fqsen($renderedName); return $placeholder; } /** - * @return array + * @return array */ public function getParts(): array { diff --git a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php index 37d8efaf..53220bec 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php +++ b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php @@ -109,13 +109,9 @@ protected function doCreate( return null; } - private function determineValue(ClassConstantIterator $value): null|Expression + private function determineValue(ClassConstantIterator $value): Expression|null { - $expression = $value->getValue() !== null ? $this->valueConverter->prettyPrintExpr($value->getValue()) : null; - if ($expression === null) { - return null; - } - + $expression = $this->valueConverter->prettyPrintExpr($value->getValue()); if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new Expression($expression, $this->valueConverter->getParts()); } diff --git a/src/phpDocumentor/Reflection/Php/Factory/Define.php b/src/phpDocumentor/Reflection/Php/Factory/Define.php index a6bf8eda..e51e0bfc 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Define.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Define.php @@ -127,11 +127,7 @@ private function determineValue(Arg|null $value): ValueExpression|null return null; } - $expression = $value->value !== null ? $this->valueConverter->prettyPrintExpr($value->value) : null; - if ($expression === null) { - return null; - } - + $expression = $this->valueConverter->prettyPrintExpr($value->value); if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new ValueExpression($expression, $this->valueConverter->getParts()); } diff --git a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php index 5790cb27..2c117959 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php +++ b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php @@ -10,7 +10,6 @@ use phpDocumentor\Reflection\Php\Enum_ as EnumElement; use phpDocumentor\Reflection\Php\EnumCase as EnumCaseElement; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; -use phpDocumentor\Reflection\Php\Expression; use phpDocumentor\Reflection\Php\Expression as ValueExpression; use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\StrategyContainer; diff --git a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php index b5293e9f..eeb4b81a 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php +++ b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php @@ -84,13 +84,9 @@ protected function doCreate( return null; } - private function determineValue(GlobalConstantIterator $value): ?Expression + private function determineValue(GlobalConstantIterator $value): Expression { - $expression = $value->getValue() !== null ? $this->valueConverter->prettyPrintExpr($value->getValue()) : null; - if ($expression === null) { - return null; - } - + $expression = $this->valueConverter->prettyPrintExpr($value->getValue()); if ($this->valueConverter instanceof ExpressionPrinter) { $expression = new Expression($expression, $this->valueConverter->getParts()); } diff --git a/src/phpDocumentor/Reflection/Php/Factory/Property.php b/src/phpDocumentor/Reflection/Php/Factory/Property.php index 82d4c0d9..5972806b 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Property.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Property.php @@ -111,10 +111,8 @@ protected function doCreate( private function determineDefault(PropertyIterator $value): Expression|null { - $expression = $value->getDefault() !== null - ? $this->valueConverter->prettyPrintExpr($value->getDefault()) - : null; - + $default = $value->getDefault(); + $expression = $default !== null ? $this->valueConverter->prettyPrintExpr($default) : null; if ($expression === null) { return null; } From a7bf4e2a1902b74d700849837ba5921e116521a1 Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Sun, 11 Dec 2022 15:32:18 +0100 Subject: [PATCH 06/10] Add test for Expressions --- .../Reflection/Php/ExpressionTest.php | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php diff --git a/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php b/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php new file mode 100644 index 00000000..f197d378 --- /dev/null +++ b/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php @@ -0,0 +1,136 @@ + + */ +final class ExpressionTest extends TestCase +{ + private const EXAMPLE_FQSEN = '\\' . self::class; + private const EXAMPLE_FQSEN_PLACEHOLDER = '{{ PHPDOC0450ed2a7bac1efcf0c13b6560767954 }}'; + + /** + * @covers ::generatePlaceholder + */ + public function testGeneratingPlaceholder(): void + { + $placeholder = Expression::generatePlaceholder(self::EXAMPLE_FQSEN); + + self::assertSame(self::EXAMPLE_FQSEN_PLACEHOLDER, $placeholder); + } + + /** + * @covers ::generatePlaceholder + */ + public function testGeneratingPlaceholderErrorsUponPassingAnEmptyName(): void + { + $this->expectException(InvalidArgumentException::class); + + Expression::generatePlaceholder(''); + } + + /** + * @covers ::__construct + */ + public function testExpressionTemplateCannotBeEmpty(): void + { + $this->expectException(InvalidArgumentException::class); + + new Expression('', []); + } + + /** + * @covers ::__construct + */ + public function testPartsShouldContainFqsensOrTypes(): void + { + $this->expectException(InvalidArgumentException::class); + + new Expression('This is an expression', [self::EXAMPLE_FQSEN_PLACEHOLDER => self::EXAMPLE_FQSEN]); + } + + /** + * @covers ::__construct + * @covers ::getExpression + */ + public function testGetExpressionTemplateString(): void + { + $expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER); + $parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)]; + $expression = new Expression($expressionTemplate, $parts); + + $result = $expression->getExpression(); + + self::assertSame($expressionTemplate, $result); + } + + /** + * @covers ::__construct + * @covers ::getParts + */ + public function testGetExtractedParts(): void + { + $expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER); + $parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)]; + $expression = new Expression($expressionTemplate, $parts); + + $result = $expression->getParts(); + + self::assertSame($parts, $result); + } + + /** + * @covers ::__toString + */ + public function testReplacePlaceholdersWhenCastingToString(): void + { + $expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER); + $parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)]; + $expression = new Expression($expressionTemplate, $parts); + + $result = (string) $expression; + + self::assertSame(sprintf('This is an %s expression', self::EXAMPLE_FQSEN), $result); + } + + /** + * @covers ::render + */ + public function testRenderingExpressionWithoutOverridesIsTheSameAsWhenCastingToString(): void + { + $expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER); + $parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)]; + $expression = new Expression($expressionTemplate, $parts); + + $result = $expression->render(); + + self::assertSame((string) $expression, $result); + } + + /** + * @covers ::render + */ + public function testOverridePartsWhenRenderingExpression(): void + { + $replacement = 'ExpressionTest'; + + $expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER); + $parts = [self::EXAMPLE_FQSEN_PLACEHOLDER => new Fqsen(self::EXAMPLE_FQSEN)]; + $expression = new Expression($expressionTemplate, $parts); + + $result = $expression->render([self::EXAMPLE_FQSEN_PLACEHOLDER => $replacement]); + + self::assertSame(sprintf('This is an %s expression', $replacement), $result); + } +} From 3e4aa9139eb1ce15f0e95cf60c5e51cd8277bd3f Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Mon, 12 Dec 2022 07:33:56 +0100 Subject: [PATCH 07/10] Update tests for elements with Expressions --- .../Reflection/Php/Expression.php | 11 +- .../Php/Expression/ExpressionPrinter.php | 9 ++ .../Reflection/Php/ArgumentTest.php | 33 +++-- .../Reflection/Php/ConstantTest.php | 14 +- .../Reflection/Php/EnumCaseTest.php | 137 ++++++++++++++++-- .../Reflection/Php/ExpressionTest.php | 9 ++ 6 files changed, 180 insertions(+), 33 deletions(-) diff --git a/src/phpDocumentor/Reflection/Php/Expression.php b/src/phpDocumentor/Reflection/Php/Expression.php index 236e91da..acbad7cc 100644 --- a/src/phpDocumentor/Reflection/Php/Expression.php +++ b/src/phpDocumentor/Reflection/Php/Expression.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\Php; use phpDocumentor\Reflection\Fqsen; @@ -76,7 +85,7 @@ public static function generatePlaceholder(string $name): string /** * @param array $parts */ - public function __construct(string $expression, array $parts) + public function __construct(string $expression, array $parts = []) { Assert::notEmpty($expression); Assert::allIsInstanceOfAny($parts, [Fqsen::class, Type::class]); diff --git a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php index 32e510f6..3d52c188 100644 --- a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php +++ b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.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\Php\Expression; use phpDocumentor\Reflection\Fqsen; diff --git a/tests/unit/phpDocumentor/Reflection/Php/ArgumentTest.php b/tests/unit/phpDocumentor/Reflection/Php/ArgumentTest.php index a07de699..39f67da2 100644 --- a/tests/unit/phpDocumentor/Reflection/Php/ArgumentTest.php +++ b/tests/unit/phpDocumentor/Reflection/Php/ArgumentTest.php @@ -26,49 +26,50 @@ final class ArgumentTest extends TestCase { public function testGetTypes(): void { - $argument = new Argument('myArgument', null, 'myDefaultValue', true, true); - $this->assertInstanceOf(Mixed_::class, $argument->getType()); + $argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, true); + self::assertInstanceOf(Mixed_::class, $argument->getType()); $argument = new Argument( 'myArgument', new String_(), - 'myDefaultValue', + new Expression('myDefaultValue'), true, true, ); - $this->assertEquals(new String_(), $argument->getType()); + self::assertEquals(new String_(), $argument->getType()); } public function testGetName(): void { - $argument = new Argument('myArgument', null, 'myDefault', true, true); - $this->assertEquals('myArgument', $argument->getName()); + $argument = new Argument('myArgument', null, new Expression('myDefault'), true, true); + + self::assertEquals('myArgument', $argument->getName()); } public function testGetDefault(): void { - $argument = new Argument('myArgument', null, 'myDefaultValue', true, true); - $this->assertEquals('myDefaultValue', $argument->getDefault()); + $argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, true); + self::assertEquals(new Expression('myDefaultValue'), $argument->getDefault()); $argument = new Argument('myArgument', null, null, true, true); - $this->assertNull($argument->getDefault()); + self::assertNull($argument->getDefault()); } public function testGetWhetherArgumentIsPassedByReference(): void { - $argument = new Argument('myArgument', null, 'myDefaultValue', true, true); - $this->assertTrue($argument->isByReference()); + $argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, true); + self::assertTrue($argument->isByReference()); $argument = new Argument('myArgument', null, null, false, true); - $this->assertFalse($argument->isByReference()); + self::assertFalse($argument->isByReference()); } public function testGetWhetherArgumentisVariadic(): void { - $argument = new Argument('myArgument', null, 'myDefaultValue', true, true); - $this->assertTrue($argument->isVariadic()); + $argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, true); + self::assertTrue($argument->isVariadic()); - $argument = new Argument('myArgument', null, 'myDefaultValue', true, false); - $this->assertFalse($argument->isVariadic()); + $argument = new Argument('myArgument', null, new Expression('myDefaultValue'), true, false); + self::assertFalse($argument->isVariadic()); } } diff --git a/tests/unit/phpDocumentor/Reflection/Php/ConstantTest.php b/tests/unit/phpDocumentor/Reflection/Php/ConstantTest.php index 0fbed684..b0843d20 100644 --- a/tests/unit/phpDocumentor/Reflection/Php/ConstantTest.php +++ b/tests/unit/phpDocumentor/Reflection/Php/ConstantTest.php @@ -42,7 +42,7 @@ protected function setUp(): void { $this->fqsen = new Fqsen('\MySpace\CONSTANT'); $this->docBlock = new DocBlock(''); - $this->fixture = new Constant($this->fqsen, $this->docBlock, $this->value); + $this->fixture = new Constant($this->fqsen, $this->docBlock, new Expression($this->value)); } private function getFixture(): MetaDataContainerInterface @@ -52,28 +52,28 @@ private function getFixture(): MetaDataContainerInterface public function testGetValue(): void { - $this->assertSame($this->value, $this->fixture->getValue()); + self::assertEquals(new Expression($this->value), $this->fixture->getValue()); } public function testIsFinal(): void { - $this->assertFalse($this->fixture->isFinal()); + self::assertFalse($this->fixture->isFinal()); } public function testGetFqsen(): void { - $this->assertSame($this->fqsen, $this->fixture->getFqsen()); - $this->assertSame($this->fqsen->getName(), $this->fixture->getName()); + self::assertSame($this->fqsen, $this->fixture->getFqsen()); + self::assertSame($this->fqsen->getName(), $this->fixture->getName()); } public function testGetDocblock(): void { - $this->assertSame($this->docBlock, $this->fixture->getDocBlock()); + self::assertSame($this->docBlock, $this->fixture->getDocBlock()); } public function testGetVisibility(): void { - $this->assertEquals(new Visibility(Visibility::PUBLIC_), $this->fixture->getVisibility()); + self::assertEquals(new Visibility(Visibility::PUBLIC_), $this->fixture->getVisibility()); } public function testLineAndColumnNumberIsReturnedWhenALocationIsProvided(): void diff --git a/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php b/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php index 11267ac2..83995a8c 100644 --- a/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php +++ b/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php @@ -32,14 +32,18 @@ final class EnumCaseTest extends TestCase private DocBlock $docBlock; /** - * Creates a new (emoty) fixture object. + * Creates a new (empty) fixture object. */ protected function setUp(): void { $this->fqsen = new Fqsen('\Enum::VALUE'); $this->docBlock = new DocBlock(''); - $this->fixture = new EnumCase($this->fqsen, $this->docBlock); + // needed for MetaDataContainer testing + $this->fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + ); } private function getFixture(): MetaDataContainerInterface @@ -49,26 +53,141 @@ private function getFixture(): MetaDataContainerInterface public function testGettingName(): void { - $this->assertSame($this->fqsen->getName(), $this->fixture->getName()); + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + ); + + $this->assertSame($this->fqsen->getName(), $fixture->getName()); } public function testGettingFqsen(): void { - $this->assertSame($this->fqsen, $this->fixture->getFqsen()); + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + ); + + $this->assertSame($this->fqsen, $fixture->getFqsen()); } public function testGettingDocBlock(): void { - $this->assertSame($this->docBlock, $this->fixture->getDocBlock()); + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + ); + + $this->assertSame($this->docBlock, $fixture->getDocBlock()); + } + + /** + * @covers ::getValue + */ + public function testValueCanBeOmitted(): void + { + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + ); + + $this->assertNull($fixture->getValue()); + } + + /** + * @uses Expression + * + * @covers ::getValue + */ + public function testValueCanBeProvidedAsAnExpression(): void + { + $expression = new Expression('Enum case expression'); + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + null, + null, + $expression, + ); + + $this->assertSame($expression, $fixture->getValue(false)); } - public function testGetValue(): void + /** + * @uses Expression + * + * @covers ::getValue + */ + public function testValueCanBeReturnedAsString(): void { - $this->assertNull($this->fixture->getValue()); + $expression = new Expression('Enum case expression'); + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + null, + null, + $expression, + ); + + $this->assertSame('Enum case expression', $fixture->getValue(true)); } - public function testGetLocationReturnsDefault(): void + /** + * @covers ::getLocation + */ + public function testGetLocationReturnsProvidedValue(): void { - self::assertEquals(new Location(-1), $this->fixture->getLocation()); + $location = new Location(15, 10); + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + $location, + ); + + self::assertSame($location, $fixture->getLocation()); + } + + /** + * @uses Location + * + * @covers ::getLocation + */ + public function testGetLocationReturnsUnknownByDefault(): void + { + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + ); + + self::assertEquals(new Location(-1), $fixture->getLocation()); + } + + /** + * @covers ::getEndLocation + */ + public function testGetEndLocationReturnsProvidedValue(): void + { + $location = new Location(11, 23); + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + null, + $location, + ); + + self::assertSame($location, $fixture->getEndLocation()); + } + + /** + * @covers ::getEndLocation + */ + public function testGetEndLocationReturnsUnknownByDefault(): void + { + $fixture = new EnumCase( + $this->fqsen, + $this->docBlock, + ); + + self::assertEquals(new Location(-1), $fixture->getEndLocation()); } } diff --git a/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php b/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php index f197d378..9d290216 100644 --- a/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php +++ b/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.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\Php; use InvalidArgumentException; From 3e599c4fca5507f3488cca8f6e3068d4f4aa2ded Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Mon, 12 Dec 2022 07:36:55 +0100 Subject: [PATCH 08/10] Update property test with new expressions --- .../Reflection/Php/PropertyTest.php | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tests/unit/phpDocumentor/Reflection/Php/PropertyTest.php b/tests/unit/phpDocumentor/Reflection/Php/PropertyTest.php index 44a7d5ef..6dc29b43 100644 --- a/tests/unit/phpDocumentor/Reflection/Php/PropertyTest.php +++ b/tests/unit/phpDocumentor/Reflection/Php/PropertyTest.php @@ -56,23 +56,23 @@ public function testGetFqsenAndGetName(): void { $property = new Property($this->fqsen); - $this->assertSame($this->fqsen, $property->getFqsen()); - $this->assertEquals($this->fqsen->getName(), $property->getName()); + self::assertSame($this->fqsen, $property->getFqsen()); + self::assertEquals($this->fqsen->getName(), $property->getName()); } public function testGettingWhetherPropertyIsStatic(): void { $property = new Property($this->fqsen, $this->visibility, $this->docBlock, null, false); - $this->assertFalse($property->isStatic()); + self::assertFalse($property->isStatic()); $property = new Property($this->fqsen, $this->visibility, $this->docBlock, null, true); - $this->assertTrue($property->isStatic()); + self::assertTrue($property->isStatic()); } public function testGettingWhetherPropertyIsReadOnly(): void { $property = new Property($this->fqsen, $this->visibility, $this->docBlock, null); - $this->assertFalse($property->isReadOnly()); + self::assertFalse($property->isReadOnly()); $property = new Property( $this->fqsen, @@ -86,38 +86,40 @@ public function testGettingWhetherPropertyIsReadOnly(): void true, ); - $this->assertTrue($property->isReadOnly()); + self::assertTrue($property->isReadOnly()); } public function testGettingVisibility(): void { $property = new Property($this->fqsen, $this->visibility, $this->docBlock, null, true); - $this->assertSame($this->visibility, $property->getVisibility()); + self::assertSame($this->visibility, $property->getVisibility()); } public function testSetAndGetTypes(): void { $property = new Property($this->fqsen, $this->visibility, $this->docBlock, null, true); - $this->assertEquals([], $property->getTypes()); + self::assertEquals([], $property->getTypes()); $property->addType('a'); - $this->assertEquals(['a'], $property->getTypes()); + self::assertEquals(['a'], $property->getTypes()); } public function testGetDefault(): void { $property = new Property($this->fqsen, $this->visibility, $this->docBlock, null, false); - $this->assertNull($property->getDefault()); + self::assertNull($property->getDefault()); - $property = new Property($this->fqsen, $this->visibility, $this->docBlock, 'a', true); - $this->assertEquals('a', $property->getDefault()); + $expression = new Expression('a'); + $property = new Property($this->fqsen, $this->visibility, $this->docBlock, $expression, true); + self::assertSame('a', $property->getDefault()); + self::assertSame($expression, $property->getDefault(false)); } public function testGetDocBlock(): void { $property = new Property($this->fqsen, $this->visibility, $this->docBlock, null, false); - $this->assertSame($this->docBlock, $property->getDocBlock()); + self::assertSame($this->docBlock, $property->getDocBlock()); } public function testLineAndColumnNumberIsReturnedWhenALocationIsProvided(): void @@ -140,9 +142,9 @@ public function testGetType(): void $type, ); - $this->assertSame($type, $fixture->getType()); + self::assertSame($type, $fixture->getType()); $fixture = new Property($this->fqsen); - $this->assertNull($fixture->getType()); + self::assertNull($fixture->getType()); } } From cefb90d97d16823e5366a8b63ca4268140975c42 Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Mon, 12 Dec 2022 07:56:16 +0100 Subject: [PATCH 09/10] Add integration tests for Expressions, and fail As part of checking whether everything works as expected I am adding integration tests, and find out that the pretty printer can be a bit greedy. This is exemplified in the ConstructorPromotionTest where I want to pass a reference to a local class constant, but the ExpressionPrinter picks up on the class reference and does not resolve it. This is a major issue, and more work is needed to a) parse the whole FQSEN and use that b) find out how to resolve references, such as self Another test is needed to see if it resolves relative references as well, as the Standard PrettyPrinter code gives me the idea it does not. --- tests/integration/FileDocblockTest.php | 2 + .../PHP8/ConstructorPromotionTest.php | 55 ++++++++++++++++++- .../data/PHP8/ConstructorPromotion.php | 2 + 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/tests/integration/FileDocblockTest.php b/tests/integration/FileDocblockTest.php index d6fa98d6..7e64e1c1 100644 --- a/tests/integration/FileDocblockTest.php +++ b/tests/integration/FileDocblockTest.php @@ -11,6 +11,8 @@ /** * Integration tests to check the correct working of processing a namespace into a project. + * + * @coversNothing */ #[CoversNothing] final class FileDocblockTest extends TestCase diff --git a/tests/integration/PHP8/ConstructorPromotionTest.php b/tests/integration/PHP8/ConstructorPromotionTest.php index 8e33106a..1a5302fc 100644 --- a/tests/integration/PHP8/ConstructorPromotionTest.php +++ b/tests/integration/PHP8/ConstructorPromotionTest.php @@ -46,7 +46,7 @@ protected function setUp() : void ); } - public function testPropertiesAreCreated() : void + public function testArgumentsAreReadCorrectly() : void { $file = $this->project->getFiles()[self::FILE]; $class = $file->getClasses()['\\PHP8\\ConstructorPromotion']; @@ -55,13 +55,45 @@ public function testPropertiesAreCreated() : void $constructor->addArgument(new Argument('name', new String_(), "'default name'")); $constructor->addArgument(new Argument('email', new Object_(new Fqsen('\\PHP8\\Email')))); $constructor->addArgument(new Argument('birth_date', new Object_(new Fqsen('\\' . DateTimeImmutable::class)))); + $constructor->addArgument( + new Argument( + 'created_at', + new Object_(new Fqsen('\\' . DateTimeImmutable::class)), + new Expression( + 'new {{ PHPDOC6ffacd918e2f70478d2fd33dcb58c4d4 }}(\'now\')', + [ + '{{ PHPDOC6ffacd918e2f70478d2fd33dcb58c4d4 }}' => new Fqsen('\\DateTimeImmutable') + ] + ) + ) + ); + $constructor->addArgument( + new Argument( + 'uses_constants', + new Array_(), + new Expression( + '[{{ PHPDOC590f53e8699817c6fa498cc11a4cbe63 }}]', + [ + '{{ PHPDOC590f53e8699817c6fa498cc11a4cbe63 }}' => new Fqsen('\PHP8\ConstructorPromotion::DEFAULT_VALUE') + ] + ) + ) + ); self::assertEquals($constructor, $class->getMethods()['\PHP8\ConstructorPromotion::__construct()']); + } + + public function testPropertiesAreCreated() : void + { + $file = $this->project->getFiles()[self::FILE]; + $class = $file->getClasses()['\\PHP8\\ConstructorPromotion']; + self::assertEquals( [ '\PHP8\ConstructorPromotion::$name' => $this->expectedNameProperty(), '\PHP8\ConstructorPromotion::$email' => $this->expectedEmailProperty(), '\PHP8\ConstructorPromotion::$birth_date' => $this->expectedBirthDateProperty(), + '\PHP8\ConstructorPromotion::$created_at' => $this->expectedCreatedAtProperty(), ], $class->getProperties() ); @@ -89,7 +121,7 @@ private function expectedConstructorMethod(): Method false, false, new Location(18, 264), - new Location(29, 568) + new Location(31, 709) ); } @@ -141,4 +173,23 @@ private function expectedBirthDateProperty(): Property new Object_(new Fqsen('\\' . DateTimeImmutable::class)) ); } + + private function expectedCreatedAtProperty(): Property + { + return new Property( + new Fqsen('\PHP8\ConstructorPromotion::$created_at'), + new Visibility(Visibility::PRIVATE_), + null, + new Expression( + 'new {{ PHPDOC6ffacd918e2f70478d2fd33dcb58c4d4 }}(\'now\')', + [ + '{{ PHPDOC6ffacd918e2f70478d2fd33dcb58c4d4 }}' => new Fqsen('\\DateTimeImmutable') + ] + ), + false, + new Location(29), + new Location(29), + new Object_(new Fqsen('\\' . DateTimeImmutable::class)) + ); + } } diff --git a/tests/integration/data/PHP8/ConstructorPromotion.php b/tests/integration/data/PHP8/ConstructorPromotion.php index ccf072c3..23eebcf4 100644 --- a/tests/integration/data/PHP8/ConstructorPromotion.php +++ b/tests/integration/data/PHP8/ConstructorPromotion.php @@ -26,5 +26,7 @@ public function __construct( public string $name = 'default name', protected Email $email, private DateTimeImmutable $birth_date, + private DateTimeImmutable $created_at = new DateTimeImmutable('now'), + private array $uses_constants = [self::DEFAULT_VALUE], ) {} } From 2675d07ca4a0e593e207f9d850ad0b0d069eda0e Mon Sep 17 00:00:00 2001 From: Jaapio Date: Sun, 8 Jun 2025 22:47:13 +0200 Subject: [PATCH 10/10] Fix code style and tests --- phpstan.neon | 1 - psalm-baseline.xml | 8 --- src/phpDocumentor/Reflection/Php/Argument.php | 9 ++-- src/phpDocumentor/Reflection/Php/Constant.php | 20 +++---- src/phpDocumentor/Reflection/Php/EnumCase.php | 18 ++++--- .../Reflection/Php/Expression.php | 6 +-- .../Php/Expression/ExpressionPrinter.php | 18 +++++-- .../Reflection/Php/Factory/Argument.php | 0 .../Reflection/Php/Factory/ClassConstant.php | 4 +- .../Php/Factory/ConstructorPromotion.php | 28 ++-------- .../Reflection/Php/Factory/EnumCase.php | 10 ++-- .../Reflection/Php/Factory/Property.php | 24 +-------- .../Php/Factory/PropertyBuilder.php | 25 ++++++++- .../Php/Factory/Reducer/Parameter.php | 23 +++++++- .../Reflection/Php/ProjectFactory.php | 10 ++-- src/phpDocumentor/Reflection/Php/Property.php | 18 ++++--- .../PHP8/ConstructorPromotionTest.php | 53 +++++++++++++------ .../Reflection/Php/EnumCaseTest.php | 16 ++---- .../Reflection/Php/ExpressionTest.php | 28 +++------- 19 files changed, 163 insertions(+), 156 deletions(-) delete mode 100644 src/phpDocumentor/Reflection/Php/Factory/Argument.php diff --git a/phpstan.neon b/phpstan.neon index 71dffd7c..63041f70 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,7 +6,6 @@ parameters: ignoreErrors: - '#Method phpDocumentor\\Reflection\\File\\LocalFile::md5\(\) should return string but returns string\|false\.#' - - '#Else branch is unreachable because ternary operator condition is always true\.#' # # all these $fqsen errors indicate the need for a decorator class around PhpParser\Node to hold the public $fqsen that Reflection is giving it) # diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 2ec6bdbc..dd789ceb 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -53,9 +53,6 @@ - - getValue() !== null]]> - @@ -115,11 +112,6 @@ getAttribute('fqsen')]]> - - - getValue() !== null]]> - - constant->consts[$this->index]->getAttribute('fqsen')]]> diff --git a/src/phpDocumentor/Reflection/Php/Argument.php b/src/phpDocumentor/Reflection/Php/Argument.php index 23f520a0..2551553b 100644 --- a/src/phpDocumentor/Reflection/Php/Argument.php +++ b/src/phpDocumentor/Reflection/Php/Argument.php @@ -38,7 +38,7 @@ public function __construct( /** @var string name of the Argument */ private readonly string $name, Type|null $type = null, - /** @var string|null the default value for an argument or null if none is provided */ + /** @var Expression|string|null the default value for an argument or null if none is provided */ private Expression|string|null $default = null, /** @var bool whether the argument passes the parameter by reference instead of by value */ private readonly bool $byReference = false, @@ -53,7 +53,7 @@ public function __construct( trigger_error( 'Default values for arguments should be of type Expression, support for strings will be ' . 'removed in 7.x', - E_USER_DEPRECATED + E_USER_DEPRECATED, ); $this->default = new Expression($this->default, []); } @@ -74,8 +74,7 @@ public function getType(): Type|null return $this->type; } - /** */ - public function getDefault(bool $asString = true): string|null + public function getDefault(bool $asString = true): Expression|string|null { if ($this->default === null) { return null; @@ -84,7 +83,7 @@ public function getDefault(bool $asString = true): string|null if ($asString) { trigger_error( 'The Default value will become of type Expression by default', - E_USER_DEPRECATED + E_USER_DEPRECATED, ); return (string) $this->default; diff --git a/src/phpDocumentor/Reflection/Php/Constant.php b/src/phpDocumentor/Reflection/Php/Constant.php index 9d87a5cc..1b334758 100644 --- a/src/phpDocumentor/Reflection/Php/Constant.php +++ b/src/phpDocumentor/Reflection/Php/Constant.php @@ -43,8 +43,6 @@ final class Constant implements Element, MetaDataContainerInterface, AttributeCo /** * Initializes the object. - * - * @param Expression|string|null $value */ public function __construct( private readonly Fqsen $fqsen, @@ -59,14 +57,16 @@ public function __construct( $this->endLocation = $endLocation ?: new Location(-1); $this->visibility = $visibility ?: new Visibility(Visibility::PUBLIC_); - if (is_string($this->value)) { - trigger_error( - 'Constant values should be of type Expression, support for strings will be ' - . 'removed in 6.x', - E_USER_DEPRECATED - ); - $this->value = new Expression($this->value, []); + if (!is_string($this->value)) { + return; } + + trigger_error( + 'Constant values should be of type Expression, support for strings will be ' + . 'removed in 6.x', + E_USER_DEPRECATED, + ); + $this->value = new Expression($this->value, []); } /** @@ -81,7 +81,7 @@ public function getValue(bool $asString = true): Expression|string|null if ($asString) { trigger_error( 'The expression value will become of type Expression by default', - E_USER_DEPRECATED + E_USER_DEPRECATED, ); return (string) $this->value; diff --git a/src/phpDocumentor/Reflection/Php/EnumCase.php b/src/phpDocumentor/Reflection/Php/EnumCase.php index 1fcb2fe8..f70934ff 100644 --- a/src/phpDocumentor/Reflection/Php/EnumCase.php +++ b/src/phpDocumentor/Reflection/Php/EnumCase.php @@ -47,14 +47,16 @@ public function __construct( $this->location = $location; $this->endLocation = $endLocation; - if (is_string($this->value)) { - trigger_error( - 'Expression values for enum cases should be of type Expression, support for strings will be ' - . 'removed in 7.x', - E_USER_DEPRECATED - ); - $this->value = new Expression($this->value, []); + if (!is_string($this->value)) { + return; } + + trigger_error( + 'Expression values for enum cases should be of type Expression, support for strings will be ' + . 'removed in 7.x', + E_USER_DEPRECATED, + ); + $this->value = new Expression($this->value, []); } #[Override] @@ -96,7 +98,7 @@ public function getValue(bool $asString = true): Expression|string|null if ($asString) { trigger_error( 'The enum case value will become of type Expression by default', - E_USER_DEPRECATED + E_USER_DEPRECATED, ); return (string) $this->value; diff --git a/src/phpDocumentor/Reflection/Php/Expression.php b/src/phpDocumentor/Reflection/Php/Expression.php index acbad7cc..d1a253cf 100644 --- a/src/phpDocumentor/Reflection/Php/Expression.php +++ b/src/phpDocumentor/Reflection/Php/Expression.php @@ -49,6 +49,8 @@ * for the definition of expressions in PHP. * @link https://www.rfc-editor.org/rfc/rfc6570 for more information on URI Templates. * @see ExpressionPrinter how an expression coming from PHP-Parser is transformed into an expression. + * + * @api */ final class Expression { @@ -82,9 +84,7 @@ public static function generatePlaceholder(string $name): string return '{{ PHPDOC' . md5($name) . ' }}'; } - /** - * @param array $parts - */ + /** @param array $parts */ public function __construct(string $expression, array $parts = []) { Assert::notEmpty($expression); diff --git a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php index 3d52c188..c56b2473 100644 --- a/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php +++ b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php @@ -16,6 +16,7 @@ use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\Php\Expression; use phpDocumentor\Reflection\Type; +use PhpParser\Node\Expr; use PhpParser\Node\Name; use PhpParser\PrettyPrinter\Standard; @@ -50,9 +51,20 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string return $placeholder; } - /** - * @return array - */ + protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node): string + { + $renderedName = parent::pObjectProperty($node->name); + $className = $node->class instanceof Name ? parent::pName($node->class) : $this->p($node->class); + $placeholder = Expression::generatePlaceholder($renderedName); + $this->parts[$placeholder] = new Fqsen( + '\\' . $className . '::' . $renderedName + ); + + return $placeholder; + } + + + /** @return array */ public function getParts(): array { return $this->parts; diff --git a/src/phpDocumentor/Reflection/Php/Factory/Argument.php b/src/phpDocumentor/Reflection/Php/Factory/Argument.php deleted file mode 100644 index e69de29b..00000000 diff --git a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php index 53220bec..7d6ed2b9 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php +++ b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php @@ -19,9 +19,9 @@ use phpDocumentor\Reflection\Php\Class_; use phpDocumentor\Reflection\Php\Constant as ConstantElement; use phpDocumentor\Reflection\Php\Enum_; -use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; use phpDocumentor\Reflection\Php\Expression; use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; +use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; use phpDocumentor\Reflection\Php\Interface_; use phpDocumentor\Reflection\Php\StrategyContainer; use phpDocumentor\Reflection\Php\Trait_; @@ -109,7 +109,7 @@ protected function doCreate( return null; } - private function determineValue(ClassConstantIterator $value): Expression|null + private function determineValue(ClassConstantIterator $value): Expression { $expression = $this->valueConverter->prettyPrintExpr($value->getValue()); if ($this->valueConverter instanceof ExpressionPrinter) { diff --git a/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php b/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php index 0469987e..69c709bd 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php +++ b/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php @@ -11,8 +11,6 @@ use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Php\Class_ as ClassElement; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; -use phpDocumentor\Reflection\Php\Expression; -use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\ProjectFactoryStrategy; use phpDocumentor\Reflection\Php\StrategyContainer; use PhpParser\Modifiers; @@ -22,8 +20,6 @@ use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use Webmozart\Assert\Assert; -use function is_string; - final class ConstructorPromotion extends AbstractFactory { /** @param iterable $reducers */ @@ -80,11 +76,11 @@ private function promoteParameterToProperty(ContextStack $context, StrategyConta ->visibility($param) ->type($param->type) ->docblock($param->getDocComment()) - ->default($this->determineDefault($param)) + ->default($param->default) ->readOnly($this->readOnly($param->flags)) ->static(false) - ->startLocation(new Location($param->getLine(), $param->getStartFilePos())) - ->endLocation(new Location($param->getEndLine(), $param->getEndFilePos())) + ->startLocation(new Location($param->getLine())) + ->endLocation(new Location($param->getEndLine())) ->hooks($param->hooks ?? []) ->build($context); @@ -99,24 +95,6 @@ private function promoteParameterToProperty(ContextStack $context, StrategyConta $methodContainer->addProperty($property); } - private function determineDefault(Param $value): Expression|null - { - $expression = $value->default !== null ? $this->valueConverter->prettyPrintExpr($value->default) : null; - if ($expression === null) { - return null; - } - - if ($this->valueConverter instanceof ExpressionPrinter) { - $expression = new Expression($expression, $this->valueConverter->getParts()); - } - - if (is_string($expression)) { - $expression = new Expression($expression, []); - } - - return $expression; - } - private function readOnly(int $flags): bool { return (bool) ($flags & Modifiers::READONLY) === true; diff --git a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php index 2c117959..8e1fc9bc 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php +++ b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php @@ -9,9 +9,9 @@ use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Php\Enum_ as EnumElement; use phpDocumentor\Reflection\Php\EnumCase as EnumCaseElement; -use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; use phpDocumentor\Reflection\Php\Expression as ValueExpression; use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; +use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; use phpDocumentor\Reflection\Php\StrategyContainer; use PhpParser\Node\Stmt\EnumCase as EnumCaseNode; use PhpParser\PrettyPrinter\Standard as PrettyPrinter; @@ -44,13 +44,17 @@ protected function doCreate(ContextStack $context, object $object, StrategyConta $enum = $context->peek(); assert($enum instanceof EnumElement); - $enum->addCase(new EnumCaseElement( + $case = new EnumCaseElement( $object->getAttribute('fqsen'), $docBlock, new Location($object->getLine()), new Location($object->getEndLine()), $this->determineValue($object), - )); + ); + + $enum->addCase($case); + + return $case; } private function determineValue(EnumCaseNode $value): ValueExpression|null diff --git a/src/phpDocumentor/Reflection/Php/Factory/Property.php b/src/phpDocumentor/Reflection/Php/Factory/Property.php index 5972806b..ea996baa 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Property.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Property.php @@ -18,8 +18,6 @@ use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\Php\Class_; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; -use phpDocumentor\Reflection\Php\Expression; -use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\Property as PropertyDescriptor; use phpDocumentor\Reflection\Php\StrategyContainer; use phpDocumentor\Reflection\Php\Trait_; @@ -88,7 +86,7 @@ protected function doCreate( ->visibility($stmt) ->type($stmt->getType()) ->docblock($stmt->getDocComment()) - ->default($this->determineDefault($stmt)) + ->default($stmt->getDefault()) ->static($stmt->isStatic()) ->startLocation(new Location($stmt->getLine())) ->endLocation(new Location($stmt->getEndLine())) @@ -105,26 +103,8 @@ protected function doCreate( } $propertyContainer->addProperty($property); - - } - } - - private function determineDefault(PropertyIterator $value): Expression|null - { - $default = $value->getDefault(); - $expression = $default !== null ? $this->valueConverter->prettyPrintExpr($default) : null; - if ($expression === null) { - return null; - } - - if ($this->valueConverter instanceof ExpressionPrinter) { - $expression = new Expression($expression, $this->valueConverter->getParts()); - } - - if (is_string($expression)) { - $expression = new Expression($expression, []); } - return $expression; + return null; } } diff --git a/src/phpDocumentor/Reflection/Php/Factory/PropertyBuilder.php b/src/phpDocumentor/Reflection/Php/Factory/PropertyBuilder.php index db1a2776..8be342fe 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/PropertyBuilder.php +++ b/src/phpDocumentor/Reflection/Php/Factory/PropertyBuilder.php @@ -9,6 +9,8 @@ use phpDocumentor\Reflection\Location; use phpDocumentor\Reflection\NodeVisitor\FindingVisitor; use phpDocumentor\Reflection\Php\AsymmetricVisibility; +use phpDocumentor\Reflection\Php\Expression; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; use phpDocumentor\Reflection\Php\Property as PropertyElement; use phpDocumentor\Reflection\Php\PropertyHook; @@ -26,11 +28,12 @@ use PhpParser\Node\Param; use PhpParser\Node\PropertyHook as PropertyHookNode; use PhpParser\NodeTraverser; -use PhpParser\PrettyPrinter\Standard as PrettyPrinter; +use PhpParser\PrettyPrinter; use function array_filter; use function array_map; use function count; +use function is_string; use function method_exists; /** @@ -159,7 +162,7 @@ public function build(ContextStack $context): PropertyElement $this->fqsen, $this->visibility, $this->docblock !== null ? $this->docBlockFactory->create($this->docblock->getText(), $context->getTypeContext()) : null, - $this->default !== null ? $this->valueConverter->prettyPrintExpr($this->default) : null, + $this->determineDefault(), $this->static, $this->startLocation, $this->endLocation, @@ -341,4 +344,22 @@ private function buildHookVisibility(string $hookName, Visibility $propertyVisib default => $propertyVisibility, }; } + + private function determineDefault(): Expression|null + { + $expression = $this->default !== null ? $this->valueConverter->prettyPrintExpr($this->default) : null; + if ($expression === null) { + return null; + } + + if ($this->valueConverter instanceof ExpressionPrinter) { + $expression = new Expression($expression, $this->valueConverter->getParts()); + } + + if (is_string($expression)) { + $expression = new Expression($expression, []); + } + + return $expression; + } } diff --git a/src/phpDocumentor/Reflection/Php/Factory/Reducer/Parameter.php b/src/phpDocumentor/Reflection/Php/Factory/Reducer/Parameter.php index 074defc1..c885932f 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Reducer/Parameter.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Reducer/Parameter.php @@ -6,6 +6,8 @@ use Override; use phpDocumentor\Reflection\Php\Argument as ArgumentDescriptor; +use phpDocumentor\Reflection\Php\Expression; +use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter; use phpDocumentor\Reflection\Php\Factory\ContextStack; use phpDocumentor\Reflection\Php\Factory\Type; use phpDocumentor\Reflection\Php\Function_; @@ -14,6 +16,7 @@ use phpDocumentor\Reflection\Php\StrategyContainer; use PhpParser\Node\Expr\Variable; use PhpParser\Node\FunctionLike; +use PhpParser\Node\Param; use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use Webmozart\Assert\Assert; @@ -47,7 +50,7 @@ public function reduce( new ArgumentDescriptor( is_string($param->var->name) ? $param->var->name : $this->valueConverter->prettyPrintExpr($param->var->name), (new Type())->fromPhpParser($param->type), - $param->default !== null ? $this->valueConverter->prettyPrintExpr($param->default) : null, + $this->determineDefault($param), $param->byRef, $param->variadic, ), @@ -56,4 +59,22 @@ public function reduce( return $carry; } + + private function determineDefault(Param $value): Expression|null + { + $expression = $value->default !== null ? $this->valueConverter->prettyPrintExpr($value->default) : null; + if ($expression === null) { + return null; + } + + if ($this->valueConverter instanceof ExpressionPrinter) { + $expression = new Expression($expression, $this->valueConverter->getParts()); + } + + if (is_string($expression)) { + $expression = new Expression($expression, []); + } + + return $expression; + } } diff --git a/src/phpDocumentor/Reflection/Php/ProjectFactory.php b/src/phpDocumentor/Reflection/Php/ProjectFactory.php index 07b2e3d6..6fab4653 100644 --- a/src/phpDocumentor/Reflection/Php/ProjectFactory.php +++ b/src/phpDocumentor/Reflection/Php/ProjectFactory.php @@ -70,7 +70,7 @@ public static function createInstance(): self $expressionPrinter = new ExpressionPrinter(); $attributeReducer = new Attribute(); - $parameterReducer = new Parameter(new PrettyPrinter()); + $parameterReducer = new Parameter($expressionPrinter); $methodStrategy = new Method($docblockFactory, [$attributeReducer, $parameterReducer]); @@ -79,10 +79,10 @@ public static function createInstance(): self new \phpDocumentor\Reflection\Php\Factory\Namespace_(), new Class_($docblockFactory, [$attributeReducer]), new Enum_($docblockFactory, [$attributeReducer]), - new EnumCase($docblockFactory, new PrettyPrinter(), [$attributeReducer]), - new Define($docblockFactory, new PrettyPrinter()), - new GlobalConstant($docblockFactory, new PrettyPrinter()), - new ClassConstant($docblockFactory, new PrettyPrinter(), [$attributeReducer]), + new EnumCase($docblockFactory, $expressionPrinter, [$attributeReducer]), + new Define($docblockFactory, $expressionPrinter), + new GlobalConstant($docblockFactory, $expressionPrinter), + new ClassConstant($docblockFactory, $expressionPrinter, [$attributeReducer]), new Factory\File($docblockFactory, NodesFactory::createInstance()), new Function_($docblockFactory, [$attributeReducer, $parameterReducer]), new Interface_($docblockFactory, [$attributeReducer]), diff --git a/src/phpDocumentor/Reflection/Php/Property.php b/src/phpDocumentor/Reflection/Php/Property.php index 58f0d831..7f0c00c5 100644 --- a/src/phpDocumentor/Reflection/Php/Property.php +++ b/src/phpDocumentor/Reflection/Php/Property.php @@ -66,14 +66,16 @@ public function __construct( $this->location = $location ?: new Location(-1); $this->endLocation = $endLocation ?: new Location(-1); - if (is_string($this->default)) { - trigger_error( - 'Default values for properties should be of type Expression, support for strings will be ' - . 'removed in 7.x', - E_USER_DEPRECATED - ); - $this->default = new Expression($this->default, []); + if (!is_string($this->default)) { + return; } + + trigger_error( + 'Default values for properties should be of type Expression, support for strings will be ' + . 'removed in 7.x', + E_USER_DEPRECATED, + ); + $this->default = new Expression($this->default, []); } /** @@ -88,7 +90,7 @@ public function getDefault(bool $asString = true): Expression|string|null if ($asString) { trigger_error( 'The Default value will become of type Expression by default', - E_USER_DEPRECATED + E_USER_DEPRECATED, ); return (string) $this->default; diff --git a/tests/integration/PHP8/ConstructorPromotionTest.php b/tests/integration/PHP8/ConstructorPromotionTest.php index 1a5302fc..236ab092 100644 --- a/tests/integration/PHP8/ConstructorPromotionTest.php +++ b/tests/integration/PHP8/ConstructorPromotionTest.php @@ -4,7 +4,6 @@ namespace integration\PHP8; -use DateTimeImmutable; use DateTimeImmutable; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Tags\Param; @@ -15,8 +14,8 @@ use phpDocumentor\Reflection\Php\Argument; use phpDocumentor\Reflection\Php\Expression; use phpDocumentor\Reflection\Php\Method; -use phpDocumentor\Reflection\Php\ProjectFactory; use phpDocumentor\Reflection\Php\Project; +use phpDocumentor\Reflection\Php\ProjectFactory; use phpDocumentor\Reflection\Php\Property; use phpDocumentor\Reflection\Php\Visibility; use phpDocumentor\Reflection\Types\Array_; @@ -51,7 +50,7 @@ public function testArgumentsAreReadCorrectly() : void $file = $this->project->getFiles()[self::FILE]; $class = $file->getClasses()['\\PHP8\\ConstructorPromotion']; - $constructor = $this->expectedContructorMethod(); + $constructor = $this->expectedConstructorMethod(); $constructor->addArgument(new Argument('name', new String_(), "'default name'")); $constructor->addArgument(new Argument('email', new Object_(new Fqsen('\\PHP8\\Email')))); $constructor->addArgument(new Argument('birth_date', new Object_(new Fqsen('\\' . DateTimeImmutable::class)))); @@ -72,12 +71,12 @@ public function testArgumentsAreReadCorrectly() : void 'uses_constants', new Array_(), new Expression( - '[{{ PHPDOC590f53e8699817c6fa498cc11a4cbe63 }}]', + '[{{ PHPDOC19b72d1f430d952a8dfe2384dd4e93dc }}]', [ - '{{ PHPDOC590f53e8699817c6fa498cc11a4cbe63 }}' => new Fqsen('\PHP8\ConstructorPromotion::DEFAULT_VALUE') - ] - ) - ) + '{{ PHPDOC19b72d1f430d952a8dfe2384dd4e93dc }}' => new Fqsen('\PHP8\ConstructorPromotion::DEFAULT_VALUE'), + ], + ), + ), ); self::assertEquals($constructor, $class->getMethods()['\PHP8\ConstructorPromotion::__construct()']); @@ -94,6 +93,7 @@ public function testPropertiesAreCreated() : void '\PHP8\ConstructorPromotion::$email' => $this->expectedEmailProperty(), '\PHP8\ConstructorPromotion::$birth_date' => $this->expectedBirthDateProperty(), '\PHP8\ConstructorPromotion::$created_at' => $this->expectedCreatedAtProperty(), + '\PHP8\ConstructorPromotion::$uses_constants' => $this->expectedUsesConstantsProperty(), ], $class->getProperties() ); @@ -121,7 +121,7 @@ private function expectedConstructorMethod(): Method false, false, new Location(18, 264), - new Location(31, 709) + new Location(31, 704) ); } @@ -140,8 +140,8 @@ private function expectedNameProperty(): Property ), "'default name'", false, - new Location(26, 393), - new Location(26, 428), + new Location(26), + new Location(26), new String_() ); } @@ -154,9 +154,9 @@ private function expectedEmailProperty(): Property null, null, false, - new Location(27, 439), - new Location(27, 460), - new Object_(new Fqsen('\\PHP8\\Email')) + new Location(27), + new Location(27), + New Object_(new Fqsen('\\PHP8\\Email')), ); } @@ -183,13 +183,32 @@ private function expectedCreatedAtProperty(): Property new Expression( 'new {{ PHPDOC6ffacd918e2f70478d2fd33dcb58c4d4 }}(\'now\')', [ - '{{ PHPDOC6ffacd918e2f70478d2fd33dcb58c4d4 }}' => new Fqsen('\\DateTimeImmutable') - ] + '{{ PHPDOC6ffacd918e2f70478d2fd33dcb58c4d4 }}' => new Fqsen('\\DateTimeImmutable'), + ], ), false, new Location(29), new Location(29), - new Object_(new Fqsen('\\' . DateTimeImmutable::class)) + new Object_(new Fqsen('\\' . DateTimeImmutable::class)), + ); + } + + private function expectedUsesConstantsProperty() + { + return new Property( + new Fqsen('\PHP8\ConstructorPromotion::$uses_constants'), + new Visibility(Visibility::PRIVATE_), + null, + new Expression( + '[{{ PHPDOC19b72d1f430d952a8dfe2384dd4e93dc }}]', + [ + '{{ PHPDOC19b72d1f430d952a8dfe2384dd4e93dc }}' => new Fqsen('\PHP8\ConstructorPromotion::DEFAULT_VALUE'), + ], + ), + false, + new Location(30), + new Location(30), + new Array_(), ); } } diff --git a/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php b/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php index 83995a8c..7fc536d8 100644 --- a/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php +++ b/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php @@ -81,9 +81,7 @@ public function testGettingDocBlock(): void $this->assertSame($this->docBlock, $fixture->getDocBlock()); } - /** - * @covers ::getValue - */ + /** @covers ::getValue */ public function testValueCanBeOmitted(): void { $fixture = new EnumCase( @@ -132,9 +130,7 @@ public function testValueCanBeReturnedAsString(): void $this->assertSame('Enum case expression', $fixture->getValue(true)); } - /** - * @covers ::getLocation - */ + /** @covers ::getLocation */ public function testGetLocationReturnsProvidedValue(): void { $location = new Location(15, 10); @@ -162,9 +158,7 @@ public function testGetLocationReturnsUnknownByDefault(): void self::assertEquals(new Location(-1), $fixture->getLocation()); } - /** - * @covers ::getEndLocation - */ + /** @covers ::getEndLocation */ public function testGetEndLocationReturnsProvidedValue(): void { $location = new Location(11, 23); @@ -178,9 +172,7 @@ public function testGetEndLocationReturnsProvidedValue(): void self::assertSame($location, $fixture->getEndLocation()); } - /** - * @covers ::getEndLocation - */ + /** @covers ::getEndLocation */ public function testGetEndLocationReturnsUnknownByDefault(): void { $fixture = new EnumCase( diff --git a/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php b/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php index 9d290216..33e4cc93 100644 --- a/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php +++ b/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php @@ -29,9 +29,7 @@ final class ExpressionTest extends TestCase private const EXAMPLE_FQSEN = '\\' . self::class; private const EXAMPLE_FQSEN_PLACEHOLDER = '{{ PHPDOC0450ed2a7bac1efcf0c13b6560767954 }}'; - /** - * @covers ::generatePlaceholder - */ + /** @covers ::generatePlaceholder */ public function testGeneratingPlaceholder(): void { $placeholder = Expression::generatePlaceholder(self::EXAMPLE_FQSEN); @@ -39,9 +37,7 @@ public function testGeneratingPlaceholder(): void self::assertSame(self::EXAMPLE_FQSEN_PLACEHOLDER, $placeholder); } - /** - * @covers ::generatePlaceholder - */ + /** @covers ::generatePlaceholder */ public function testGeneratingPlaceholderErrorsUponPassingAnEmptyName(): void { $this->expectException(InvalidArgumentException::class); @@ -49,9 +45,7 @@ public function testGeneratingPlaceholderErrorsUponPassingAnEmptyName(): void Expression::generatePlaceholder(''); } - /** - * @covers ::__construct - */ + /** @covers ::__construct */ public function testExpressionTemplateCannotBeEmpty(): void { $this->expectException(InvalidArgumentException::class); @@ -59,9 +53,7 @@ public function testExpressionTemplateCannotBeEmpty(): void new Expression('', []); } - /** - * @covers ::__construct - */ + /** @covers ::__construct */ public function testPartsShouldContainFqsensOrTypes(): void { $this->expectException(InvalidArgumentException::class); @@ -99,9 +91,7 @@ public function testGetExtractedParts(): void self::assertSame($parts, $result); } - /** - * @covers ::__toString - */ + /** @covers ::__toString */ public function testReplacePlaceholdersWhenCastingToString(): void { $expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER); @@ -113,9 +103,7 @@ public function testReplacePlaceholdersWhenCastingToString(): void self::assertSame(sprintf('This is an %s expression', self::EXAMPLE_FQSEN), $result); } - /** - * @covers ::render - */ + /** @covers ::render */ public function testRenderingExpressionWithoutOverridesIsTheSameAsWhenCastingToString(): void { $expressionTemplate = sprintf('This is an %s expression', self::EXAMPLE_FQSEN_PLACEHOLDER); @@ -127,9 +115,7 @@ public function testRenderingExpressionWithoutOverridesIsTheSameAsWhenCastingToS self::assertSame((string) $expression, $result); } - /** - * @covers ::render - */ + /** @covers ::render */ public function testOverridePartsWhenRenderingExpression(): void { $replacement = 'ExpressionTest';