mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 13:51:54 +01:00
Fix #4475 - verify that used attributes actual use the Attribute attribute
This commit is contained in:
parent
763eff2e8b
commit
14807326fe
@ -129,55 +129,69 @@ class AttributeAnalyzer
|
|||||||
|
|
||||||
$attribute_class_storage = $codebase->classlike_storage_provider->get($attribute->fq_class_name);
|
$attribute_class_storage = $codebase->classlike_storage_provider->get($attribute->fq_class_name);
|
||||||
|
|
||||||
if ($attribute_class_storage->attributes) {
|
$has_attribute_attribute = $attribute->fq_class_name === 'Attribute';
|
||||||
foreach ($attribute_class_storage->attributes as $attribute_attribute) {
|
|
||||||
if ($attribute_attribute->fq_class_name === 'Attribute') {
|
|
||||||
if (!$attribute_attribute->args) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$first_arg = reset($attribute_attribute->args);
|
foreach ($attribute_class_storage->attributes as $attribute_attribute) {
|
||||||
|
if ($attribute_attribute->fq_class_name === 'Attribute') {
|
||||||
|
$has_attribute_attribute = true;
|
||||||
|
|
||||||
$first_arg_type = $first_arg->type;
|
if (!$attribute_attribute->args) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($first_arg_type instanceof UnresolvedConstantComponent) {
|
$first_arg = reset($attribute_attribute->args);
|
||||||
$first_arg_type = new Union([
|
|
||||||
\Psalm\Internal\Codebase\ConstantTypeResolver::resolve(
|
|
||||||
$codebase->classlikes,
|
|
||||||
$first_arg_type,
|
|
||||||
$source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer ? $source : null
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$first_arg_type->isSingleIntLiteral()) {
|
$first_arg_type = $first_arg->type;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$acceptable_mask = $first_arg_type->getSingleIntLiteral()->value;
|
if ($first_arg_type instanceof UnresolvedConstantComponent) {
|
||||||
|
$first_arg_type = new Union([
|
||||||
|
\Psalm\Internal\Codebase\ConstantTypeResolver::resolve(
|
||||||
|
$codebase->classlikes,
|
||||||
|
$first_arg_type,
|
||||||
|
$source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer ? $source : null
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (($acceptable_mask & $target) !== $target) {
|
if (!$first_arg_type->isSingleIntLiteral()) {
|
||||||
$target_map = [
|
return;
|
||||||
1 => 'class',
|
}
|
||||||
2 => 'function',
|
|
||||||
4 => 'method',
|
|
||||||
8 => 'property',
|
|
||||||
16 => 'class constant',
|
|
||||||
32 => 'function/method parameter'
|
|
||||||
];
|
|
||||||
|
|
||||||
if (\Psalm\IssueBuffer::accepts(
|
$acceptable_mask = $first_arg_type->getSingleIntLiteral()->value;
|
||||||
new InvalidAttribute(
|
|
||||||
'This attribute can not be used on a ' . $target_map[$target],
|
if (($acceptable_mask & $target) !== $target) {
|
||||||
$attribute->name_location
|
$target_map = [
|
||||||
),
|
1 => 'class',
|
||||||
$source->getSuppressedIssues()
|
2 => 'function',
|
||||||
)) {
|
4 => 'method',
|
||||||
// fall through
|
8 => 'property',
|
||||||
}
|
16 => 'class constant',
|
||||||
|
32 => 'function/method parameter'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (\Psalm\IssueBuffer::accepts(
|
||||||
|
new InvalidAttribute(
|
||||||
|
'This attribute can not be used on a ' . $target_map[$target],
|
||||||
|
$attribute->name_location
|
||||||
|
),
|
||||||
|
$source->getSuppressedIssues()
|
||||||
|
)) {
|
||||||
|
// fall through
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$has_attribute_attribute) {
|
||||||
|
if (\Psalm\IssueBuffer::accepts(
|
||||||
|
new InvalidAttribute(
|
||||||
|
'The class ' . $attribute->fq_class_name . ' doesn’t have the Attribute attribute',
|
||||||
|
$attribute->name_location
|
||||||
|
),
|
||||||
|
$source->getSuppressedIssues()
|
||||||
|
)) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ abstract class Atomic implements TypeNode
|
|||||||
/**
|
/**
|
||||||
* @param array{int,int}|null $php_version
|
* @param array{int,int}|null $php_version
|
||||||
* @param array<string, array<string, array{Union}>> $template_type_map
|
* @param array<string, array<string, array{Union}>> $template_type_map
|
||||||
* @param array<string, TypeAlias\LinkableTypeAlias> $type_aliases
|
* @param array<string, TypeAlias> $type_aliases
|
||||||
*/
|
*/
|
||||||
public static function create(
|
public static function create(
|
||||||
string $value,
|
string $value,
|
||||||
|
@ -111,6 +111,17 @@ class AttributeTest extends TestCase
|
|||||||
public function providerInvalidCodeParse(): iterable
|
public function providerInvalidCodeParse(): iterable
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'attributeClassHasNoAttributeAnnotation' => [
|
||||||
|
'<?php
|
||||||
|
class A {}
|
||||||
|
|
||||||
|
#[A]
|
||||||
|
class B {}',
|
||||||
|
'error_message' => 'InvalidAttribute',
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
'8.0'
|
||||||
|
],
|
||||||
'missingAttributeOnClass' => [
|
'missingAttributeOnClass' => [
|
||||||
'<?php
|
'<?php
|
||||||
use Foo\Bar\Pure;
|
use Foo\Bar\Pure;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user