From 452a8036e57ef1b156d4c52a8f6eada2f9677f90 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 17 Apr 2020 16:30:02 +0200 Subject: [PATCH 1/2] refactoring & fix getValues return type --- composer.json | 2 +- extension.neon | 2 +- src/EnumDynamicReturnTypeExtension.php | 84 +++++++++ ...EnumGetValueDynamicReturnTypeExtension.php | 70 ------- tests/Assets/AllTypeEnum.php | 17 ++ tests/Assets/ArrayTypeEnum.php | 12 ++ tests/Assets/BoolTypeEnum.php | 13 ++ tests/Assets/DocCommentEnum.php | 19 ++ tests/Assets/FloatTypeEnum.php | 13 ++ tests/Assets/IntTypeEnum.php | 13 ++ tests/Assets/NullTypeEnum.php | 12 ++ tests/Assets/StrTypeEnum.php | 13 ++ .../{StrEnum.php => VisibilityEnum.php} | 4 +- tests/EnumDynamicReturnTypeExtensionTest.php | 171 ++++++++++++++++++ ...GetValueDynamicReturnTypeExtensionTest.php | 35 ---- tests/EnumMethodReflectionTest.php | 23 +-- ...numMethodsClassReflectionExtensionTest.php | 12 +- tests/ExtensionTestCase.php | 23 ++- tests/data/get_value.php | 8 - 19 files changed, 406 insertions(+), 140 deletions(-) create mode 100644 src/EnumDynamicReturnTypeExtension.php delete mode 100644 src/EnumGetValueDynamicReturnTypeExtension.php create mode 100644 tests/Assets/AllTypeEnum.php create mode 100644 tests/Assets/ArrayTypeEnum.php create mode 100644 tests/Assets/BoolTypeEnum.php create mode 100644 tests/Assets/DocCommentEnum.php create mode 100644 tests/Assets/FloatTypeEnum.php create mode 100644 tests/Assets/IntTypeEnum.php create mode 100644 tests/Assets/NullTypeEnum.php create mode 100644 tests/Assets/StrTypeEnum.php rename tests/Assets/{StrEnum.php => VisibilityEnum.php} (86%) create mode 100644 tests/EnumDynamicReturnTypeExtensionTest.php delete mode 100644 tests/EnumGetValueDynamicReturnTypeExtensionTest.php delete mode 100644 tests/data/get_value.php diff --git a/composer.json b/composer.json index 8fb0d33..6663115 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "phpstan/phpstan": "^0.12" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^7.5.20" }, "autoload": { "psr-4": { diff --git a/extension.neon b/extension.neon index 864d462..f6c4b98 100644 --- a/extension.neon +++ b/extension.neon @@ -3,6 +3,6 @@ services: tags: - phpstan.broker.methodsClassReflectionExtension - - class: MabeEnumPHPStan\EnumGetValueDynamicReturnTypeExtension + - class: MabeEnumPHPStan\EnumDynamicReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension diff --git a/src/EnumDynamicReturnTypeExtension.php b/src/EnumDynamicReturnTypeExtension.php new file mode 100644 index 0000000..8e56f52 --- /dev/null +++ b/src/EnumDynamicReturnTypeExtension.php @@ -0,0 +1,84 @@ +, Type> + */ + private $enumValuesTypeBuffer = []; + + public function getClass(): string + { + return Enum::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + $supportedMethods = ['getvalue']; + if (method_exists(Enum::class, 'getValues')) { + array_push($supportedMethods, 'getvalues'); + } + + return in_array(strtolower($methodReflection->getName()), $supportedMethods, true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $enumType = $scope->getType($methodCall->var); + $methodName = $methodReflection->getName(); + $methodClasses = $enumType->getReferencedClasses(); + if (count($methodClasses) !== 1) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $enumeration = $methodClasses[0]; + + switch (strtolower($methodName)) { + case 'getvalue': + return $this->getEnumValuesType($enumeration, $scope); + case 'getvalues': + return new ArrayType( + new IntegerType(), + $this->getEnumValuesType($enumeration, $scope) + ); + default: + throw new ShouldNotHappenException("Method {$methodName} is not supported"); + } + } + + /** + * Returns union type of all values of an enumeration + * @phpstan-param class-string $enumClass + */ + private function getEnumValuesType(string $enumeration, Scope $scope): Type + { + if (isset($this->enumValuesTypeBuffer[$enumeration])) { + return $this->enumValuesTypeBuffer[$enumeration]; + } + + $values = array_values($enumeration::getConstants()); + $types = array_map(function ($value) use ($scope): Type { + return $scope->getTypeFromValue($value); + }, $values); + + return $this->enumValuesTypeBuffer[$enumeration] = TypeCombinator::union(...$types); + } +} diff --git a/src/EnumGetValueDynamicReturnTypeExtension.php b/src/EnumGetValueDynamicReturnTypeExtension.php deleted file mode 100644 index 30955b0..0000000 --- a/src/EnumGetValueDynamicReturnTypeExtension.php +++ /dev/null @@ -1,70 +0,0 @@ -getName() === 'getValue'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - $enumType = $scope->getType($methodCall->var); - if (count($enumType->getReferencedClasses()) !== 1) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - /** @var string $enumClass */ - $enumClass = $enumType->getReferencedClasses()[0]; - if (array_key_exists($enumClass, $this->enumTypes)) { - return $this->enumTypes[$enumClass]; - } - $types = array_map(function ($value) use ($scope): Type { - return $scope->getTypeFromValue($value); - }, self::getEnumValues($enumClass)); - - $this->enumTypes[$enumClass] = TypeCombinator::union(...$types); - - return $this->enumTypes[$enumClass]; - } - - /** - * @phpstan-param class-string $enumClass - */ - private static function getEnumValues(string $enumClass): array - { - if (method_exists($enumClass, 'getValues')) { - return $enumClass::getValues(); - } - - throw new \PHPStan\ShouldNotHappenException(); - } -} diff --git a/tests/Assets/AllTypeEnum.php b/tests/Assets/AllTypeEnum.php new file mode 100644 index 0000000..90d86ac --- /dev/null +++ b/tests/Assets/AllTypeEnum.php @@ -0,0 +1,17 @@ +extension = new EnumDynamicReturnTypeExtension(); + } + + public function testNullType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', 'null', $this->extension); + + if (method_exists(Enum::class, 'getValues')) { + $this->processCode($code, '$enum->getValues()', 'array', $this->extension); + } + } + + public function testBoolType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', 'bool', $this->extension); + + if (method_exists(Enum::class, 'getValues')) { + $this->processCode($code, '$enum->getValues()', 'array', $this->extension); + } + } + + public function testStringType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', "'str1'|'str2'", $this->extension); + + if (method_exists(Enum::class, 'getValues')) { + $this->processCode($code, '$enum->getValues()', "array", $this->extension); + } + } + + public function testIntType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', '0|1', $this->extension); + + if (method_exists(Enum::class, 'getValues')) { + $this->processCode($code, '$enum->getValues()', 'array', $this->extension); + } + } + + public function testFloatType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', '1.1|1.2', $this->extension); + + if (method_exists(Enum::class, 'getValues')) { + $this->processCode($code, '$enum->getValues()', 'array', $this->extension); + } + } + + public function testArrayType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', 'array(array())', $this->extension); + + if (method_exists(Enum::class, 'getValues')) { + $this->processCode($code, '$enum->getValues()', 'array', $this->extension); + } + } + + public function testUnionTypes(): void + { + $code = <<<'CODE' +processCode( + $code, + '$enum->getValue()', + "1|1.1|'str'|array(null, true, 1, 1.1, 'str', array())|true|null", + $this->extension + ); + + if (method_exists(Enum::class, 'getValues')) { + $this->processCode( + $code, + '$enum->getValues()', + "array", + $this->extension + ); + } + } + + public function testGeneralizedTypes(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', 'string', $this->extension); + + if (method_exists(Enum::class, 'getValues')) { + $this->processCode($code, '$enum->getValues()', 'array', $this->extension); + } + } + + public function testUnsupportedMethod(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getName()', 'string', $this->extension); + } +} diff --git a/tests/EnumGetValueDynamicReturnTypeExtensionTest.php b/tests/EnumGetValueDynamicReturnTypeExtensionTest.php deleted file mode 100644 index 5e216dd..0000000 --- a/tests/EnumGetValueDynamicReturnTypeExtensionTest.php +++ /dev/null @@ -1,35 +0,0 @@ -processFile(__DIR__ . '/data/get_value.php', '$strEnum->getValue()', "'no doc block'|'public str'|'str'", new EnumGetValueDynamicReturnTypeExtension()); - } - - public function testGeneralizedTypes(): void - { - $this->processFile(__DIR__ . '/data/get_value.php', '$bigStrEnum->getValue()', "string", new EnumGetValueDynamicReturnTypeExtension()); - } -} diff --git a/tests/EnumMethodReflectionTest.php b/tests/EnumMethodReflectionTest.php index abb1b68..fd27ce2 100644 --- a/tests/EnumMethodReflectionTest.php +++ b/tests/EnumMethodReflectionTest.php @@ -7,8 +7,9 @@ use MabeEnumPHPStan\EnumMethodReflection; use MabeEnumPHPStan\EnumMethodsClassReflectionExtension; use MabeEnumPHPStanTest\Assets\DeprecatedEnum; +use MabeEnumPHPStanTest\Assets\DocCommentEnum; use MabeEnumPHPStanTest\Assets\NotAnEnum; -use MabeEnumPHPStanTest\Assets\StrEnum; +use MabeEnumPHPStanTest\Assets\VisibilityEnum; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Testing\TestCase; use PHPStan\TrinaryLogic; @@ -34,7 +35,7 @@ public function setUp() public function getDeclaringClass() { - $classReflection = $this->broker->getClass(StrEnum::class); + $classReflection = $this->broker->getClass(VisibilityEnum::class); $methodReflection = $this->reflectionExtension->getMethod($classReflection, 'STR'); $this->assertSame($classReflection, $methodReflection->getDeclaringClass()); @@ -42,7 +43,7 @@ public function getDeclaringClass() public function testShouldBeStatic() { - $classReflection = $this->broker->getClass(StrEnum::class); + $classReflection = $this->broker->getClass(VisibilityEnum::class); $methodReflection = $this->reflectionExtension->getMethod($classReflection, 'STR'); $this->assertTrue($methodReflection->isStatic()); @@ -50,7 +51,7 @@ public function testShouldBeStatic() public function testShouldNotBePrivate() { - $classReflection = $this->broker->getClass(StrEnum::class); + $classReflection = $this->broker->getClass(VisibilityEnum::class); $methodReflection = $this->reflectionExtension->getMethod($classReflection, 'STR'); $this->assertFalse($methodReflection->isPrivate()); @@ -58,7 +59,7 @@ public function testShouldNotBePrivate() public function testShouldBePublic() { - $classReflection = $this->broker->getClass(StrEnum::class); + $classReflection = $this->broker->getClass(VisibilityEnum::class); $methodReflection = $this->reflectionExtension->getMethod($classReflection, 'STR'); $this->assertTrue($methodReflection->isPublic()); @@ -66,24 +67,24 @@ public function testShouldBePublic() public function testGetVariants() { - $classReflection = $this->broker->getClass(StrEnum::class); + $classReflection = $this->broker->getClass(VisibilityEnum::class); $methodReflection = $this->reflectionExtension->getMethod($classReflection, 'STR'); $parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); - $this->assertSame(StrEnum::class, $parametersAcceptor->getReturnType()->describe(VerbosityLevel::value())); + $this->assertSame(VisibilityEnum::class, $parametersAcceptor->getReturnType()->describe(VerbosityLevel::value())); } public function testGetDocComment() { - $classReflection = $this->broker->getClass(StrEnum::class); - $docMethodRefl = $this->reflectionExtension->getMethod($classReflection, 'STR'); - $noDocMethodRefl = $this->reflectionExtension->getMethod($classReflection, 'NO_DOC_BLOCK'); + $classReflection = $this->broker->getClass(DocCommentEnum::class); + $docMethodRefl = $this->reflectionExtension->getMethod($classReflection, 'WITH_DOC_BLOCK'); + $noDocMethodRefl = $this->reflectionExtension->getMethod($classReflection, 'WITHOUT_DOC_BLOCK'); // return null on no doc block $this->assertSame(null, $noDocMethodRefl->getDocComment()); // return the correct doc block - $this->assertRegExp('/String const without visibility declaration/', $docMethodRefl->getDocComment()); + $this->assertRegExp('/With doc block/', $docMethodRefl->getDocComment()); // remove @var declaration $this->assertNotRegExp('/@var/', $docMethodRefl->getDocComment()); diff --git a/tests/EnumMethodsClassReflectionExtensionTest.php b/tests/EnumMethodsClassReflectionExtensionTest.php index f390933..fd474a8 100644 --- a/tests/EnumMethodsClassReflectionExtensionTest.php +++ b/tests/EnumMethodsClassReflectionExtensionTest.php @@ -7,7 +7,7 @@ use MabeEnumPHPStan\EnumMethodReflection; use MabeEnumPHPStan\EnumMethodsClassReflectionExtension; use MabeEnumPHPStanTest\Assets\NotAnEnum; -use MabeEnumPHPStanTest\Assets\StrEnum; +use MabeEnumPHPStanTest\Assets\VisibilityEnum; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Testing\TestCase; use PHPStan\Type\VerbosityLevel; @@ -32,16 +32,16 @@ public function setUp() public function testHasMethodSuccess() { - $classReflection = $this->broker->getClass(StrEnum::class); + $classReflection = $this->broker->getClass(VisibilityEnum::class); - foreach (array_keys(StrEnum::getConstants()) as $name) { + foreach (array_keys(VisibilityEnum::getConstants()) as $name) { $this->assertTrue($this->reflectionExtension->hasMethod($classReflection, $name)); } } public function testHasMethodUnknownNotFound() { - $classReflection = $this->broker->getClass(StrEnum::class); + $classReflection = $this->broker->getClass(VisibilityEnum::class); $this->assertFalse($this->reflectionExtension->hasMethod($classReflection, 'UNKNOWN')); } @@ -53,9 +53,9 @@ public function testHasMethodNotSubclassOfEnumNotFound() public function testGetMethodSuccess() { - $classReflection = $this->broker->getClass(StrEnum::class); + $classReflection = $this->broker->getClass(VisibilityEnum::class); - foreach (array_keys(StrEnum::getConstants()) as $name) { + foreach (array_keys(VisibilityEnum::getConstants()) as $name) { $methodReflection = $this->reflectionExtension->getMethod($classReflection, $name); $this->assertInstanceOf(EnumMethodReflection::class, $methodReflection); diff --git a/tests/ExtensionTestCase.php b/tests/ExtensionTestCase.php index e445b79..4971c98 100644 --- a/tests/ExtensionTestCase.php +++ b/tests/ExtensionTestCase.php @@ -25,10 +25,9 @@ abstract class ExtensionTestCase extends TestCase protected function processFile( string $file, string $expression, - string $type, + string $expectedType, DynamicMethodReturnTypeExtension $extension - ): void - { + ): void { $broker = $this->createBroker([$extension]); $parser = $this->getParser(); $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); @@ -60,7 +59,7 @@ protected function processFile( $resolver->processNodes( $parser->parseFile($file), $this->createScopeFactory($broker, $typeSpecifier)->create(ScopeContext::create($file)), - function (Node $node, Scope $scope) use ($expression, $type, &$run): void { + function (Node $node, Scope $scope) use ($expression, $expectedType, &$run): void { if ($node instanceof VirtualNode) { return; } @@ -69,10 +68,24 @@ function (Node $node, Scope $scope) use ($expression, $type, &$run): void { } /** @var \PhpParser\Node\Stmt\Expression $expNode */ $expNode = $this->getParser()->parseString(sprintf('getType($expNode->expr)->describe(VerbosityLevel::precise())); + self::assertSame($expectedType, $scope->getType($expNode->expr)->describe(VerbosityLevel::precise())); $run = true; } ); self::assertTrue($run); } + + protected function processCode( + string $code, + string $expression, + string $expectedType, + DynamicMethodReturnTypeExtension $extension + ): void { + $fd = tmpfile(); + $fdMeta = stream_get_meta_data($fd); + $file = $fdMeta['uri']; + fwrite($fd, $code, strlen($code)); + + $this->processFile($file, $expression, $expectedType, $extension); + } } diff --git a/tests/data/get_value.php b/tests/data/get_value.php deleted file mode 100644 index d1d621b..0000000 --- a/tests/data/get_value.php +++ /dev/null @@ -1,8 +0,0 @@ - Date: Sat, 18 Apr 2020 13:44:53 +0200 Subject: [PATCH 2/2] fixed error on Enum type declaration and MyEnum|NoEnum and also support MyEnum1|MyEnum2 --- src/EnumDynamicReturnTypeExtension.php | 116 ++++++++---- tests/Assets/BigStrEnum.php | 39 +++- tests/Assets/NotAnEnum.php | 3 + ...DynamicReturnTypeExtensionGetValueTest.php | 173 ++++++++++++++++++ ...ynamicReturnTypeExtensionGetValuesTest.php | 158 ++++++++++++++++ tests/EnumDynamicReturnTypeExtensionTest.php | 138 -------------- 6 files changed, 446 insertions(+), 181 deletions(-) create mode 100644 tests/EnumDynamicReturnTypeExtensionGetValueTest.php create mode 100644 tests/EnumDynamicReturnTypeExtensionGetValuesTest.php diff --git a/src/EnumDynamicReturnTypeExtension.php b/src/EnumDynamicReturnTypeExtension.php index 8e56f52..00d5b1a 100644 --- a/src/EnumDynamicReturnTypeExtension.php +++ b/src/EnumDynamicReturnTypeExtension.php @@ -11,19 +11,25 @@ use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ArrayType; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\ConstantTypeHelper; use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\IntegerType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; class EnumDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { /** - * Buffer of known return types of Enum::getValues() - * @var Type[] - * @phpstan-var array, Type> + * Buffer of all types of enumeration values + * @phpstan-var array, Type[]> */ - private $enumValuesTypeBuffer = []; + private $enumValueTypesBuffer = []; + + /** + * Buffer of all types of enumeration ordinals + * @phpstan-var array, Type[]> + */ + private $enumOrdinalTypesBuffer = []; public function getClass(): string { @@ -40,45 +46,87 @@ public function isMethodSupported(MethodReflection $methodReflection): bool return in_array(strtolower($methodReflection->getName()), $supportedMethods, true); } - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - $enumType = $scope->getType($methodCall->var); - $methodName = $methodReflection->getName(); - $methodClasses = $enumType->getReferencedClasses(); - if (count($methodClasses) !== 1) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $callType = $scope->getType($methodCall->var); + $callClasses = $callType->getReferencedClasses(); + $methodName = strtolower($methodReflection->getName()); + $returnTypes = []; + foreach ($callClasses as $callClass) { + if (!is_subclass_of($callClass, Enum::class, true)) { + $returnTypes[] = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()) + ->getReturnType(); + } else { + switch ($methodName) { + case 'getvalue': + $returnTypes[] = $this->enumGetValueReturnType($callClass); + break; + case 'getvalues': + $returnTypes[] = $this->enumGetValuesReturnType($callClass); + break; + default: + throw new ShouldNotHappenException("Method {$methodName} is not supported"); + } + } } - $enumeration = $methodClasses[0]; - - switch (strtolower($methodName)) { - case 'getvalue': - return $this->getEnumValuesType($enumeration, $scope); - case 'getvalues': - return new ArrayType( - new IntegerType(), - $this->getEnumValuesType($enumeration, $scope) - ); - default: - throw new ShouldNotHappenException("Method {$methodName} is not supported"); - } + return TypeCombinator::union(...$returnTypes); } /** - * Returns union type of all values of an enumeration - * @phpstan-param class-string $enumClass + * Returns types of all values of an enumeration + * @phpstan-param class-string $enumeration + * @return Type[] */ - private function getEnumValuesType(string $enumeration, Scope $scope): Type + private function enumValueTypes(string $enumeration): array { - if (isset($this->enumValuesTypeBuffer[$enumeration])) { - return $this->enumValuesTypeBuffer[$enumeration]; + if (isset($this->enumValueTypesBuffer[$enumeration])) { + return $this->enumValueTypesBuffer[$enumeration]; } $values = array_values($enumeration::getConstants()); - $types = array_map(function ($value) use ($scope): Type { - return $scope->getTypeFromValue($value); - }, $values); + $types = array_map([ConstantTypeHelper::class, 'getTypeFromValue'], $values); + + return $this->enumValueTypesBuffer[$enumeration] = $types; + } + + /** + * Returns types of all ordinals of an enumeration + * @phpstan-param class-string $enumeration + * @return Type[] + */ + private function enumOrdinalTypes(string $enumeration): array + { + if (isset($this->enumOrdinalTypesBuffer[$enumeration])) { + return $this->enumOrdinalTypesBuffer[$enumeration]; + } - return $this->enumValuesTypeBuffer[$enumeration] = TypeCombinator::union(...$types); + $ordinals = array_keys($enumeration::getOrdinals()); + $types = array_map([ConstantTypeHelper::class, 'getTypeFromValue'], $ordinals); + + return $this->enumOrdinalTypesBuffer[$enumeration] = $types; + } + + /** + * Returns return type of Enum::getValue() + * @phpstan-param class-string $enumeration + */ + private function enumGetValueReturnType(string $enumeration): Type + { + return TypeCombinator::union(...$this->enumValueTypes($enumeration)); + } + + /** + * Returns return type of Enum::getValues() + * @phpstan-param class-string $enumeration + */ + private function enumGetValuesReturnType(string $enumeration): ArrayType + { + $keyTypes = $this->enumOrdinalTypes($enumeration); + $valueTypes = $this->enumValueTypes($enumeration); + return new ConstantArrayType($keyTypes, $valueTypes, count($keyTypes)); } } diff --git a/tests/Assets/BigStrEnum.php b/tests/Assets/BigStrEnum.php index 5d59315..ce96e98 100644 --- a/tests/Assets/BigStrEnum.php +++ b/tests/Assets/BigStrEnum.php @@ -8,13 +8,34 @@ class BigStrEnum extends Enum { - const C_1 = 'c1'; - const C_2 = 'c2'; - const C_3 = 'c3'; - const C_4 = 'c4'; - const C_5 = 'c5'; - const C_6 = 'c6'; - const C_7 = 'c7'; - const C_8 = 'c8'; - const C_9 = 'c9'; + const C_01 = '01'; + const C_02 = '02'; + const C_03 = '03'; + const C_04 = '04'; + const C_05 = '05'; + const C_06 = '06'; + const C_07 = '07'; + const C_08 = '08'; + const C_09 = '09'; + const C_10 = '10'; + const C_11 = '11'; + const C_12 = '12'; + const C_13 = '13'; + const C_14 = '14'; + const C_15 = '15'; + const C_16 = '16'; + const C_17 = '17'; + const C_18 = '18'; + const C_19 = '19'; + const C_20 = '20'; + const C_21 = '21'; + const C_22 = '22'; + const C_23 = '23'; + const C_24 = '24'; + const C_25 = '25'; + const C_26 = '26'; + const C_27 = '27'; + const C_28 = '28'; + const C_29 = '29'; + const C_30 = '30'; } diff --git a/tests/Assets/NotAnEnum.php b/tests/Assets/NotAnEnum.php index 2201473..f692f2c 100644 --- a/tests/Assets/NotAnEnum.php +++ b/tests/Assets/NotAnEnum.php @@ -10,4 +10,7 @@ class NotAnEnum private const PRIVATE_STR = 'private str'; protected const PROTECTED_STR = 'protected str'; public const PUBLIC_STR = 'public str'; + + public function getValue(): string {return __FUNCTION__; } + public function getValues(): array { return []; } } diff --git a/tests/EnumDynamicReturnTypeExtensionGetValueTest.php b/tests/EnumDynamicReturnTypeExtensionGetValueTest.php new file mode 100644 index 0000000..8c74b29 --- /dev/null +++ b/tests/EnumDynamicReturnTypeExtensionGetValueTest.php @@ -0,0 +1,173 @@ +extension = new EnumDynamicReturnTypeExtension(); + + // Version < 3.x did not support array values + if (method_exists(Enum::class, 'getByName')) { + $this->defaultReturnType = 'bool|float|int|string|null'; + } + } + + public function testNullType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', 'null', $this->extension); + } + + public function testBoolType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', 'bool', $this->extension); + } + + public function testStringType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', "'str1'|'str2'", $this->extension); + } + + public function testIntType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', '0|1', $this->extension); + } + + public function testFloatType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', '1.1|1.2', $this->extension); + } + + public function testArrayType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', 'array(array())', $this->extension); + } + + public function testAllTypes(): void + { + $code = <<<'CODE' +processCode( + $code, + '$enum->getValue()', + "1|1.1|'str'|array(null, true, 1, 1.1, 'str', array())|true|null", + $this->extension + ); + } + + public function testGeneralizedTypes(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', 'string', $this->extension); + } + + public function testBaseEnum(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', $this->defaultReturnType, $this->extension); + } + + public function testUnionEnum(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', "0|1|'str1'|'str2'", $this->extension); + } + + public function testEnumAndNonEnum(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValue()', $this->defaultReturnType, $this->extension); + } +} diff --git a/tests/EnumDynamicReturnTypeExtensionGetValuesTest.php b/tests/EnumDynamicReturnTypeExtensionGetValuesTest.php new file mode 100644 index 0000000..30e338b --- /dev/null +++ b/tests/EnumDynamicReturnTypeExtensionGetValuesTest.php @@ -0,0 +1,158 @@ +markTestSkipped('Enum::getValues() not supported in version 1.x'); + } + + $this->extension = new EnumDynamicReturnTypeExtension(); + } + + public function testNullType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValues()', 'array(null)', $this->extension); + } + + public function testBoolType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValues()', 'array(true, false)', $this->extension); + } + + public function testStringType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValues()', "array('str1', 'str2')", $this->extension); + } + + public function testIntType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValues()', 'array(0, 1)', $this->extension); + } + + public function testFloatType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValues()', 'array(1.1, 1.2)', $this->extension); + } + + public function testArrayType(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValues()', 'array(array(array()))', $this->extension); + } + + public function testAllTypes(): void + { + $code = <<<'CODE' +processCode( + $code, + '$enum->getValues()', + "array(null, true, 1, 1.1, 'str', array(null, true, 1, 1.1, 'str', array()))", + $this->extension + ); + } + + public function testBaseEnum(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValues()', 'array', $this->extension); + } + + public function testUnionEnum(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValues()', "array(0|'str1', 1|'str2')", $this->extension); + } + + public function testEnumAndNonEnum(): void + { + $code = <<<'CODE' +processCode($code, '$enum->getValues()', 'array', $this->extension); + } +} diff --git a/tests/EnumDynamicReturnTypeExtensionTest.php b/tests/EnumDynamicReturnTypeExtensionTest.php index 845b79f..9b2c676 100644 --- a/tests/EnumDynamicReturnTypeExtensionTest.php +++ b/tests/EnumDynamicReturnTypeExtensionTest.php @@ -19,144 +19,6 @@ protected function setUp(): void $this->extension = new EnumDynamicReturnTypeExtension(); } - public function testNullType(): void - { - $code = <<<'CODE' -processCode($code, '$enum->getValue()', 'null', $this->extension); - - if (method_exists(Enum::class, 'getValues')) { - $this->processCode($code, '$enum->getValues()', 'array', $this->extension); - } - } - - public function testBoolType(): void - { - $code = <<<'CODE' -processCode($code, '$enum->getValue()', 'bool', $this->extension); - - if (method_exists(Enum::class, 'getValues')) { - $this->processCode($code, '$enum->getValues()', 'array', $this->extension); - } - } - - public function testStringType(): void - { - $code = <<<'CODE' -processCode($code, '$enum->getValue()', "'str1'|'str2'", $this->extension); - - if (method_exists(Enum::class, 'getValues')) { - $this->processCode($code, '$enum->getValues()', "array", $this->extension); - } - } - - public function testIntType(): void - { - $code = <<<'CODE' -processCode($code, '$enum->getValue()', '0|1', $this->extension); - - if (method_exists(Enum::class, 'getValues')) { - $this->processCode($code, '$enum->getValues()', 'array', $this->extension); - } - } - - public function testFloatType(): void - { - $code = <<<'CODE' -processCode($code, '$enum->getValue()', '1.1|1.2', $this->extension); - - if (method_exists(Enum::class, 'getValues')) { - $this->processCode($code, '$enum->getValues()', 'array', $this->extension); - } - } - - public function testArrayType(): void - { - $code = <<<'CODE' -processCode($code, '$enum->getValue()', 'array(array())', $this->extension); - - if (method_exists(Enum::class, 'getValues')) { - $this->processCode($code, '$enum->getValues()', 'array', $this->extension); - } - } - - public function testUnionTypes(): void - { - $code = <<<'CODE' -processCode( - $code, - '$enum->getValue()', - "1|1.1|'str'|array(null, true, 1, 1.1, 'str', array())|true|null", - $this->extension - ); - - if (method_exists(Enum::class, 'getValues')) { - $this->processCode( - $code, - '$enum->getValues()', - "array", - $this->extension - ); - } - } - - public function testGeneralizedTypes(): void - { - $code = <<<'CODE' -processCode($code, '$enum->getValue()', 'string', $this->extension); - - if (method_exists(Enum::class, 'getValues')) { - $this->processCode($code, '$enum->getValues()', 'array', $this->extension); - } - } - public function testUnsupportedMethod(): void { $code = <<<'CODE'