From 72103f9eaf80436b3439f80f66c8dcd95dd6c93d Mon Sep 17 00:00:00 2001 From: Samuel Levy Date: Mon, 22 Aug 2022 12:26:25 +1000 Subject: [PATCH] Added "Invokable Cases" PHPStan extension testing --- phpstan.neon | 1 + .../InvokableCases/InvokableCasesTestCase.php | 44 +++++++++ tests/PHPStan/InvokableCases/Role.php | 26 +++++ tests/PHPStan/InvokableCases/Status.php | 26 +++++ tests/PHPStan/InvokableCases/Suits.php | 30 ++++++ tests/PHPStan/InvokableCasesTest.php | 99 +++++++++++++++++++ 6 files changed, 226 insertions(+) create mode 100644 tests/PHPStan/InvokableCases/InvokableCasesTestCase.php create mode 100644 tests/PHPStan/InvokableCases/Role.php create mode 100644 tests/PHPStan/InvokableCases/Status.php create mode 100644 tests/PHPStan/InvokableCases/Suits.php create mode 100644 tests/PHPStan/InvokableCasesTest.php diff --git a/phpstan.neon b/phpstan.neon index 6a91329..8efbb66 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,6 @@ includes: - ./vendor/nunomaduro/larastan/extension.neon + - ./extension.neon parameters: paths: diff --git a/tests/PHPStan/InvokableCases/InvokableCasesTestCase.php b/tests/PHPStan/InvokableCases/InvokableCasesTestCase.php new file mode 100644 index 0000000..ae04874 --- /dev/null +++ b/tests/PHPStan/InvokableCases/InvokableCasesTestCase.php @@ -0,0 +1,44 @@ +createReflectionProvider(); + $class = $reflectionProvider->getClass($enum); + + if ($exists) { + $this->assertTrue($class->hasMethod($case), sprintf('%s on class %s does not exist', $case, $enum)); + $method = $class->getMethod($case, new OutOfClassScope()); + if ($static) { + $this->assertTrue($method->isStatic(), sprintf('%s on class %s is not static', $case, $enum)); + } else { + $this->assertFalse($method->isStatic(), sprintf('%s on class %s is static', $case, $enum)); + } + } else { + $this->assertFalse($class->hasMethod($case), sprintf('%s on class %s exists', $case, $enum)); + } + } + + public function assertStaticallyCallableType(string $enum, string $case, string $type): void + { + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($enum); + + $method = $class->getMethod($case, new OutOfClassScope()); + $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); + $methodReturnType = $methodVariant->getReturnType(); + $this->assertInstanceOf($type, $methodReturnType); + } +} diff --git a/tests/PHPStan/InvokableCases/Role.php b/tests/PHPStan/InvokableCases/Role.php new file mode 100644 index 0000000..9f7519c --- /dev/null +++ b/tests/PHPStan/InvokableCases/Role.php @@ -0,0 +1,26 @@ +value > self::created->value; + } +} diff --git a/tests/PHPStan/InvokableCases/Suits.php b/tests/PHPStan/InvokableCases/Suits.php new file mode 100644 index 0000000..c4ebc57 --- /dev/null +++ b/tests/PHPStan/InvokableCases/Suits.php @@ -0,0 +1,30 @@ + true, + default => false, + }; + } +} diff --git a/tests/PHPStan/InvokableCasesTest.php b/tests/PHPStan/InvokableCasesTest.php new file mode 100644 index 0000000..26a6bc3 --- /dev/null +++ b/tests/PHPStan/InvokableCasesTest.php @@ -0,0 +1,99 @@ +assertStaticallyCallable($class, 'cases'); + $this->assertStaticallyCallable($class, 'from', false); + $this->assertStaticallyCallable($class, 'tryFrom', false); + + // Defined methods + $this->assertStaticallyCallable($class, 'administrator'); + $this->assertStaticallyCallable($class, 'isManager', true, false); + + // Cases + $this->assertStaticallyCallable($class, 'admin'); + $this->assertStaticallyCallable($class, 'manager'); + $this->assertStaticallyCallable($class, 'staff'); + + // Missing Case + $this->assertStaticallyCallable($class, 'customer', false); +}); + +it('correctly identifies allowable static method calls for invokable int backed enum', function () { + $class = \ArchTech\Enums\Tests\PHPStan\InvokableCases\Status::class; + + // Base enum + $this->assertStaticallyCallable($class, 'cases'); + $this->assertStaticallyCallable($class, 'from'); + $this->assertStaticallyCallable($class, 'tryFrom'); + + // Defined methods + $this->assertStaticallyCallable($class, 'initial'); + $this->assertStaticallyCallable($class, 'isStarted', true, false); + + // Cases + $this->assertStaticallyCallable($class, 'created'); + $this->assertStaticallyCallable($class, 'running'); + $this->assertStaticallyCallable($class, 'done'); + + // Missing Case + $this->assertStaticallyCallable($class, 'failed', false); +}); + +it('correctly identifies allowable static method calls for invokable string backed enum', function () { + $class = \ArchTech\Enums\Tests\PHPStan\InvokableCases\Suits::class; + + // Base enum + $this->assertStaticallyCallable($class, 'cases'); + $this->assertStaticallyCallable($class, 'from'); + $this->assertStaticallyCallable($class, 'tryFrom'); + + // Defined methods + $this->assertStaticallyCallable($class, 'valuable'); + $this->assertStaticallyCallable($class, 'isRed', true, false); + + // Cases + $this->assertStaticallyCallable($class, 'clubs'); + $this->assertStaticallyCallable($class, 'diamonds'); + $this->assertStaticallyCallable($class, 'hearts'); + $this->assertStaticallyCallable($class, 'spades'); + + // Missing Case + $this->assertStaticallyCallable($class, 'joker', false); +}); + +it('correctly identifies types for invoked pure enum cases', function () { + $class = \ArchTech\Enums\Tests\PHPStan\InvokableCases\Role::class; + + // Cases + $this->assertStaticallyCallableType($class, 'admin', StringType::class); + $this->assertStaticallyCallableType($class, 'manager', StringType::class); + $this->assertStaticallyCallableType($class, 'staff', StringType::class); +}); + +it('correctly identifies types for invoked int backed enum cases', function () { + $class = \ArchTech\Enums\Tests\PHPStan\InvokableCases\Status::class; + + // Cases + $this->assertStaticallyCallableType($class, 'created', IntegerType::class); + $this->assertStaticallyCallableType($class, 'running', IntegerType::class); + $this->assertStaticallyCallableType($class, 'done', IntegerType::class); +}); + +it('correctly identifies types for invoked string backed enum cases', function () { + $class = \ArchTech\Enums\Tests\PHPStan\InvokableCases\Suits::class; + + // Cases + $this->assertStaticallyCallableType($class, 'clubs', StringType::class); + $this->assertStaticallyCallableType($class, 'diamonds', StringType::class); + $this->assertStaticallyCallableType($class, 'hearts', StringType::class); + $this->assertStaticallyCallableType($class, 'spades', StringType::class); +});