-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* BackedEnumGenericsRule * readme * fix php 7.4 * Proper fix by conditional ignore
- Loading branch information
Showing
8 changed files
with
198 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
$config = []; | ||
|
||
// https://github.com/phpstan/phpstan/issues/6290 | ||
if (PHP_VERSION_ID < 80_000) { | ||
$config['parameters']['ignoreErrors'][] = '~Class BackedEnum not found.~'; | ||
} | ||
|
||
return $config; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace ShipMonk\PHPStan\Rule; | ||
|
||
use BackedEnum; | ||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\InClassNode; | ||
use PHPStan\Reflection\ClassReflection; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Type\VerbosityLevel; | ||
|
||
/** | ||
* @implements Rule<InClassNode> | ||
*/ | ||
class BackedEnumGenericsRule implements Rule | ||
{ | ||
|
||
public function getNodeType(): string | ||
{ | ||
return InClassNode::class; | ||
} | ||
|
||
/** | ||
* @param InClassNode $node | ||
* @return string[] | ||
*/ | ||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
$classReflection = $node->getClassReflection(); | ||
$backedEnumType = $classReflection->getBackedEnumType(); | ||
|
||
if ($backedEnumType === null) { | ||
return []; | ||
} | ||
|
||
if (!$this->isGenericBackedEnum($classReflection)) { | ||
return []; | ||
} | ||
|
||
$expectedType = $backedEnumType->describe(VerbosityLevel::typeOnly()); | ||
$expectedTag = BackedEnum::class . "<$expectedType>"; | ||
|
||
foreach ($classReflection->getAncestors() as $interface) { | ||
if ($this->hasGenericsTag($interface, $expectedTag)) { | ||
return []; | ||
} | ||
} | ||
|
||
return ["Class {$classReflection->getName()} extends generic BackedEnum, but does not specify its type. Use @implements $expectedTag"]; | ||
} | ||
|
||
private function hasGenericsTag(ClassReflection $classReflection, string $expectedTag): bool | ||
{ | ||
if ($classReflection->isBackedEnum()) { | ||
$tags = $classReflection->getImplementsTags(); | ||
} elseif ($classReflection->isInterface()) { | ||
$tags = $classReflection->getExtendsTags(); | ||
} else { | ||
$tags = []; | ||
} | ||
|
||
foreach ($tags as $tag) { | ||
$implementsTagType = $tag->getType(); | ||
|
||
if ($implementsTagType->describe(VerbosityLevel::typeOnly()) === $expectedTag) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private function isGenericBackedEnum(ClassReflection $classReflection): bool | ||
{ | ||
foreach ($classReflection->getAncestors() as $ancestor) { | ||
if ($ancestor->getName() === BackedEnum::class && $ancestor->isGeneric()) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace ShipMonk\PHPStan\Rule; | ||
|
||
use PHPStan\Rules\Rule; | ||
use ShipMonk\PHPStan\RuleTestCase; | ||
use function array_merge; | ||
use const PHP_VERSION_ID; | ||
|
||
/** | ||
* @extends RuleTestCase<BackedEnumGenericsRule> | ||
*/ | ||
class BackedEnumGenericsRuleTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new BackedEnumGenericsRule(); | ||
} | ||
|
||
/** | ||
* @return string[] | ||
*/ | ||
public static function getAdditionalConfigFiles(): array | ||
{ | ||
return array_merge( | ||
parent::getAdditionalConfigFiles(), | ||
[__DIR__ . '/data/BackedEnumGenericsRule/stub.neon'], | ||
); | ||
} | ||
|
||
public function testClass(): void | ||
{ | ||
if (PHP_VERSION_ID < 80_100) { | ||
self::markTestSkipped('Requires PHP 8.1'); | ||
} | ||
|
||
$this->analyseFile(__DIR__ . '/data/BackedEnumGenericsRule/code.php'); | ||
} | ||
|
||
} |
14 changes: 14 additions & 0 deletions
14
tests/Rule/data/BackedEnumGenericsRule/BackedEnum.php.stub
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
|
||
/** | ||
* @template T of int|string | ||
*/ | ||
interface BackedEnum | ||
{ | ||
|
||
/** | ||
* @var T | ||
*/ | ||
public $value; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
/** | ||
* @implements BackedEnum<string> | ||
*/ | ||
enum MyStringEnum: string { | ||
} | ||
|
||
/** | ||
* @implements BackedEnum<int> | ||
*/ | ||
enum MyIntEnum: int { | ||
} | ||
|
||
/** | ||
* @extends BackedEnum<int> | ||
*/ | ||
interface MyBackedEnum extends BackedEnum { | ||
|
||
} | ||
|
||
enum MyIntEnumWithoutImplements: int { // error: Class MyIntEnumWithoutImplements extends generic BackedEnum, but does not specify its type. Use @implements BackedEnum<int> | ||
} | ||
|
||
enum MyIntEnumWithImplementsInParent: int implements MyBackedEnum { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
parameters: | ||
stubFiles: | ||
- BackedEnum.php.stub |