diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c57d558..0281edfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - yyyy-mm-dd +## [5.5.3] - 2023-10-11 + +- Fix crash on `IntersectionType` (intersection types) + - Support `@category` tags on classes and methods ## [5.5.2] - 2023-03-12 diff --git a/src/Parser/NodeVisitor.php b/src/Parser/NodeVisitor.php index 189cb974..788a21e7 100644 --- a/src/Parser/NodeVisitor.php +++ b/src/Parser/NodeVisitor.php @@ -131,18 +131,7 @@ protected function addFunction(FunctionNode $node, ?string $namespace = null) $parameter->setVariadic($param->variadic); - $type = $param->type; - $typeStr = $this->typeToString($type); - - if (null !== $typeStr) { - $typeArr = [[$typeStr, false]]; - - if ($param->type instanceof NullableType) { - $typeArr[] = ['null', false]; - } - - $parameter->setHint($this->resolveHint($typeArr)); - } + $this->manageHint($param->type, $parameter); $function->addParameter($parameter); } @@ -167,18 +156,7 @@ protected function addFunction(FunctionNode $node, ?string $namespace = null) $function->setModifiersFromTags(); $function->setErrors($errors); - $returnType = $node->getReturnType(); - $returnTypeStr = $this->typeToString($returnType); - - if (null !== $returnTypeStr) { - $returnTypeArr = [[$returnTypeStr, false]]; - - if ($returnType instanceof NullableType) { - $returnTypeArr[] = ['null', false]; - } - - $function->setHint($this->resolveHint($returnTypeArr)); - } + $this->manageHint($node->getReturnType(), $function); $this->context->addFunction($function); @@ -188,7 +166,7 @@ protected function addFunction(FunctionNode $node, ?string $namespace = null) } /** - * @param \PhpParser\Node\Identifier|\PhpParser\Node\Name|NullableType|UnionType|IntersectionType|null $type Type declaration + * @param \PhpParser\Node\ComplexType|\PhpParser\Node\Identifier|\PhpParser\Node\Name|NullableType|UnionType|IntersectionType|null $type Type declaration */ protected function typeToString($type): ?string { @@ -206,9 +184,13 @@ protected function typeToString($type): ?string } elseif ($type instanceof IntersectionType) { $typeString = []; foreach ($type->types as $type) { - $typeString[] = $type->__toString(); + $typeAsStr = $type->__toString(); + if ($type instanceof FullyQualified && 0 !== strpos($typeAsStr, '\\')) { + $typeAsStr = '\\' . $typeAsStr; + } + $typeString[] = $typeAsStr; } - $typeString = implode('&', $typeString); + return implode('&', $typeString); } if ($typeString === null) { @@ -332,18 +314,7 @@ protected function addMethod(ClassMethodNode $node) $parameter->setVariadic($param->variadic); - $type = $param->type; - $typeStr = $this->typeToString($type); - - if (null !== $typeStr) { - $typeArr = [[$typeStr, false]]; - - if ($param->type instanceof NullableType) { - $typeArr[] = ['null', false]; - } - - $parameter->setHint($this->resolveHint($typeArr)); - } + $this->manageHint($param->type, $parameter); $method->addParameter($parameter); } @@ -371,18 +342,7 @@ protected function addMethod(ClassMethodNode $node) $method->setModifiersFromTags(); $method->setErrors($errors); - $returnType = $node->getReturnType(); - $returnTypeStr = $this->typeToString($returnType); - - if (null !== $returnTypeStr) { - $returnTypeArr = [[$returnTypeStr, false]]; - - if ($returnType instanceof NullableType) { - $returnTypeArr[] = ['null', false]; - } - - $method->setHint($this->resolveHint($returnTypeArr)); - } + $this->manageHint($node->getReturnType(), $method); if ($this->context->getFilter()->acceptMethod($method)) { $this->context->getClass()->addMethod($method); @@ -417,6 +377,14 @@ protected function addTagFromCommentToMethod( if (is_array($firstTagFound)) { $hint = $firstTagFound[0]; $hintDescription = $firstTagFound[1] ?? null; + if (is_array($hint) && isset($hint[0]) && stripos($hint[0][0] ?? '', '&') !== false) {// Detect intersection type + $methodOrFunctionOrProperty->setIntersectionType(true); + $intersectionParts = explode('&', $hint[0][0]); + $hint = []; + foreach ($intersectionParts as $part) { + $hint[] = [$part, false]; + } + } $methodOrFunctionOrProperty->setHint(is_array($hint) ? $this->resolveHint($hint) : $hint); if ($hintDescription !== null) { if (is_string($hintDescription)) { @@ -457,6 +425,36 @@ protected function addProperty(PropertyNode $node) } } + /** + * @param \PhpParser\Node\ComplexType|\PhpParser\Node\Identifier|\PhpParser\Node\Name|NullableType|UnionType|IntersectionType|null $type Type declaration + * @param MethodReflection|FunctionReflection|ParameterReflection|PropertyReflection $object + */ + protected function manageHint($type, Reflection $object): void + { + if ($type instanceof IntersectionType) { + $object->setIntersectionType(true); + + $typeArr = []; + foreach ($type->types as $type) { + $typeStr = $this->typeToString($type); + $typeArr[] = [$typeStr, false]; + } + + $object->setHint($this->resolveHint($typeArr)); + } else { + $typeStr = $this->typeToString($type); + + if (null !== $typeStr) { + $typeArr = [[$typeStr, false]]; + + if ($type instanceof NullableType) { + $typeArr[] = ['null', false]; + } + $object->setHint($this->resolveHint($typeArr)); + } + } + } + /** * @return array * @phpstan-return array{PropertyReflection,string[]} @@ -475,6 +473,9 @@ protected function getPropertyReflectionFromParserProperty(PropertyNode $node, P $property->setShortDesc($comment->getShortDesc()); $property->setLongDesc($comment->getLongDesc()); $property->setSee($this->resolveSee($comment->getTag('see'))); + + $this->manageHint($node->type, $property); + if ($errors = $comment->getErrors()) { $property->setErrors($errors); } else { @@ -617,6 +618,9 @@ protected function updateMethodParametersFromTags(Reflection $method, array $tag return $errors; } + /** + * @phpstan-param $hints array{0: string, 1: bool} + */ protected function resolveHint(array $hints): array { foreach ($hints as $i => $hint) { @@ -626,6 +630,9 @@ protected function resolveHint(array $hints): array return $hints; } + /** + * @phpstan-param $alias array{0: string, 1: bool} + */ protected function resolveAlias($alias) { // not a class diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index 038b4bdd..915f76b0 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -19,6 +19,8 @@ class FunctionReflection extends Reflection /** @var array */ protected $parameters = []; protected $byRef; + /** @var bool */ + protected $isIntersectionType = false; protected $project; /** @var string|null */ protected $file = null; @@ -43,6 +45,16 @@ public function isByRef() return $this->byRef; } + public function setIntersectionType(bool $boolean): void + { + $this->isIntersectionType = $boolean; + } + + public function isIntersectionType(): bool + { + return $this->isIntersectionType; + } + /** * @return Project */ @@ -202,6 +214,7 @@ public function toArray() 'tags' => $this->tags, 'modifiers' => $this->modifiers, 'is_by_ref' => $this->byRef, + 'is_intersection_type' => $this->isIntersectionType(), 'exceptions' => $this->exceptions, 'errors' => $this->errors, 'parameters' => array_map( @@ -233,6 +246,10 @@ public static function fromArray(Project $project, array $array) $method->relativeFilePath = $array['relative_file'] ?? '';// New in 5.5.0 $method->fromCache = true; + if (isset($array['is_intersection_type'])) {// New in 5.5.3 + $method->setIntersectionType($array['is_intersection_type']); + } + foreach ($array['parameters'] as $parameter) { $method->addParameter(ParameterReflection::fromArray($project, $parameter)); } diff --git a/src/Reflection/MethodReflection.php b/src/Reflection/MethodReflection.php index a3ff4f49..a36f4f55 100644 --- a/src/Reflection/MethodReflection.php +++ b/src/Reflection/MethodReflection.php @@ -21,7 +21,9 @@ class MethodReflection extends Reflection protected $class; protected $parameters = []; protected $byRef; - protected $exceptions = []; + /** @var bool */ + protected $isIntersectionType = false; + protected $exceptions = []; public function __toString() { @@ -38,6 +40,16 @@ public function isByRef() return $this->byRef; } + public function setIntersectionType(bool $boolean): void + { + $this->isIntersectionType = $boolean; + } + + public function isIntersectionType(): bool + { + return $this->isIntersectionType; + } + /** * {@inheritDoc} */ @@ -135,6 +147,7 @@ public function toArray() 'see' => $this->see, 'modifiers' => $this->modifiers, 'is_by_ref' => $this->byRef, + 'is_intersection_type' => $this->isIntersectionType(), 'exceptions' => $this->exceptions, 'errors' => $this->errors, 'parameters' => array_map( @@ -151,17 +164,19 @@ static function ($parameter) { */ public static function fromArray(Project $project, array $array) { - $method = new self($array['name'], $array['line']); - $method->shortDesc = $array['short_desc']; - $method->longDesc = $array['long_desc']; - $method->hint = $array['hint']; - $method->hintDesc = $array['hint_desc']; - $method->tags = $array['tags']; - $method->modifiers = $array['modifiers']; - $method->byRef = $array['is_by_ref']; - $method->exceptions = $array['exceptions']; - $method->errors = $array['errors']; - $method->see = $array['see'] ?? [];// New in 5.4.0 + $method = new self($array['name'], $array['line']); + $method->shortDesc = $array['short_desc']; + $method->longDesc = $array['long_desc']; + $method->hint = $array['hint']; + $method->hintDesc = $array['hint_desc']; + $method->tags = $array['tags']; + $method->modifiers = $array['modifiers']; + $method->byRef = $array['is_by_ref']; + $method->exceptions = $array['exceptions']; + $method->errors = $array['errors']; + $method->see = $array['see'] ?? [];// New in 5.4.0 + $method->isIntersectionType = $array['is_intersection_type'] ?? false;// New in 5.5.3 + foreach ($array['parameters'] as $parameter) { $method->addParameter(ParameterReflection::fromArray($project, $parameter)); diff --git a/src/Reflection/ParameterReflection.php b/src/Reflection/ParameterReflection.php index e4e3d061..5dd186bf 100644 --- a/src/Reflection/ParameterReflection.php +++ b/src/Reflection/ParameterReflection.php @@ -24,6 +24,8 @@ class ParameterReflection extends Reflection protected $byRef; protected $default; protected $variadic; + /** @var bool */ + protected $isIntersectionType = false; public function __toString() { @@ -38,6 +40,16 @@ public function getClass() return $this->method->getClass(); } + public function setIntersectionType(bool $boolean): void + { + $this->isIntersectionType = $boolean; + } + + public function isIntersectionType(): bool + { + return $this->isIntersectionType; + } + public function setByRef($boolean) { $this->byRef = $boolean; @@ -134,6 +146,7 @@ public function toArray() 'variadic' => $this->variadic, 'is_by_ref' => $this->byRef, 'is_read_only' => $this->isReadOnly(), + 'is_intersection_type' => $this->isIntersectionType(), ]; } @@ -142,16 +155,17 @@ public function toArray() */ public static function fromArray(Project $project, array $array) { - $parameter = new self($array['name'], $array['line']); - $parameter->shortDesc = $array['short_desc']; - $parameter->longDesc = $array['long_desc']; - $parameter->hint = $array['hint']; - $parameter->tags = $array['tags']; - $parameter->modifiers = $array['modifiers']; - $parameter->default = $array['default']; - $parameter->variadic = $array['variadic']; - $parameter->byRef = $array['is_by_ref']; - $parameter->isReadOnly = $array['is_read_only'] ?? false;// New in 5.4.0 + $parameter = new self($array['name'], $array['line']); + $parameter->shortDesc = $array['short_desc']; + $parameter->longDesc = $array['long_desc']; + $parameter->hint = $array['hint']; + $parameter->tags = $array['tags']; + $parameter->modifiers = $array['modifiers']; + $parameter->default = $array['default']; + $parameter->variadic = $array['variadic']; + $parameter->byRef = $array['is_by_ref']; + $parameter->isReadOnly = $array['is_read_only'] ?? false;// New in 5.4.0 + $parameter->isIntersectionType = $array['is_intersection_type'] ?? false;// New in 5.5.3 return $parameter; } diff --git a/src/Reflection/PropertyReflection.php b/src/Reflection/PropertyReflection.php index 0d0fedd4..5ec1f4a1 100644 --- a/src/Reflection/PropertyReflection.php +++ b/src/Reflection/PropertyReflection.php @@ -21,6 +21,8 @@ class PropertyReflection extends Reflection protected $default; /** @var bool */ protected $isWriteOnly = false; + /** @var bool */ + protected $isIntersectionType = false; public function __toString() { @@ -70,6 +72,16 @@ public function setClass(ClassReflection $class): void $this->class = $class; } + public function setIntersectionType(bool $boolean): void + { + $this->isIntersectionType = $boolean; + } + + public function isIntersectionType(): bool + { + return $this->isIntersectionType; + } + /** * @return array */ @@ -88,6 +100,7 @@ public function toArray() 'errors' => $this->errors, 'is_read_only' => $this->isReadOnly(), 'is_write_only' => $this->isWriteOnly(), + 'is_intersection_type' => $this->isIntersectionType(), ]; } @@ -114,6 +127,10 @@ public static function fromArray(Project $project, array $array) $property->setWriteOnly($array['is_write_only']); } + if (isset($array['is_intersection_type'])) {// New in 5.5.3 + $property->setIntersectionType($array['is_intersection_type']); + } + return $property; } diff --git a/src/Resources/themes/default/class.twig b/src/Resources/themes/default/class.twig index 12fa97cf..c83ce251 100644 --- a/src/Resources/themes/default/class.twig +++ b/src/Resources/themes/default/class.twig @@ -108,7 +108,7 @@ {% if method.static %}static{% endif %} {% if method.protected %}protected{% endif %} {% if method.private %}private{% endif %} - {{ hint_link(method.hint) }} + {{ hint_link(method.hint, method.isIntersectionType()) }} {{ method.name|raw }}{{ block('method_parameters_signature') }} {%- endblock %} @@ -122,7 +122,7 @@ {% for parameter in method.parameters %} - + @@ -133,7 +133,7 @@ {% block return %}
{% if parameter.hint %}{{ hint_link(parameter.hint) }}{% endif %}{% if parameter.hint %}{{ hint_link(parameter.hint, parameter.isIntersectionType()) }}{% endif %} {%- if parameter.variadic %}...{% endif %}${{ parameter.name|raw }} {{ parameter.shortdesc|desc(class)|md_to_html }}
- +
{{ hint_link(method.hint) }}{{ hint_link(method.hint, method.isIntersectionType()) }} {{ method.hintDesc|desc(class)|md_to_html }}
@@ -217,7 +217,7 @@ {% if property.isStatic() %}static{% endif %} {% if property.isProtected() %}protected{% endif %} {% if property.isPrivate() %}private{% endif %} - {{ hint_link(property.hint) }} + {{ hint_link(property.hint, property.isIntersectionType()) }} {% if property.isInternal() %}{% trans 'internal' %}{% endif %} {% if property.isDeprecated() %}{% trans 'deprecated' %}{% endif %} {% if property.isReadOnly() %}{% trans 'read-only' %}{% endif %} @@ -245,7 +245,7 @@ {% for method in methods %}
- {% if method.static %}static {% endif %}{{ hint_link(method.hint) }} + {% if method.static %}static {% endif %}{{ hint_link(method.hint, method.isIntersectionType()) }}
{{ method.name|raw }}{{ block('method_parameters_signature') }} diff --git a/src/Resources/themes/default/macros.twig b/src/Resources/themes/default/macros.twig index 374840ca..fb899e15 100644 --- a/src/Resources/themes/default/macros.twig +++ b/src/Resources/themes/default/macros.twig @@ -31,7 +31,7 @@ {# #} {%- endmacro %} -{% macro hint_link(hints) -%} +{% macro hint_link(hints, isIntersectionType = false) -%} {%- from _self import class_link %} {%- if hints %} @@ -42,7 +42,7 @@ {{- abbr_class(hint.name) }} {%- endif %} {%- if hint.array %}[]{% endif %} - {%- if not loop.last %}|{% endif %} + {%- if not loop.last %}{%- if isIntersectionType %}&{% else %}|{% endif %}{% endif %} {%- endfor %} {%- endif %} {%- endmacro %} @@ -71,7 +71,7 @@ {%- from "macros.twig" import hint_link -%} ( {%- for parameter in method.parameters %} - {%- if parameter.hashint %}{{ hint_link(parameter.hint) }} {% endif -%} + {%- if parameter.hashint %}{{ hint_link(parameter.hint, parameter.isIntersectionType()) }} {% endif -%} {%- if parameter.variadic %}...{% endif %}${{ parameter.name|raw }} {%- if parameter.default is not null %} = {{ parameter.default }}{% endif %} {%- if not loop.last %}, {% endif %} @@ -83,7 +83,7 @@ {%- from "macros.twig" import hint_link -%} ( {%- for parameter in method.parameters %} - {%- if parameter.hashint %}{{ hint_link(parameter.hint) }} {% endif -%} + {%- if parameter.hashint %}{{ hint_link(parameter.hint, parameter.isIntersectionType()) }} {% endif -%} {%- if parameter.variadic %}...{% endif %}${{ parameter.name|raw }} {%- if parameter.default is not null %} = {{ parameter.default }}{% endif %} {%- if not loop.last %}, {% endif %} diff --git a/tests/phar/data/src/Intersection.php b/tests/phar/data/src/Intersection.php deleted file mode 100644 index d74c4131..00000000 --- a/tests/phar/data/src/Intersection.php +++ /dev/null @@ -1,11 +0,0 @@ -