Skip to content

Commit 31eac4f

Browse files
Add RequireExtends and RequireImplements attributes
1 parent 3394d5d commit 31eac4f

8 files changed

+204
-22
lines changed

README.md

+22-20
Original file line numberDiff line numberDiff line change
@@ -94,27 +94,29 @@ This extension works by interacting with the parser that PHPStan uses to parse t
9494

9595
These are the available attributes and their corresponding PHPDoc annotations:
9696

97-
| Attribute | PHPDoc Annotations |
98-
|-------------------------------------------------------------------------------------------------------------------|--------------------------------------|
99-
| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` |
100-
| [Internal](https://github.com/php-static-analysis/attributes/blob/main/doc/Internal.md) | `@internal` |
101-
| [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |
102-
| [Method](https://github.com/php-static-analysis/attributes/blob/main/doc/Method.md) | `@method` |
103-
| [Mixin](https://github.com/php-static-analysis/attributes/blob/main/doc/Mixin.md) | `@mixin` |
104-
| [Param](https://github.com/php-static-analysis/attributes/blob/main/doc/Param.md) | `@param` |
105-
| [ParamOut](https://github.com/php-static-analysis/attributes/blob/main/doc/ParamOut.md) | `@param-out` |
106-
| [Property](https://github.com/php-static-analysis/attributes/blob/main/doc/Property.md) | `@property` `@var` |
107-
| [PropertyRead](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyRead.md) | `@property-read` |
108-
| [PropertyWrite](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyWrite.md) | `@property-write` |
109-
| [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
110-
| [SelfOut](https://github.com/php-static-analysis/attributes/blob/main/doc/SelfOut.md) | `@self-out` `@this-out` |
111-
| [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
97+
| Attribute | PHPDoc Annotations |
98+
|------------------------------------------------------------------------------------------------------------|--------------------------------------|
99+
| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` |
100+
| [Internal](https://github.com/php-static-analysis/attributes/blob/main/doc/Internal.md) | `@internal` |
101+
| [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |
102+
| [Method](https://github.com/php-static-analysis/attributes/blob/main/doc/Method.md) | `@method` |
103+
| [Mixin](https://github.com/php-static-analysis/attributes/blob/main/doc/Mixin.md) | `@mixin` |
104+
| [Param](https://github.com/php-static-analysis/attributes/blob/main/doc/Param.md) | `@param` |
105+
| [ParamOut](https://github.com/php-static-analysis/attributes/blob/main/doc/ParamOut.md) | `@param-out` |
106+
| [Property](https://github.com/php-static-analysis/attributes/blob/main/doc/Property.md) | `@property` `@var` |
107+
| [PropertyRead](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyRead.md) | `@property-read` |
108+
| [PropertyWrite](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyWrite.md) | `@property-write` |
109+
| [RequireExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireExtends.md) | `@require-extends` |
110+
| [RequireImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireImplements.md) | `@require-implements` |
111+
| [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
112+
| [SelfOut](https://github.com/php-static-analysis/attributes/blob/main/doc/SelfOut.md) | `@self-out` `@this-out` |
113+
| [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
112114
| [TemplateContravariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateContravariant.md) | `@template-contravariant` |
113-
| [TemplateCovariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateCovariant.md) | `@template-covariant` |
114-
| [TemplateExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateExtends.md) | `@extends` `@template-extends` |
115-
| [TemplateImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateImplements.md) | `@implements` `@template-implements` |
116-
| [TemplateUse](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateUse.md) | `@use` `@template-use` |
117-
| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` |
115+
| [TemplateCovariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateCovariant.md) | `@template-covariant` |
116+
| [TemplateExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateExtends.md) | `@extends` `@template-extends` |
117+
| [TemplateImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateImplements.md) | `@implements` `@template-implements` |
118+
| [TemplateUse](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateUse.md) | `@use` `@template-use` |
119+
| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` |
118120

119121

120122

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
"prefer-stable": true,
2525
"require": {
2626
"php": ">=8.0",
27-
"php-static-analysis/attributes": "^0.1.13 || dev-main",
28-
"php-static-analysis/node-visitor": "^0.1.13 || dev-main",
27+
"php-static-analysis/attributes": "^0.1.14 || dev-main",
28+
"php-static-analysis/node-visitor": "^0.1.14 || dev-main",
2929
"phpstan/phpstan": "^1.8"
3030
},
3131
"require-dev": {

tests/RequireExtendsAttributeTest.php

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension;
4+
5+
class RequireExtendsAttributeTest extends BaseAttributeTestCase
6+
{
7+
public function testClassRequireExtendsAttribute(): void
8+
{
9+
$errors = $this->analyse(__DIR__ . '/data/RequireExtends/TraitRequireExtendsAttribute.php');
10+
$expectedErrors = [
11+
'Trait test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends\TraitRequireExtendsAttribute requires using class to extend test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends\ClassRequireExtendsAttribute, but test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends\ClassRequireExtendsAttributeChild2 does not.' => 21,
12+
];
13+
14+
$this->checkExpectedErrors($errors, $expectedErrors);
15+
}
16+
17+
public function testInvalidClassRequireExtendsAttribute(): void
18+
{
19+
$errors = $this->analyse(__DIR__ . '/data/RequireExtends/InvalidTraitRequireExtendsAttribute.php');
20+
21+
$expectedErrors = [
22+
'PHPDoc tag @phpstan-require-extends has invalid value (): Unexpected token "\n ", expected type at offset 31' => 7,
23+
'Parameter #1 $class of attribute class PhpStaticAnalysis\Attributes\RequireExtends constructor expects string, int given.' => 7,
24+
'PHPDoc tag @phpstan-require-extends contains non-object type int.' => 12,
25+
'PHPDoc tag @phpstan-require-extends can only be used once.' => 17,
26+
'Attribute class PhpStaticAnalysis\Attributes\RequireExtends is not repeatable but is already present above the class.' => 18,
27+
'Attribute class PhpStaticAnalysis\Attributes\RequireExtends does not have the property target.' => 21,
28+
];
29+
30+
$this->checkExpectedErrors($errors, $expectedErrors);
31+
}
32+
}
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension;
4+
5+
class RequireImplementsAttributeTest extends BaseAttributeTestCase
6+
{
7+
public function testClassRequireImplementsAttribute(): void
8+
{
9+
$errors = $this->analyse(__DIR__ . '/data/RequireImplements/TraitRequireImplementsAttribute.php');
10+
$expectedErrors = [
11+
'Trait test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements\TraitRequireImplementsAttribute requires using class to implement test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements\InterfaceRequireImplementsAttribute3, but test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements\ClassRequireImplementsAttribute2 does not.' => 33,
12+
];
13+
14+
$this->checkExpectedErrors($errors, $expectedErrors);
15+
}
16+
17+
public function testInvalidClassRequireImplementsAttribute(): void
18+
{
19+
$errors = $this->analyse(__DIR__ . '/data/RequireImplements/InvalidTraitRequireImplementsAttribute.php');
20+
21+
$expectedErrors = [
22+
'PHPDoc tag @phpstan-require-implements has invalid value (): Unexpected token "\n ", expected type at offset 34' => 7,
23+
'Parameter #1 ...$interfaces of attribute class PhpStaticAnalysis\Attributes\RequireImplements constructor expects string, int given.' => 7,
24+
'PHPDoc tag @phpstan-require-implements contains non-object type int.' => 12,
25+
'Attribute class PhpStaticAnalysis\Attributes\RequireImplements does not have the property target.' => 15,
26+
];
27+
28+
$this->checkExpectedErrors($errors, $expectedErrors);
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends;
4+
5+
use PhpStaticAnalysis\Attributes\RequireExtends;
6+
7+
#[RequireExtends(0)]
8+
trait InvalidTraitRequireExtendsAttribute2
9+
{
10+
}
11+
12+
#[RequireExtends('+5')]
13+
trait InvalidTraitRequireExtendsAttributeChild3
14+
{
15+
}
16+
17+
#[RequireExtends('InvalidClassRequireExtendsAttribute')]
18+
#[RequireExtends('InvalidClassRequireExtendsAttribute')]
19+
trait InvalidTraitRequireExtendsAttribute
20+
{
21+
#[RequireExtends('InvalidClassRequireExtendsAttribute')]
22+
public string $name = '';
23+
}
24+
25+
class InvalidClassRequireExtendsAttribute
26+
{
27+
}
28+
29+
class InvalidClassRequireExtendsAttributeChild extends InvalidClassRequireExtendsAttribute
30+
{
31+
use InvalidTraitRequireExtendsAttribute;
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends;
4+
5+
use PhpStaticAnalysis\Attributes\RequireExtends;
6+
7+
#[RequireExtends('ClassRequireExtendsAttribute')] // the class using this trait needs to extend this class
8+
trait TraitRequireExtendsAttribute
9+
{
10+
}
11+
12+
class ClassRequireExtendsAttribute
13+
{
14+
}
15+
16+
class ClassRequireExtendsAttributeChild extends ClassRequireExtendsAttribute
17+
{
18+
use TraitRequireExtendsAttribute;
19+
}
20+
21+
class ClassRequireExtendsAttributeChild2
22+
{
23+
use TraitRequireExtendsAttribute;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements;
4+
5+
use PhpStaticAnalysis\Attributes\RequireImplements;
6+
7+
#[RequireImplements(0)]
8+
trait InvalidTraitRequireImplementsAttribute2
9+
{
10+
}
11+
12+
#[RequireImplements('+5')]
13+
trait InvalidTraitRequireImplementsAttribute
14+
{
15+
#[RequireImplements('InvalidInterfaceRequireImplementsAttribute')]
16+
public string $name = '';
17+
}
18+
19+
interface InvalidInterfaceRequireImplementsAttribute
20+
{
21+
}
22+
23+
class InvalidClassRequireImplementsAttribute implements InvalidInterfaceRequireImplementsAttribute
24+
{
25+
use InvalidTraitRequireImplementsAttribute;
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements;
4+
5+
use PhpStaticAnalysis\Attributes\RequireImplements;
6+
7+
#[RequireImplements('InterfaceRequireImplementsAttribute')] // the class that uses this trait needs to implement these interfaces
8+
#[RequireImplements(
9+
'InterfaceRequireImplementsAttribute2',
10+
'InterfaceRequireImplementsAttribute3'
11+
)]
12+
trait TraitRequireImplementsAttribute
13+
{
14+
}
15+
16+
interface InterfaceRequireImplementsAttribute
17+
{
18+
}
19+
20+
interface InterfaceRequireImplementsAttribute2
21+
{
22+
}
23+
24+
interface InterfaceRequireImplementsAttribute3
25+
{
26+
}
27+
28+
class ClassRequireImplementsAttribute implements InterfaceRequireImplementsAttribute, InterfaceRequireImplementsAttribute2, InterfaceRequireImplementsAttribute3
29+
{
30+
use TraitRequireImplementsAttribute;
31+
}
32+
33+
class ClassRequireImplementsAttribute2 implements InterfaceRequireImplementsAttribute, InterfaceRequireImplementsAttribute2
34+
{
35+
use TraitRequireImplementsAttribute;
36+
}

0 commit comments

Comments
 (0)