1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Fix #4475 - verify that used attributes actual use the Attribute attribute

This commit is contained in:
Matt Brown 2020-11-22 00:52:56 -05:00 committed by Daniil Gentili
parent 763eff2e8b
commit 14807326fe
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
3 changed files with 65 additions and 40 deletions

View File

@ -129,55 +129,69 @@ class AttributeAnalyzer
$attribute_class_storage = $codebase->classlike_storage_provider->get($attribute->fq_class_name);
if ($attribute_class_storage->attributes) {
foreach ($attribute_class_storage->attributes as $attribute_attribute) {
if ($attribute_attribute->fq_class_name === 'Attribute') {
if (!$attribute_attribute->args) {
return;
}
$has_attribute_attribute = $attribute->fq_class_name === 'Attribute';
$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_type = new Union([
\Psalm\Internal\Codebase\ConstantTypeResolver::resolve(
$codebase->classlikes,
$first_arg_type,
$source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer ? $source : null
)
]);
}
$first_arg = reset($attribute_attribute->args);
if (!$first_arg_type->isSingleIntLiteral()) {
return;
}
$first_arg_type = $first_arg->type;
$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) {
$target_map = [
1 => 'class',
2 => 'function',
4 => 'method',
8 => 'property',
16 => 'class constant',
32 => 'function/method parameter'
];
if (!$first_arg_type->isSingleIntLiteral()) {
return;
}
if (\Psalm\IssueBuffer::accepts(
new InvalidAttribute(
'This attribute can not be used on a ' . $target_map[$target],
$attribute->name_location
),
$source->getSuppressedIssues()
)) {
// fall through
}
$acceptable_mask = $first_arg_type->getSingleIntLiteral()->value;
if (($acceptable_mask & $target) !== $target) {
$target_map = [
1 => 'class',
2 => 'function',
4 => 'method',
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 . ' doesnt have the Attribute attribute',
$attribute->name_location
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}

View File

@ -90,7 +90,7 @@ abstract class Atomic implements TypeNode
/**
* @param array{int,int}|null $php_version
* @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(
string $value,

View File

@ -111,6 +111,17 @@ class AttributeTest extends TestCase
public function providerInvalidCodeParse(): iterable
{
return [
'attributeClassHasNoAttributeAnnotation' => [
'<?php
class A {}
#[A]
class B {}',
'error_message' => 'InvalidAttribute',
[],
false,
'8.0'
],
'missingAttributeOnClass' => [
'<?php
use Foo\Bar\Pure;