refactor: extract reflection type resolving to class

This commit is contained in:
Romain Canon 2021-12-02 20:52:00 +01:00
parent 680941687b
commit 99b4f4f7aa
12 changed files with 215 additions and 385 deletions

View File

@ -1,23 +0,0 @@
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Definition\Exception;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use LogicException;
use ReflectionMethod;
final class MethodReturnTypesDoNotMatch extends LogicException
{
public function __construct(ReflectionMethod $parameter, Type $typeFromDocBlock, Type $typeFromReflection)
{
$signature = Reflection::signature($parameter);
parent::__construct(
"Return types for method `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).",
1634048916
);
}
}

View File

@ -1,23 +0,0 @@
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Definition\Exception;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use LogicException;
use ReflectionParameter;
final class ParameterTypesDoNotMatch extends LogicException
{
public function __construct(ReflectionParameter $parameter, Type $typeFromDocBlock, Type $typeFromReflection)
{
$signature = Reflection::signature($parameter);
parent::__construct(
"Types for parameter `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).",
1617220595
);
}
}

View File

@ -1,23 +0,0 @@
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Definition\Exception;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use LogicException;
use ReflectionProperty;
final class PropertyTypesDoNotMatch extends LogicException
{
public function __construct(ReflectionProperty $property, Type $typeFromDocBlock, Type $typeFromReflection)
{
$signature = Reflection::signature($property);
parent::__construct(
"Types for property `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).",
1617218939
);
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Definition\Exception;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use LogicException;
use ReflectionFunctionAbstract;
use ReflectionParameter;
use ReflectionProperty;
use Reflector;
final class TypesDoNotMatch extends LogicException
{
/**
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
*/
public function __construct(Reflector $reflection, Type $typeFromDocBlock, Type $typeFromReflection)
{
$signature = Reflection::signature($reflection);
if ($reflection instanceof ReflectionProperty) {
$message = "Types for property `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).";
} elseif ($reflection instanceof ReflectionParameter) {
$message = "Types for parameter `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).";
} else {
$message = "Return types for method `$signature` do not match: `$typeFromDocBlock` (docblock) does not accept `$typeFromReflection` (native).";
}
parent::__construct($message, 1638471381);
}
}

View File

@ -10,6 +10,10 @@ use CuyZ\Valinor\Definition\Methods;
use CuyZ\Valinor\Definition\Properties;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassAliasSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\HandleClassGenericSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionMethod;
@ -21,6 +25,8 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
{
private AttributesRepository $attributesFactory;
private TypeParserFactory $typeParserFactory;
private ReflectionPropertyDefinitionBuilder $propertyBuilder;
private ReflectionMethodDefinitionBuilder $methodBuilder;
@ -28,22 +34,24 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesRepository)
{
$this->attributesFactory = $attributesRepository;
$this->typeParserFactory = $typeParserFactory;
$this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($typeParserFactory, $attributesRepository);
$this->methodBuilder = new ReflectionMethodDefinitionBuilder($typeParserFactory, $attributesRepository);
$this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($attributesRepository);
$this->methodBuilder = new ReflectionMethodDefinitionBuilder($attributesRepository);
}
public function for(ClassSignature $signature): ClassDefinition
{
$reflection = Reflection::class($signature->className());
$typeResolver = $this->typeResolver($signature);
$properties = array_map(
fn (ReflectionProperty $property) => $this->propertyBuilder->for($signature, $property),
fn (ReflectionProperty $property) => $this->propertyBuilder->for($property, $typeResolver),
$reflection->getProperties()
);
$methods = array_map(
fn (ReflectionMethod $method) => $this->methodBuilder->for($signature, $method),
fn (ReflectionMethod $method) => $this->methodBuilder->for($method, $typeResolver),
$reflection->getMethods()
);
@ -55,4 +63,20 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
new Methods(...$methods),
);
}
private function typeResolver(ClassSignature $signature): ReflectionTypeResolver
{
$nativeParser = $this->typeParserFactory->get(
new ClassContextSpecification($signature->className())
);
$advancedParser = $this->typeParserFactory->get(
new ClassContextSpecification($signature->className()),
new ClassAliasSpecification($signature->className()),
new HandleClassGenericSpecification(),
new TypeAliasAssignerSpecification($signature->generics()),
);
return new ReflectionTypeResolver($nativeParser, $advancedParser);
}
}

View File

@ -4,48 +4,32 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Definition\Repository\Reflection;
use CuyZ\Valinor\Definition\ClassSignature;
use CuyZ\Valinor\Definition\Exception\MethodReturnTypesDoNotMatch;
use CuyZ\Valinor\Definition\MethodDefinition;
use CuyZ\Valinor\Definition\Parameters;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassAliasSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\HandleClassGenericSpecification;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\MixedType;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionMethod;
use ReflectionParameter;
use function array_map;
use function trim;
final class ReflectionMethodDefinitionBuilder
{
private TypeParserFactory $typeParserFactory;
private ReflectionParameterDefinitionBuilder $parameterBuilder;
public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesRepository)
public function __construct(AttributesRepository $attributesRepository)
{
$this->typeParserFactory = $typeParserFactory;
$this->parameterBuilder = new ReflectionParameterDefinitionBuilder($typeParserFactory, $attributesRepository);
$this->parameterBuilder = new ReflectionParameterDefinitionBuilder($attributesRepository);
}
public function for(ClassSignature $signature, ReflectionMethod $reflection): MethodDefinition
public function for(ReflectionMethod $reflection, ReflectionTypeResolver $typeResolver): MethodDefinition
{
$parameters = array_map(
fn (ReflectionParameter $parameter) => $this->parameterBuilder->for($signature, $parameter),
fn (ReflectionParameter $parameter) => $this->parameterBuilder->for($parameter, $typeResolver),
$reflection->getParameters()
);
$returnType = $this->resolveType($signature, $reflection);
$returnType = $typeResolver->resolveType($reflection);
return new MethodDefinition(
$reflection->name,
@ -56,80 +40,4 @@ final class ReflectionMethodDefinitionBuilder
$returnType
);
}
private function resolveType(ClassSignature $signature, ReflectionMethod $reflection): Type
{
$nativeType = $this->nativeType($reflection);
$typeFromDocBlock = $this->typeFromDocBlock($signature, $reflection);
if (! $nativeType && ! $typeFromDocBlock) {
return MixedType::get();
}
if (! $nativeType) {
/** @var Type $typeFromDocBlock */
return $typeFromDocBlock;
}
if (! $typeFromDocBlock) {
return $nativeType;
}
if (! $typeFromDocBlock instanceof UnresolvableType
&& ! $nativeType instanceof UnresolvableType
&& ! $typeFromDocBlock->matches($nativeType)
) {
throw new MethodReturnTypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
}
return $typeFromDocBlock;
}
private function typeFromDocBlock(ClassSignature $signature, ReflectionMethod $reflection): ?Type
{
$type = Reflection::docBlockReturnType($reflection);
if ($type === null) {
return null;
}
$parser = $this->typeParserFactory->get(
new ClassContextSpecification($signature->className()),
new ClassAliasSpecification($signature->className()),
new HandleClassGenericSpecification(),
new TypeAliasAssignerSpecification($signature->generics()),
);
return $this->parseType($type, $reflection, $parser);
}
private function nativeType(ReflectionMethod $reflection): ?Type
{
$reflectionType = $reflection->getReturnType();
if (! $reflectionType) {
return null;
}
$type = Reflection::flattenType($reflectionType);
$parser = $this->typeParserFactory->get(
new ClassContextSpecification($reflection->getDeclaringClass()->name)
);
return $this->parseType($type, $reflection, $parser);
}
private function parseType(string $raw, ReflectionMethod $reflection, TypeParser $parser): Type
{
try {
return $parser->parse($raw);
} catch (InvalidType $exception) {
$raw = trim($raw);
$signature = Reflection::signature($reflection);
return new UnresolvableType(
"The type `$raw` for return type of method `$signature` could not be resolved: {$exception->getMessage()}"
);
}
}
}

View File

@ -4,43 +4,27 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Definition\Repository\Reflection;
use CuyZ\Valinor\Definition\ClassSignature;
use CuyZ\Valinor\Definition\Exception\InvalidParameterDefaultValue;
use CuyZ\Valinor\Definition\Exception\ParameterTypesDoNotMatch;
use CuyZ\Valinor\Definition\ParameterDefinition;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassAliasSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\HandleClassGenericSpecification;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\MixedType;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionParameter;
use function trim;
final class ReflectionParameterDefinitionBuilder
{
private TypeParserFactory $typeParserFactory;
private AttributesRepository $attributesFactory;
public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesRepository)
public function __construct(AttributesRepository $attributesRepository)
{
$this->typeParserFactory = $typeParserFactory;
$this->attributesFactory = $attributesRepository;
}
public function for(ClassSignature $classSignature, ReflectionParameter $reflection): ParameterDefinition
public function for(ReflectionParameter $reflection, ReflectionTypeResolver $typeResolver): ParameterDefinition
{
$name = $reflection->name;
$signature = Reflection::signature($reflection);
$type = $this->resolveType($classSignature, $reflection);
$type = $typeResolver->resolveType($reflection);
$isOptional = $reflection->isOptional();
$defaultValue = $reflection->isDefaultValueAvailable() ? $reflection->getDefaultValue() : null;
$attributes = $this->attributesFactory->for($reflection);
@ -54,84 +38,4 @@ final class ReflectionParameterDefinitionBuilder
return new ParameterDefinition($name, $signature, $type, $isOptional, $defaultValue, $attributes);
}
private function resolveType(ClassSignature $signature, ReflectionParameter $reflection): Type
{
$nativeType = $this->nativeType($reflection);
$typeFromDocBlock = $this->typeFromDocBlock($signature, $reflection);
if (! $nativeType && ! $typeFromDocBlock) {
return MixedType::get();
}
if (! $nativeType) {
/** @var Type $typeFromDocBlock */
return $typeFromDocBlock;
}
if (! $typeFromDocBlock) {
return $nativeType;
}
if (! $typeFromDocBlock instanceof UnresolvableType
&& ! $nativeType instanceof UnresolvableType
&& ! $typeFromDocBlock->matches($nativeType)
) {
throw new ParameterTypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
}
return $typeFromDocBlock;
}
private function typeFromDocBlock(ClassSignature $signature, ReflectionParameter $reflection): ?Type
{
$type = Reflection::docBlockType($reflection);
if ($type === null) {
return null;
}
$parser = $this->typeParserFactory->get(
new ClassContextSpecification($signature->className()),
new ClassAliasSpecification($signature->className()),
new HandleClassGenericSpecification(),
new TypeAliasAssignerSpecification($signature->generics()),
);
return $this->parseType($type, $reflection, $parser);
}
private function nativeType(ReflectionParameter $reflection): ?Type
{
$reflectionType = $reflection->getType();
if (! $reflectionType) {
return null;
}
$class = $reflection->getDeclaringClass();
assert($class !== null);
$type = Reflection::flattenType($reflectionType);
$parser = $this->typeParserFactory->get(
new ClassContextSpecification($class->name)
);
return $this->parseType($type, $reflection, $parser);
}
private function parseType(string $raw, ReflectionParameter $reflection, TypeParser $parser): Type
{
try {
return $parser->parse($raw);
} catch (InvalidType $exception) {
$raw = trim($raw);
$signature = Reflection::signature($reflection);
return new UnresolvableType(
"The type `$raw` for parameter `$signature` could not be resolved: {$exception->getMessage()}"
);
}
}
}

View File

@ -4,43 +4,27 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Definition\Repository\Reflection;
use CuyZ\Valinor\Definition\ClassSignature;
use CuyZ\Valinor\Definition\Exception\InvalidPropertyDefaultValue;
use CuyZ\Valinor\Definition\Exception\PropertyTypesDoNotMatch;
use CuyZ\Valinor\Definition\PropertyDefinition;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassAliasSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\ClassContextSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\HandleClassGenericSpecification;
use CuyZ\Valinor\Type\Parser\Factory\Specifications\TypeAliasAssignerSpecification;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\MixedType;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionProperty;
use function trim;
final class ReflectionPropertyDefinitionBuilder
{
private TypeParserFactory $typeParserFactory;
private AttributesRepository $attributesRepository;
public function __construct(TypeParserFactory $typeParserFactory, AttributesRepository $attributesRepository)
public function __construct(AttributesRepository $attributesRepository)
{
$this->typeParserFactory = $typeParserFactory;
$this->attributesRepository = $attributesRepository;
}
public function for(ClassSignature $classSignature, ReflectionProperty $reflection): PropertyDefinition
public function for(ReflectionProperty $reflection, ReflectionTypeResolver $typeResolver): PropertyDefinition
{
$name = $reflection->name;
$signature = Reflection::signature($reflection);
$type = $this->resolveType($classSignature, $reflection);
$type = $typeResolver->resolveType($reflection);
$hasDefaultValue = $this->hasDefaultValue($reflection);
$defaultValue = $this->defaultValue($reflection);
$isPublic = $reflection->isPublic();
@ -64,82 +48,6 @@ final class ReflectionPropertyDefinitionBuilder
);
}
private function resolveType(ClassSignature $signature, ReflectionProperty $reflection): Type
{
$nativeType = $this->nativeType($reflection);
$typeFromDocBlock = $this->typeFromDocBlock($signature, $reflection);
if (! $nativeType && ! $typeFromDocBlock) {
return MixedType::get();
}
if (! $nativeType) {
/** @var Type $typeFromDocBlock */
return $typeFromDocBlock;
}
if (! $typeFromDocBlock) {
return $nativeType;
}
if (! $typeFromDocBlock instanceof UnresolvableType
&& ! $nativeType instanceof UnresolvableType
&& ! $typeFromDocBlock->matches($nativeType)
) {
throw new PropertyTypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
}
return $typeFromDocBlock;
}
private function typeFromDocBlock(ClassSignature $signature, ReflectionProperty $reflection): ?Type
{
$type = Reflection::docBlockType($reflection);
if ($type === null) {
return null;
}
$parser = $this->typeParserFactory->get(
new ClassContextSpecification($signature->className()),
new ClassAliasSpecification($signature->className()),
new HandleClassGenericSpecification(),
new TypeAliasAssignerSpecification($signature->generics()),
);
return $this->parseType($type, $reflection, $parser);
}
private function nativeType(ReflectionProperty $reflection): ?Type
{
$reflectionType = $reflection->getType();
if (! $reflectionType) {
return null;
}
$type = Reflection::flattenType($reflectionType);
$parser = $this->typeParserFactory->get(
new ClassContextSpecification($reflection->getDeclaringClass()->name)
);
return $this->parseType($type, $reflection, $parser);
}
private function parseType(string $raw, ReflectionProperty $reflection, TypeParser $parser): Type
{
try {
return $parser->parse($raw);
} catch (InvalidType $exception) {
$raw = trim($raw);
$signature = Reflection::signature($reflection);
return new UnresolvableType(
"The type `$raw` for property `$signature` could not be resolved: {$exception->getMessage()}"
);
}
}
private function hasDefaultValue(ReflectionProperty $reflection): bool
{
// @PHP8.0 `$reflection->hasDefaultValue()`

View File

@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Definition\Repository\Reflection;
use CuyZ\Valinor\Definition\Exception\TypesDoNotMatch;
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\MixedType;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionFunctionAbstract;
use ReflectionParameter;
use ReflectionProperty;
use Reflector;
final class ReflectionTypeResolver
{
private TypeParser $nativeParser;
private TypeParser $advancedParser;
public function __construct(TypeParser $nativeParser, TypeParser $advancedParser)
{
$this->nativeParser = $nativeParser;
$this->advancedParser = $advancedParser;
}
/**
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
*/
public function resolveType(Reflector $reflection): Type
{
$nativeType = $this->nativeType($reflection);
$typeFromDocBlock = $this->typeFromDocBlock($reflection);
if (! $nativeType && ! $typeFromDocBlock) {
return MixedType::get();
}
if (! $nativeType) {
/** @var Type $typeFromDocBlock */
return $typeFromDocBlock;
}
if (! $typeFromDocBlock) {
return $nativeType;
}
if (! $typeFromDocBlock instanceof UnresolvableType
&& ! $nativeType instanceof UnresolvableType
&& ! $typeFromDocBlock->matches($nativeType)
) {
throw new TypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
}
return $typeFromDocBlock;
}
/**
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
*/
private function typeFromDocBlock(Reflector $reflection): ?Type
{
$type = $reflection instanceof ReflectionFunctionAbstract
? Reflection::docBlockReturnType($reflection)
: Reflection::docBlockType($reflection);
if ($type === null) {
return null;
}
return $this->parseType($type, $reflection, $this->advancedParser);
}
/**
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
*/
private function nativeType(Reflector $reflection): ?Type
{
$reflectionType = $reflection instanceof ReflectionFunctionAbstract
? $reflection->getReturnType()
: $reflection->getType();
if (! $reflectionType) {
return null;
}
$type = Reflection::flattenType($reflectionType);
return $this->parseType($type, $reflection, $this->nativeParser);
}
/**
* @param ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection
*/
private function parseType(string $raw, Reflector $reflection, TypeParser $parser): Type
{
try {
return $parser->parse($raw);
} catch (InvalidType $exception) {
$raw = trim($raw);
$signature = Reflection::signature($reflection);
if ($reflection instanceof ReflectionProperty) {
$message = "The type `$raw` for property `$signature` could not be resolved: {$exception->getMessage()}";
} elseif ($reflection instanceof ReflectionParameter) {
$message = "The type `$raw` for parameter `$signature` could not be resolved: {$exception->getMessage()}";
} else {
$message = "The type `$raw` for return type of method `$signature` could not be resolved: {$exception->getMessage()}";
}
return new UnresolvableType($message);
}
}
}

View File

@ -19,7 +19,7 @@ final class BasicTemplateParser implements TemplateParser
{
$templates = [];
preg_match_all('/@(phpstan-|psalm-)?template\s+(\w+)(\s+of\s+([\w\s|&<>\'",-\[\]\\\\]+))?/', $source, $raw);
preg_match_all("/@(phpstan-|psalm-)?template\s+(\w+)(\s+of\s+([\w\s?|&<>'\",-:\\\\\[\]{}]+))?/", $source, $raw);
/** @var string[] $list */
$list = $raw[2];

View File

@ -92,18 +92,13 @@ final class Reflection
public static function docBlockType(Reflector $reflection): ?string
{
if ($reflection instanceof ReflectionProperty) {
$docComment = $reflection->getDocComment() ?: '';
$regex = '@var\s+([\w\s?|&<>\'",-\[\]{}:\\\\]+)';
$docComment = self::sanitizeDocComment($reflection);
$regex = "@var\s+([\w\s?|&<>'\",-:\\\\\[\]{}]+)";
} else {
$docComment = $reflection->getDeclaringFunction()->getDocComment() ?: '';
$regex = "@param\s+([\w\s?|&<>'\",-\[\]{}:\\\\]+)\s+\\$$reflection->name(\s+|$)";
$docComment = self::sanitizeDocComment($reflection->getDeclaringFunction());
$regex = "@param\s+([\w\s?|&<>'\",-:\\\\\[\]{}]+)\s+\\$$reflection->name(\W+|$)";
}
/** @var string $docComment */
$docComment = preg_replace('#^\s*/\*\*([^/]+)/\s*$#', '$1', $docComment);
$docComment = preg_replace('/\s*\*([^\s]*)/', '$1', $docComment);
/** @var string $docComment */
if (! preg_match("/$regex/", $docComment, $matches)) {
return null;
}
@ -113,9 +108,9 @@ final class Reflection
public static function docBlockReturnType(ReflectionFunctionAbstract $reflection): ?string
{
$docComment = $reflection->getDocComment() ?: '';
$docComment = self::sanitizeDocComment($reflection);
if (! preg_match('/@return\s+([\w\s?|&<>\'",-\[\]{}:\\\\]+)/', $docComment, $matches)) {
if (! preg_match("/@return\s+([\w\s?|&<>'\",-:\\\\\[\]{}]+)(\W*|$)/", $docComment, $matches)) {
return null;
}
@ -142,4 +137,14 @@ final class Reflection
return new ReflectionMethod($callable[0], $callable[1]);
}
/**
* @param ReflectionClass<object>|ReflectionProperty|ReflectionFunctionAbstract $reflection
*/
private static function sanitizeDocComment(Reflector $reflection): string
{
$docComment = preg_replace('#^\s*/\*\*([^/]+)/\s*$#', '$1', $reflection->getDocComment() ?: '');
return preg_replace('/\s*\*\s*(\S*)/', '$1', $docComment); // @phpstan-ignore-line
}
}

View File

@ -7,9 +7,7 @@ namespace CuyZ\Valinor\Tests\Unit\Definition\Repository\Reflection;
use CuyZ\Valinor\Definition\ClassSignature;
use CuyZ\Valinor\Definition\Exception\InvalidParameterDefaultValue;
use CuyZ\Valinor\Definition\Exception\InvalidPropertyDefaultValue;
use CuyZ\Valinor\Definition\Exception\MethodReturnTypesDoNotMatch;
use CuyZ\Valinor\Definition\Exception\ParameterTypesDoNotMatch;
use CuyZ\Valinor\Definition\Exception\PropertyTypesDoNotMatch;
use CuyZ\Valinor\Definition\Exception\TypesDoNotMatch;
use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository;
use CuyZ\Valinor\Tests\Fake\Definition\Repository\FakeAttributesRepository;
use CuyZ\Valinor\Tests\Fake\Type\Parser\Factory\FakeTypeParserFactory;
@ -187,8 +185,8 @@ final class ReflectionClassDefinitionRepositoryTest extends TestCase
public bool $propertyWithNotMatchingTypes;
});
$this->expectException(PropertyTypesDoNotMatch::class);
$this->expectExceptionCode(1617218939);
$this->expectException(TypesDoNotMatch::class);
$this->expectExceptionCode(1638471381);
$this->expectExceptionMessage("Types for property `$class::\$propertyWithNotMatchingTypes` do not match: `string` (docblock) does not accept `bool` (native).");
$this->repository->for(new ClassSignature($class));
@ -247,8 +245,8 @@ final class ReflectionClassDefinitionRepositoryTest extends TestCase
}
});
$this->expectException(ParameterTypesDoNotMatch::class);
$this->expectExceptionCode(1617220595);
$this->expectException(TypesDoNotMatch::class);
$this->expectExceptionCode(1638471381);
$this->expectExceptionMessage("Types for parameter `$class::publicMethod(\$parameterWithNotMatchingTypes)` do not match: `string` (docblock) does not accept `bool` (native).");
$this->repository->for(new ClassSignature($class));
@ -267,8 +265,8 @@ final class ReflectionClassDefinitionRepositoryTest extends TestCase
}
});
$this->expectException(MethodReturnTypesDoNotMatch::class);
$this->expectExceptionCode(1634048916);
$this->expectException(TypesDoNotMatch::class);
$this->expectExceptionCode(1638471381);
$this->expectExceptionMessage("Return types for method `$class::publicMethod()` do not match: `bool` (docblock) does not accept `string` (native).");
$this->repository->for(new ClassSignature($class));