diff --git a/Makefile b/Makefile
index 1aab96f2..63707afb 100644
--- a/Makefile
+++ b/Makefile
@@ -16,16 +16,16 @@ static-code-analysis: vendor ## Runs a static code analysis with phpstan/phpstan
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 vendor/bin/psalm
.PHONY: test
-test: test-unit test-functional ## Runs all test suites with phpunit/phpunit
+test: test-unit test-integration ## Runs all test suites with phpunit/phpunit
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 vendor/bin/phpunit
.PHONY: test-unit
test-unit: ## Runs unit tests with phpunit/phpunit
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 vendor/bin/phpunit --testsuite=unit
-.PHONY: test-functional
-test-functional: ## Runs unit tests with phpunit/phpunit
- docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 vendor/bin/phpunit --testsuite=functional
+.PHONY: test-integration
+test-integration: ## Runs unit tests with phpunit/phpunit
+ docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 vendor/bin/phpunit --testsuite=integration
.PHONY: dependency-analysis
dependency-analysis: vendor ## Runs a dependency analysis with maglnet/composer-require-checker
diff --git a/docs/expressions.rst b/docs/expressions.rst
new file mode 100644
index 00000000..679e6d40
--- /dev/null
+++ b/docs/expressions.rst
@@ -0,0 +1,60 @@
+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.
+
+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/docs/filtering.rst b/docs/filtering.rst
deleted file mode 100644
index e8425d00..00000000
--- a/docs/filtering.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Filtering
-=========
-
-.. info:: The contents for this page are planned but need to be written, please come back later to check for this
\ No newline at end of file
diff --git a/docs/incremental-updates.rst b/docs/incremental-updates.rst
deleted file mode 100644
index 8f83164b..00000000
--- a/docs/incremental-updates.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Incremental updates
-===================
-
-.. info:: The contents for this page are planned but need to be written, please come back later to check for this
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index d4f2f984..59d0b765 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -2,6 +2,5 @@ Reflection
==========
.. toctree::
- :hidden:
-
+ expressions
meta-data
diff --git a/docs/inspecting.rst b/docs/inspecting.rst
deleted file mode 100644
index 660040f2..00000000
--- a/docs/inspecting.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Inspecting
-==========
-
-.. info:: The contents for this page are planned but need to be written, please come back later to check for this
\ No newline at end of file
diff --git a/docs/integrating-with-silex-and-cilex.rst b/docs/integrating-with-silex-and-cilex.rst
deleted file mode 100644
index 4b80a411..00000000
--- a/docs/integrating-with-silex-and-cilex.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Integrating with Silex and Cilex
-================================
-
-.. info:: The contents for this page are planned but need to be written, please come back later to check for this
\ No newline at end of file
diff --git a/docs/usage.rst b/docs/usage.rst
deleted file mode 100644
index 35474b2a..00000000
--- a/docs/usage.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Usage
-=====
-
-.. info:: The contents for this page are planned but need to be written, please come back later to check for this
\ No newline at end of file
diff --git a/phpstan.neon b/phpstan.neon
index d49d462e..f2b4e184 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -17,10 +17,6 @@ parameters:
- '#Parameter \#1 \$fqsen of class phpDocumentor\\Reflection\\Php\\(.*) constructor expects phpDocumentor\\Reflection\\Fqsen, mixed given\.#'
- '#Parameter \#1 \$fqsen of method phpDocumentor\\Reflection\\Php\\File::addNamespace\(\) expects phpDocumentor\\Reflection\\Fqsen, mixed given\.#'
#
- # there is one test case that prevents changing PropertyIterator::getDefault() to just return Expr (this is set in PhpParser)
- # src/phpDocumentor/Reflection/Php/Factory/Property.php
- - '#Parameter \#1 \$node of method PhpParser\\PrettyPrinterAbstract::prettyPrintExpr\(\) expects PhpParser\\Node\\Expr, PhpParser\\Node\\Expr\|string given\.#'
- #
# Type hint in php-parser is incorrect.
- '#Cannot cast PhpParser\\Node\\Expr\|string to string.#'
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 235ec710..57d87416 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -21,6 +21,9 @@
./tests/integration
+
+
+
./src/
@@ -35,8 +38,8 @@
diff --git a/psalm.xml b/psalm.xml
index 8b73b240..e49273fe 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -9,6 +9,11 @@
+
+
diff --git a/src/phpDocumentor/Reflection/Php/Argument.php b/src/phpDocumentor/Reflection/Php/Argument.php
index 8a0669cd..81b2a0cc 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.
*/
@@ -27,8 +32,8 @@ final class Argument
/** @var Type a normalized type that should be in this Argument */
private Type $type;
- /** @var string|null the default value for an argument or null if none is provided */
- private ?string $default;
+ /** @var Expression|null the default value for an argument or null if none is provided */
+ private ?Expression $default;
/** @var bool whether the argument passes the parameter by reference instead of by value */
private bool $byReference;
@@ -38,22 +43,34 @@ final class Argument
/**
* Initializes the object.
+ *
+ * @param string|Expression|null $default
*/
public function __construct(
string $name,
?Type $type = null,
- ?string $default = null,
+ $default = null,
bool $byReference = false,
bool $isVariadic = false
) {
$this->name = $name;
- $this->default = $default;
$this->byReference = $byReference;
$this->isVariadic = $isVariadic;
if ($type === null) {
$type = new Mixed_();
}
+ if (is_string($default)) {
+ trigger_error(
+ 'Default values for arguments should be of type Expression, support for strings will be '
+ . 'removed in 6.x',
+ E_USER_DEPRECATED
+ );
+ $default = new Expression($default, []);
+ }
+
+ $this->default = $default;
+
$this->type = $type;
}
@@ -70,8 +87,24 @@ public function getType(): ?Type
return $this->type;
}
- public function getDefault(): ?string
+ /**
+ * @return Expression|string|null
+ */
+ public function getDefault(bool $asString = true)
{
+ if ($this->default === null) {
+ return 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 8b95e652..ede8bc41 100644
--- a/src/phpDocumentor/Reflection/Php/Constant.php
+++ b/src/phpDocumentor/Reflection/Php/Constant.php
@@ -19,6 +19,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
*/
@@ -30,7 +35,8 @@ final class Constant implements Element, MetaDataContainerInterface
private ?DocBlock $docBlock;
- private ?string $value;
+ /** @var string|Expression|null */
+ private $value;
private Location $location;
@@ -42,11 +48,13 @@ final class Constant implements Element, MetaDataContainerInterface
/**
* Initializes the object.
+ *
+ * @param Expression|string|null $value
*/
public function __construct(
Fqsen $fqsen,
?DocBlock $docBlock = null,
- ?string $value = null,
+ $value = null,
?Location $location = null,
?Location $endLocation = null,
?Visibility $visibility = null,
@@ -54,18 +62,43 @@ public function __construct(
) {
$this->fqsen = $fqsen;
$this->docBlock = $docBlock;
- $this->value = $value;
$this->location = $location ?: new Location(-1);
$this->endLocation = $endLocation ?: new Location(-1);
$this->visibility = $visibility ?: new Visibility(Visibility::PUBLIC_);
$this->final = $final;
+
+ if (is_string($value)) {
+ trigger_error(
+ 'Constant values should be of type Expression, support for strings will be '
+ . 'removed in 6.x',
+ E_USER_DEPRECATED
+ );
+ $value = new Expression($value, []);
+ }
+
+ $this->value = $value;
}
/**
- * Returns the value of this constant.
+ * Returns the expression value for this constant.
+ *
+ * @return Expression|string|null
*/
- public function getValue(): ?string
+ public function getValue(bool $asString = true)
{
+ if ($this->value === null) {
+ return 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 0d32e369..2ff6f992 100644
--- a/src/phpDocumentor/Reflection/Php/EnumCase.php
+++ b/src/phpDocumentor/Reflection/Php/EnumCase.php
@@ -10,6 +10,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;
+
final class EnumCase implements Element, MetaDataContainerInterface
{
use MetadataContainer;
@@ -22,14 +27,18 @@ final class EnumCase implements Element, MetaDataContainerInterface
private Location $endLocation;
- private ?string $value;
+ /** @var Expression|string|null */
+ private $value;
+ /**
+ * @param Expression|string|null $value
+ */
public function __construct(
Fqsen $fqsen,
?DocBlock $docBlock,
?Location $location = null,
?Location $endLocation = null,
- ?string $value = null
+ $value = null
) {
if ($location === null) {
$location = new Location(-1);
@@ -43,6 +52,15 @@ public function __construct(
$this->docBlock = $docBlock;
$this->location = $location;
$this->endLocation = $endLocation;
+ if (is_string($value)) {
+ trigger_error(
+ 'Expression values for enum cases should be of type Expression, support for strings will be '
+ . 'removed in 6.x',
+ E_USER_DEPRECATED
+ );
+ $value = new Expression($value, []);
+ }
+
$this->value = $value;
}
@@ -71,8 +89,26 @@ public function getEndLocation(): Location
return $this->endLocation;
}
- public function getValue(): ?string
+ /**
+ * Returns the value for this enum case.
+ *
+ * @return Expression|string|null
+ */
+ public function getValue(bool $asString = true)
{
+ if ($this->value === null) {
+ return 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..acbad7cc
--- /dev/null
+++ b/src/phpDocumentor/Reflection/Php/Expression.php
@@ -0,0 +1,156 @@
+ 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
+ {
+ return $this->parts;
+ }
+
+ /**
+ * 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
+ {
+ 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
new file mode 100644
index 00000000..3d52c188
--- /dev/null
+++ b/src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php
@@ -0,0 +1,60 @@
+ */
+ private array $parts = [];
+
+ protected function resetState(): void
+ {
+ parent::resetState();
+
+ $this->parts = [];
+ }
+
+ protected function pName(Name $node): string
+ {
+ $renderedName = parent::pName($node);
+ $placeholder = Expression::generatePlaceholder($renderedName);
+ $this->parts[$placeholder] = new Fqsen('\\' . $renderedName);
+
+ 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
+ */
+ 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
index e04daa3a..6d07b30e 100644
--- a/src/phpDocumentor/Reflection/Php/Factory/Argument.php
+++ b/src/phpDocumentor/Reflection/Php/Factory/Argument.php
@@ -14,6 +14,8 @@
namespace phpDocumentor\Reflection\Php\Factory;
use phpDocumentor\Reflection\Php\Argument as ArgumentDescriptor;
+use phpDocumentor\Reflection\Php\Expression;
+use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\Function_;
use phpDocumentor\Reflection\Php\Method;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
@@ -23,6 +25,8 @@
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
+use function is_string;
+
/**
* Strategy to convert Param to Argument
*
@@ -77,10 +81,28 @@ protected function doCreate(
new ArgumentDescriptor(
(string) $object->var->name,
(new Type())->fromPhpParser($object->type),
- $object->default !== null ? $this->valueConverter->prettyPrintExpr($object->default) : null,
+ $this->determineDefault($object),
$object->byRef,
$object->variadic
)
);
}
+
+ private function determineDefault(Param $value): ?Expression
+ {
+ $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/Factory/ClassConstant.php b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php
index 4d479ae8..75261de7 100644
--- a/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php
+++ b/src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php
@@ -18,6 +18,8 @@
use phpDocumentor\Reflection\Php\Class_;
use phpDocumentor\Reflection\Php\Constant as ConstantElement;
use phpDocumentor\Reflection\Php\Enum_;
+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_;
@@ -26,6 +28,8 @@
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
+use function is_string;
+
/**
* Strategy to convert ClassConstantIterator to ConstantElement
*
@@ -79,7 +83,7 @@ protected function doCreate(
$constantContainer->addConstant(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),
@@ -88,6 +92,20 @@ protected function doCreate(
}
}
+ private function determineValue(ClassConstantIterator $value): Expression
+ {
+ $expression = $this->valueConverter->prettyPrintExpr($value->getValue());
+ 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/ConstructorPromotion.php b/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php
index 00e77718..ea0436ba 100644
--- a/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php
+++ b/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php
@@ -9,6 +9,8 @@
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Class_ as ClassElement;
+use phpDocumentor\Reflection\Php\Expression;
+use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategy;
use phpDocumentor\Reflection\Php\Property;
use phpDocumentor\Reflection\Php\StrategyContainer;
@@ -20,6 +22,8 @@
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
+use function is_string;
+
final class ConstructorPromotion extends AbstractFactory
{
private PrettyPrinter $valueConverter;
@@ -72,7 +76,7 @@ private function promoteParameterToProperty(ContextStack $context, Param $param)
new Fqsen($methodContainer->getFqsen() . '::$' . (string) $param->var->name),
$this->buildPropertyVisibilty($param->flags),
$this->createDocBlock($param->getDocComment(), $context->getTypeContext()),
- $param->default !== null ? $this->valueConverter->prettyPrintExpr($param->default) : null,
+ $this->determineDefault($param),
false,
new Location($param->getLine()),
new Location($param->getEndLine()),
@@ -83,6 +87,24 @@ private function promoteParameterToProperty(ContextStack $context, Param $param)
$methodContainer->addProperty($property);
}
+ private function determineDefault(Param $value): ?Expression
+ {
+ $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 buildPropertyVisibilty(int $flags): Visibility
{
if ((bool) ($flags & Class_::MODIFIER_PRIVATE) === true) {
diff --git a/src/phpDocumentor/Reflection/Php/Factory/Define.php b/src/phpDocumentor/Reflection/Php/Factory/Define.php
index dca55fbd..4178d3b8 100644
--- a/src/phpDocumentor/Reflection/Php/Factory/Define.php
+++ b/src/phpDocumentor/Reflection/Php/Factory/Define.php
@@ -17,6 +17,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;
@@ -30,6 +32,7 @@
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use function assert;
+use function is_string;
use function sprintf;
use function strpos;
@@ -119,13 +122,22 @@ protected function doCreate(
$file->addConstant($constant);
}
- private function determineValue(?Arg $value): ?string
+ private function determineValue(?Arg $value): ?ValueExpression
{
if ($value === null) {
return null;
}
- return $this->valueConverter->prettyPrintExpr($value->value);
+ $expression = $this->valueConverter->prettyPrintExpr($value->value);
+ 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
diff --git a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php
index de419b42..c71b2b90 100644
--- a/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php
+++ b/src/phpDocumentor/Reflection/Php/Factory/EnumCase.php
@@ -8,11 +8,14 @@
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Enum_ as EnumElement;
use phpDocumentor\Reflection\Php\EnumCase as EnumCaseElement;
+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
{
@@ -21,6 +24,7 @@ final class EnumCase extends AbstractFactory
public function __construct(DocBlockFactoryInterface $docBlockFactory, PrettyPrinter $prettyPrinter)
{
parent::__construct($docBlockFactory);
+
$this->prettyPrinter = $prettyPrinter;
}
@@ -37,12 +41,31 @@ protected function doCreate(ContextStack $context, object $object, StrategyConta
$docBlock = $this->createDocBlock($object->getDocComment(), $context->getTypeContext());
$enum = $context->peek();
assert($enum instanceof EnumElement);
+
$enum->addCase(new EnumCaseElement(
$object->getAttribute('fqsen'),
$docBlock,
new Location($object->getLine()),
new Location($object->getEndLine()),
- $object->expr !== null ? $this->prettyPrinter->prettyPrintExpr($object->expr) : null
+ $this->determineValue($object)
));
}
+
+ private function determineValue(EnumCaseNode $value): ?ValueExpression
+ {
+ $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 $expression;
+ }
}
diff --git a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php
index e2e0abd3..59900e66 100644
--- a/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php
+++ b/src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php
@@ -16,12 +16,16 @@
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_;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
+use function is_string;
+
/**
* Strategy to convert GlobalConstantIterator to ConstantElement
*
@@ -70,11 +74,25 @@ 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())
)
);
}
}
+
+ private function determineValue(GlobalConstantIterator $value): Expression
+ {
+ $expression = $this->valueConverter->prettyPrintExpr($value->getValue());
+ 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 4be697d4..347f292a 100644
--- a/src/phpDocumentor/Reflection/Php/Factory/Property.php
+++ b/src/phpDocumentor/Reflection/Php/Factory/Property.php
@@ -16,6 +16,8 @@
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Location;
use phpDocumentor\Reflection\Php\Class_;
+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;
@@ -25,6 +27,8 @@
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use Webmozart\Assert\Assert;
+use function is_string;
+
/**
* Strategy to convert PropertyIterator to PropertyDescriptor
*
@@ -74,17 +78,12 @@ protected function doCreate(
$iterator = new PropertyIterator($object);
foreach ($iterator as $stmt) {
- $default = null;
- if ($iterator->getDefault() !== null) {
- $default = $this->valueConverter->prettyPrintExpr($iterator->getDefault());
- }
-
$propertyContainer->addProperty(
new PropertyDescriptor(
$stmt->getFqsen(),
$this->buildVisibility($stmt),
$this->createDocBlock($stmt->getDocComment(), $context->getTypeContext()),
- $default,
+ $this->determineDefault($stmt),
$stmt->isStatic(),
new Location($stmt->getLine()),
new Location($stmt->getEndLine()),
@@ -95,6 +94,25 @@ protected function doCreate(
}
}
+ private function determineDefault(PropertyIterator $value): ?Expression
+ {
+ $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;
+ }
+
/**
* Converts the visibility of the property to a valid Visibility object.
*/
diff --git a/src/phpDocumentor/Reflection/Php/Factory/PropertyIterator.php b/src/phpDocumentor/Reflection/Php/Factory/PropertyIterator.php
index 6447c29d..bcc25c22 100644
--- a/src/phpDocumentor/Reflection/Php/Factory/PropertyIterator.php
+++ b/src/phpDocumentor/Reflection/Php/Factory/PropertyIterator.php
@@ -133,11 +133,9 @@ public function getName(): string
}
/**
- * returns the default value of the current property.
- *
- * @return string|Expr|null
+ * Returns the default value of the current property.
*/
- public function getDefault()
+ public function getDefault(): ?Expr
{
return $this->property->props[$this->index]->default;
}
diff --git a/src/phpDocumentor/Reflection/Php/ProjectFactory.php b/src/phpDocumentor/Reflection/Php/ProjectFactory.php
index fea139d0..49c4c7d6 100644
--- a/src/phpDocumentor/Reflection/Php/ProjectFactory.php
+++ b/src/phpDocumentor/Reflection/Php/ProjectFactory.php
@@ -17,6 +17,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\Argument;
use phpDocumentor\Reflection\Php\Factory\Class_;
use phpDocumentor\Reflection\Php\Factory\ClassConstant;
@@ -30,13 +31,13 @@
use phpDocumentor\Reflection\Php\Factory\IfStatement;
use phpDocumentor\Reflection\Php\Factory\Interface_;
use phpDocumentor\Reflection\Php\Factory\Method;
+use phpDocumentor\Reflection\Php\Factory\Namespace_ as NamespaceNode;
use phpDocumentor\Reflection\Php\Factory\Noop;
use phpDocumentor\Reflection\Php\Factory\Property;
use phpDocumentor\Reflection\Php\Factory\Trait_;
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;
@@ -65,24 +66,24 @@ public function __construct($strategies)
public static function createInstance(): self
{
$docblockFactory = DocBlockFactory::createInstance();
-
+ $expressionPrinter = new ExpressionPrinter();
$methodStrategy = new Method($docblockFactory);
$strategies = new ProjectFactoryStrategies(
[
- new \phpDocumentor\Reflection\Php\Factory\Namespace_(),
- new Argument(new PrettyPrinter()),
+ new NamespaceNode(),
+ new Argument($expressionPrinter),
new Class_($docblockFactory),
new Enum_($docblockFactory),
- new EnumCase($docblockFactory, new PrettyPrinter()),
- new Define($docblockFactory, new PrettyPrinter()),
- new GlobalConstant($docblockFactory, new PrettyPrinter()),
- new ClassConstant($docblockFactory, new PrettyPrinter()),
+ new EnumCase($docblockFactory, $expressionPrinter),
+ new Define($docblockFactory, $expressionPrinter),
+ new GlobalConstant($docblockFactory, $expressionPrinter),
+ new ClassConstant($docblockFactory, $expressionPrinter),
new Factory\File($docblockFactory, NodesFactory::createInstance()),
new Function_($docblockFactory),
new Interface_($docblockFactory),
$methodStrategy,
- new Property($docblockFactory, new PrettyPrinter()),
+ new Property($docblockFactory, $expressionPrinter),
new Trait_($docblockFactory),
new IfStatement(),
new TraitUse(),
@@ -90,14 +91,12 @@ public static function createInstance(): self
);
$strategies->addStrategy(
- new ConstructorPromotion($methodStrategy, $docblockFactory, new PrettyPrinter()),
+ new ConstructorPromotion($methodStrategy, $docblockFactory, $expressionPrinter),
1100
);
$strategies->addStrategy(new Noop(), -PHP_INT_MAX);
- return new static(
- $strategies
- );
+ return new static($strategies);
}
public function addStrategy(
diff --git a/src/phpDocumentor/Reflection/Php/Property.php b/src/phpDocumentor/Reflection/Php/Property.php
index 326ab221..0a115a6c 100644
--- a/src/phpDocumentor/Reflection/Php/Property.php
+++ b/src/phpDocumentor/Reflection/Php/Property.php
@@ -20,6 +20,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.
*/
@@ -34,7 +39,7 @@ final class Property implements Element, MetaDataContainerInterface
/** @var string[] $types */
private array $types = [];
- private ?string $default = null;
+ private ?Expression $default = null;
private bool $static = false;
@@ -50,12 +55,13 @@ final class Property implements Element, MetaDataContainerInterface
/**
* @param Visibility|null $visibility when null is provided a default 'public' is set.
+ * @param string|Expression|null $default
*/
public function __construct(
Fqsen $fqsen,
?Visibility $visibility = null,
?DocBlock $docBlock = null,
- ?string $default = null,
+ $default = null,
bool $static = false,
?Location $location = null,
?Location $endLocation = null,
@@ -65,19 +71,44 @@ public function __construct(
$this->fqsen = $fqsen;
$this->visibility = $visibility ?: new Visibility('public');
$this->docBlock = $docBlock;
- $this->default = $default;
$this->static = $static;
$this->location = $location ?: new Location(-1);
$this->endLocation = $endLocation ?: new Location(-1);
$this->type = $type;
$this->readOnly = $readOnly;
+
+ if (is_string($default)) {
+ trigger_error(
+ 'Default values for properties should be of type Expression, support for strings will be '
+ . 'removed in 6.x',
+ E_USER_DEPRECATED
+ );
+ $default = new Expression($default, []);
+ }
+
+ $this->default = $default;
}
/**
- * returns the default value of this property.
+ * Returns the default value for this property.
+ *
+ * @return Expression|string|null
*/
- public function getDefault(): ?string
+ public function getDefault(bool $asString = true)
{
+ if ($this->default === null) {
+ return 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/FileDocblockTest.php b/tests/integration/FileDocblockTest.php
index e33edb27..a6af1131 100644
--- a/tests/integration/FileDocblockTest.php
+++ b/tests/integration/FileDocblockTest.php
@@ -10,6 +10,8 @@
/**
* Integration tests to check the correct working of processing a namespace into a project.
+ *
+ * @coversNothing
*/
final class FileDocblockTest extends TestCase
{
diff --git a/tests/integration/PHP8/ConstructorPromotionTest.php b/tests/integration/PHP8/ConstructorPromotionTest.php
index d14b435a..ae377f0a 100644
--- a/tests/integration/PHP8/ConstructorPromotionTest.php
+++ b/tests/integration/PHP8/ConstructorPromotionTest.php
@@ -4,6 +4,7 @@
namespace integration\PHP8;
+use DateTimeImmutable;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
@@ -11,11 +12,13 @@
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;
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_;
@@ -26,12 +29,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
{
@@ -44,30 +45,62 @@ protected function setUp() : void
);
}
- public function testPropertiesAreCreated() : void
+ 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_()));
$constructor->addArgument(new Argument('email', new String_(), '\'test@example.com\''));
- $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)),
+ 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::$birth_date' => $this->expectedBirthDateProperty(),
+ '\PHP8\ConstructorPromotion::$created_at' => $this->expectedCreatedAtProperty(),
],
$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,15 +119,14 @@ private function expectedContructorMethod(): Method
false,
false,
false,
- new Location(16, 218),
- new Location(27, 522)
+ new Location(18, 264),
+ new Location(31, 709)
);
- return $constructor;
}
private function expectedNameProperty(): Property
{
- $name = new Property(
+ return new Property(
new Fqsen('\PHP8\ConstructorPromotion::$name'),
new Visibility(Visibility::PUBLIC_),
new DocBlock(
@@ -107,40 +139,56 @@ private function expectedNameProperty(): Property
),
null,
false,
- new Location(24),
- new Location(24),
+ new Location(26),
+ new Location(26),
new String_()
);
- return $name;
}
private function expectedEmailProperty(): Property
{
- $email = new Property(
+ return new Property(
new Fqsen('\PHP8\ConstructorPromotion::$email'),
new Visibility(Visibility::PROTECTED_),
null,
'\'test@example.com\'',
false,
- new Location(25),
- new Location(25),
+ new Location(27),
+ new Location(27),
new String_()
);
- return $email;
}
private function expectedBirthDateProperty(): Property
{
- $birthDate = new Property(
+ return 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))
+ new Location(28),
+ new Location(28),
+ 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))
);
- return $birthDate;
}
}
diff --git a/tests/integration/data/PHP8/ConstructorPromotion.php b/tests/integration/data/PHP8/ConstructorPromotion.php
index cc1883c6..1ccf68ac 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,
protected string $email = 'test@example.com',
private DateTimeImmutable $birth_date,
+ private DateTimeImmutable $created_at = new DateTimeImmutable('now'),
+ private array $uses_constants = [self::DEFAULT_VALUE],
) {}
}
diff --git a/tests/unit/phpDocumentor/Reflection/Php/ArgumentTest.php b/tests/unit/phpDocumentor/Reflection/Php/ArgumentTest.php
index 38478a10..d44f6f28 100644
--- a/tests/unit/phpDocumentor/Reflection/Php/ArgumentTest.php
+++ b/tests/unit/phpDocumentor/Reflection/Php/ArgumentTest.php
@@ -32,17 +32,17 @@ 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());
}
/**
@@ -50,8 +50,9 @@ public function testGetTypes(): void
*/
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());
}
/**
@@ -59,11 +60,11 @@ public function testGetName(): void
*/
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());
}
/**
@@ -71,11 +72,11 @@ public function testGetDefault(): void
*/
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());
}
/**
@@ -83,10 +84,10 @@ public function testGetWhetherArgumentIsPassedByReference(): void
*/
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 40cf4144..3717ce84 100644
--- a/tests/unit/phpDocumentor/Reflection/Php/ConstantTest.php
+++ b/tests/unit/phpDocumentor/Reflection/Php/ConstantTest.php
@@ -19,9 +19,9 @@
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
/**
- * @uses \phpDocumentor\Reflection\DocBlock
- * @uses \phpDocumentor\Reflection\Php\Visibility
- * @uses \phpDocumentor\Reflection\Fqsen
+ * @uses DocBlock
+ * @uses Visibility
+ * @uses Fqsen
*
* @coversDefaultClass \phpDocumentor\Reflection\Php\Constant
* @covers ::__construct
@@ -46,7 +46,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
@@ -60,7 +60,7 @@ private function getFixture(): MetaDataContainerInterface
*/
public function testGetValue(): void
{
- $this->assertSame($this->value, $this->fixture->getValue());
+ self::assertEquals(new Expression($this->value), $this->fixture->getValue());
}
/**
@@ -69,7 +69,7 @@ public function testGetValue(): void
*/
public function testIsFinal(): void
{
- $this->assertFalse($this->fixture->isFinal());
+ self::assertFalse($this->fixture->isFinal());
}
/**
@@ -78,8 +78,8 @@ public function testIsFinal(): void
*/
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());
}
/**
@@ -87,7 +87,7 @@ public function testGetFqsen(): void
*/
public function testGetDocblock(): void
{
- $this->assertSame($this->docBlock, $this->fixture->getDocBlock());
+ self::assertSame($this->docBlock, $this->fixture->getDocBlock());
}
/**
@@ -95,7 +95,7 @@ public function testGetDocblock(): void
*/
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 d2f4dabe..09e2583a 100644
--- a/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php
+++ b/tests/unit/phpDocumentor/Reflection/Php/EnumCaseTest.php
@@ -36,14 +36,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
@@ -56,7 +60,12 @@ 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());
}
/**
@@ -64,7 +73,12 @@ public function testGettingName(): void
*/
public function testGettingFqsen(): void
{
- $this->assertSame($this->fqsen, $this->fixture->getFqsen());
+ $fixture = new EnumCase(
+ $this->fqsen,
+ $this->docBlock
+ );
+
+ $this->assertSame($this->fqsen, $fixture->getFqsen());
}
/**
@@ -72,22 +86,121 @@ public function testGettingFqsen(): void
*/
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 testGetValue(): void
+ 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));
+ }
+
+ /**
+ * @uses Expression
+ *
+ * @covers ::getValue
+ */
+ public function testValueCanBeReturnedAsString(): void
+ {
+ $expression = new Expression('Enum case expression');
+ $fixture = new EnumCase(
+ $this->fqsen,
+ $this->docBlock,
+ null,
+ null,
+ $expression
+ );
+
+ $this->assertSame('Enum case expression', $fixture->getValue(true));
+ }
+
+ /**
+ * @covers ::getLocation
+ */
+ public function testGetLocationReturnsProvidedValue(): void
{
- $this->assertNull($this->fixture->getValue());
+ $location = new Location(15, 10);
+ $fixture = new EnumCase(
+ $this->fqsen,
+ $this->docBlock,
+ $location
+ );
+
+ self::assertSame($location, $fixture->getLocation());
}
/**
+ * @uses Location
+ *
* @covers ::getLocation
*/
- public function testGetLocationReturnsDefault(): void
+ public function testGetLocationReturnsUnknownByDefault(): void
{
- self::assertEquals(new Location(-1), $this->fixture->getLocation());
+ $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
new file mode 100644
index 00000000..9d290216
--- /dev/null
+++ b/tests/unit/phpDocumentor/Reflection/Php/ExpressionTest.php
@@ -0,0 +1,145 @@
+
+ */
+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);
+ }
+}
diff --git a/tests/unit/phpDocumentor/Reflection/Php/Factory/ArgumentTest.php b/tests/unit/phpDocumentor/Reflection/Php/Factory/ArgumentTest.php
index 2cae57cf..c1cc9882 100644
--- a/tests/unit/phpDocumentor/Reflection/Php/Factory/ArgumentTest.php
+++ b/tests/unit/phpDocumentor/Reflection/Php/Factory/ArgumentTest.php
@@ -16,12 +16,12 @@
use Mockery as m;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Php\Argument as ArgumentDescriptor;
+use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
use phpDocumentor\Reflection\Php\Method as MethodElement;
use phpDocumentor\Reflection\Php\ProjectFactoryStrategies;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\String_;
-use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use stdClass;
/**
@@ -40,7 +40,7 @@ class ArgumentTest extends TestCase
{
protected function setUp(): void
{
- $this->fixture = new Argument(new PrettyPrinter());
+ $this->fixture = new Argument(new ExpressionPrinter());
}
/**
diff --git a/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyIteratorTest.php b/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyIteratorTest.php
index ce1d1945..3dc59115 100644
--- a/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyIteratorTest.php
+++ b/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyIteratorTest.php
@@ -16,6 +16,7 @@
use Mockery as m;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use PhpParser\Comment\Doc;
+use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Property as PropertyNode;
use PhpParser\Node\Stmt\PropertyProperty;
@@ -98,12 +99,12 @@ public function testProxyMethods(): void
public function testGetDefault(): void
{
$prop = m::mock(PropertyProperty::class);
- $prop->default = 'myDefault';
+ $prop->default = new String_('myDefault');
$property = new PropertyNode(1, [$prop]);
$fixture = new PropertyIterator($property);
- $this->assertEquals('myDefault', $fixture->getDefault());
+ $this->assertEquals(new String_('myDefault'), $fixture->getDefault());
}
/**
diff --git a/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyTest.php b/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyTest.php
index 047f2574..57e47bd5 100644
--- a/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyTest.php
+++ b/tests/unit/phpDocumentor/Reflection/Php/Factory/PropertyTest.php
@@ -33,11 +33,11 @@
use function next;
/**
- * @uses \phpDocumentor\Reflection\Php\Factory\PropertyIterator
- * @uses \phpDocumentor\Reflection\Php\Property
+ * @uses PropertyIterator
+ * @uses PropertyDescriptor
* @uses \phpDocumentor\Reflection\Php\Visibility
- * @uses \phpDocumentor\Reflection\Php\ProjectFactoryStrategies
- * @uses \phpDocumentor\Reflection\Php\Factory\Type
+ * @uses ProjectFactoryStrategies
+ * @uses Type
*
* @covers \phpDocumentor\Reflection\Php\Factory\Property
* @covers \phpDocumentor\Reflection\Php\Factory\AbstractFactory
@@ -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);
diff --git a/tests/unit/phpDocumentor/Reflection/Php/Factory/TestCase.php b/tests/unit/phpDocumentor/Reflection/Php/Factory/TestCase.php
index 6ca49fbd..6ab04661 100644
--- a/tests/unit/phpDocumentor/Reflection/Php/Factory/TestCase.php
+++ b/tests/unit/phpDocumentor/Reflection/Php/Factory/TestCase.php
@@ -27,8 +27,7 @@
*/
abstract class TestCase extends MockeryTestCase
{
- /** @var ProjectFactoryStrategy */
- protected $fixture;
+ protected ProjectFactoryStrategy $fixture;
public static function createContext(?Context $typeContext = null): ContextStack
{
diff --git a/tests/unit/phpDocumentor/Reflection/Php/PropertyTest.php b/tests/unit/phpDocumentor/Reflection/Php/PropertyTest.php
index ba05b9d9..cecaf6bc 100644
--- a/tests/unit/phpDocumentor/Reflection/Php/PropertyTest.php
+++ b/tests/unit/phpDocumentor/Reflection/Php/PropertyTest.php
@@ -60,8 +60,8 @@ 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());
}
/**
@@ -73,10 +73,10 @@ public function testGetFqsenAndGetName(): void
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());
}
/**
@@ -88,7 +88,7 @@ public function testGettingWhetherPropertyIsStatic(): void
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,
@@ -102,7 +102,7 @@ public function testGettingWhetherPropertyIsReadOnly(): void
true
);
- $this->assertTrue($property->isReadOnly());
+ self::assertTrue($property->isReadOnly());
}
/**
@@ -115,7 +115,7 @@ 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());
}
/**
@@ -127,10 +127,10 @@ public function testGettingVisibility(): void
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());
}
/**
@@ -141,10 +141,12 @@ public function testSetAndGetTypes(): void
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));
}
/**
@@ -155,7 +157,7 @@ public function testGetDefault(): void
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
@@ -184,9 +186,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());
}
}