1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Forbid most magic methods on enums

Fixes vimeo/psalm#8889

Additionally this fixes case-sensitivity of
MethodSignatureMustOmitReturnType issue

Fixes vimeo/psalm#8888
This commit is contained in:
Bruce Weirdan 2022-12-12 03:03:20 -04:00
parent ca0b2a1b65
commit 19a1005bc3
No known key found for this signature in database
GPG Key ID: CFC3AAB181751B0D
8 changed files with 84 additions and 3 deletions

View File

@ -264,6 +264,7 @@
<xs:element name="InvalidDocblockParamName" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidEnumBackingType" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidEnumCaseValue" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidEnumMethod" type="MethodIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidExtendClass" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidFalsableReturnType" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidFunctionCall" type="IssueHandlerType" minOccurs="0" />

View File

@ -45,6 +45,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even
- [InaccessibleProperty](issues/InaccessibleProperty.md)
- [InterfaceInstantiation](issues/InterfaceInstantiation.md)
- [InvalidAttribute](issues/InvalidAttribute.md)
- [InvalidEnumMethod](issues/InvalidEnumMethod.md)
- [InvalidExtendClass](issues/InvalidExtendClass.md)
- [InvalidGlobal](issues/InvalidGlobal.md)
- [InvalidParamDefault](issues/InvalidParamDefault.md)

View File

@ -69,6 +69,7 @@
- [InvalidDocblockParamName](issues/InvalidDocblockParamName.md)
- [InvalidEnumBackingType](issues/InvalidEnumBackingType.md)
- [InvalidEnumCaseValue](issues/InvalidEnumCaseValue.md)
- [InvalidEnumMethod](issues/InvalidEnumMethod.md)
- [InvalidExtendClass](issues/InvalidExtendClass.md)
- [InvalidFalsableReturnType](issues/InvalidFalsableReturnType.md)
- [InvalidFunctionCall](issues/InvalidFunctionCall.md)

View File

@ -0,0 +1,15 @@
# InvalidEnumMethod
Enums may not define most of the magic methods like `__get`, `__toString`, etc.
```php
<?php
enum Status: string {
case Open = 'open';
case Closed = 'closed';
public function __toString(): string {
return "SomeStatus";
}
}
```

View File

@ -2020,6 +2020,10 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
MethodAnalyzer::checkMethodSignatureMustOmitReturnType($storage, $codeLocation);
if ($appearing_class_storage->is_enum) {
MethodAnalyzer::checkForbiddenEnumMethod($storage);
}
if (!$context->calling_method_id || !$context->collect_initializations) {
$context->calling_method_id = strtolower((string)$method_id);
}

View File

@ -9,6 +9,7 @@ use Psalm\Codebase;
use Psalm\Context;
use Psalm\Internal\Codebase\InternalCallMapHandler;
use Psalm\Internal\MethodIdentifier;
use Psalm\Issue\InvalidEnumMethod;
use Psalm\Issue\InvalidStaticInvocation;
use Psalm\Issue\MethodSignatureMustOmitReturnType;
use Psalm\Issue\NonStaticSelfCall;
@ -27,6 +28,24 @@ use function strtolower;
*/
class MethodAnalyzer extends FunctionLikeAnalyzer
{
// https://github.com/php/php-src/blob/a83923044c48982c80804ae1b45e761c271966d3/Zend/zend_enum.c#L77-L95
private const FORBIDDEN_ENUM_METHODS = [
'__construct',
'__destruct',
'__clone',
'__get',
'__set',
'__unset',
'__isset',
'__tostring',
'__debuginfo',
'__serialize',
'__unserialize',
'__sleep',
'__wakeup',
'__set_state',
];
/** @psalm-external-mutation-free */
public function __construct(
PhpParser\Node\Stmt\ClassMethod $function,
@ -266,13 +285,17 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
return;
}
$cased_method_name = $method_storage->cased_name;
if ($method_storage->cased_name === null) {
return;
}
$method_name_lc = strtolower($method_storage->cased_name);
$methodsOfInterest = ['__clone', '__construct', '__destruct'];
if (in_array($cased_method_name, $methodsOfInterest)) {
if (in_array($method_name_lc, $methodsOfInterest, true)) {
IssueBuffer::maybeAdd(
new MethodSignatureMustOmitReturnType(
'Method ' . $cased_method_name . ' must not declare a return type',
'Method ' . $method_storage->cased_name . ' must not declare a return type',
$code_location
)
);
@ -288,4 +311,20 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
strtolower($function_name)
);
}
public static function checkForbiddenEnumMethod(MethodStorage $method_storage): void
{
if ($method_storage->cased_name === null || $method_storage->location === null) {
return;
}
$method_name_lc = strtolower($method_storage->cased_name);
if (in_array($method_name_lc, self::FORBIDDEN_ENUM_METHODS, true)) {
IssueBuffer::maybeAdd(new InvalidEnumMethod(
'Enums cannot define ' . $method_storage->cased_name,
$method_storage->location,
$method_storage->defining_fqcln . '::' . $method_storage->cased_name
));
}
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Psalm\Issue;
final class InvalidEnumMethod extends MethodIssue
{
public const ERROR_LEVEL = -1;
public const SHORTCODE = 314;
}

View File

@ -686,6 +686,17 @@ class EnumTest extends TestCase
'ignored_issues' => [],
'php_version' => '8.1',
],
'forbiddenMethod' => [
'code' => '<?php
enum Foo {
case A;
public function __get() {}
}
',
'error_message' => 'InvalidEnumMethod',
'ignored_issues' => [],
'php_version' => '8.1',
],
];
}
}