Skip to content

Commit fada1e1

Browse files
Add the TemplateExtends, TemplateImplements and TemplateUse attributes
1 parent e1e3ea2 commit fada1e1

17 files changed

+266
-24
lines changed

README.md

+19-16
Original file line numberDiff line numberDiff line change
@@ -94,22 +94,25 @@ 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-
| [Property](https://github.com/php-static-analysis/attributes/blob/main/doc/Property.md) | `@property` `@var` |
106-
| [PropertyRead](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyRead.md) | `@property-read` |
107-
| [PropertyWrite](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyWrite.md) | `@property-write` |
108-
| [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
109-
| [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
110-
| [TemplateContravariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateContravariant.md) | `@template-contravariant` |
111-
| [TemplateCovariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateCovariant.md) | `@template-covariant` |
112-
| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` |
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+
| [Property](https://github.com/php-static-analysis/attributes/blob/main/doc/Property.md) | `@property` `@var` |
106+
| [PropertyRead](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyRead.md) | `@property-read` |
107+
| [PropertyWrite](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyWrite.md) | `@property-write` |
108+
| [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
109+
| [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
110+
| [TemplateContravariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateContravariant.md) | `@template-contravariant` |
111+
| [TemplateCovariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateCovariant.md) | `@template-covariant` |
112+
| [TemplateExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateExtends.md) | `@extends` `@template-extends` |
113+
| [TemplateImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateImplements.md) | `@implements` `@template-implements` |
114+
| [TemplateUse](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateUse.md) | `@use` `@template-use` |
115+
| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` |
113116

114117

115118

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.10 || dev-main",
28-
"php-static-analysis/node-visitor": "^0.1.10 || dev-main",
27+
"php-static-analysis/attributes": "^0.1.11 || dev-main",
28+
"php-static-analysis/node-visitor": "^0.1.11 || dev-main",
2929
"phpstan/phpstan": "^1.8"
3030
},
3131
"require-dev": {

tests/MethodAttributeTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function testInvalidClassMethodAttribute(): void
2929
$expectedErrors = [
3030
'PHPDoc tag @method has invalid value (): Unexpected token "\n * ", expected type at offset 14' => 8,
3131
'PHPDoc tag @method has invalid value (string): Unexpected token "\n * ", expected \'(\' at offset 32' => 8,
32-
'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\Method constructor expects string, int given.' => 8,
32+
'Parameter #1 ...$methods of attribute class PhpStaticAnalysis\Attributes\Method constructor expects string, int given.' => 8,
3333
'Attribute class PhpStaticAnalysis\Attributes\Method does not have the method target.' => 13,
3434
'Call to an undefined method test\PhpStaticAnalysis\PHPStanExtension\data\Method\InvalidClassMethodAttribute::badFunction().' => 31,
3535
];

tests/MixinAttributeTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function testInvalidClassMixinAttribute(): void
2929
$expectedErrors = [
3030
'PHPDoc tag @mixin contains unknown class test\PhpStaticAnalysis\PHPStanExtension\data\Mixin\count.' => 7,
3131
'PHPDoc tag @mixin has invalid value (): Unexpected token "\n * ", expected type at offset 13' => 7,
32-
'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\Mixin constructor expects string, int given.' => 7,
32+
'Parameter #1 ...$classes of attribute class PhpStaticAnalysis\Attributes\Mixin constructor expects string, int given.' => 7,
3333
'Attribute class PhpStaticAnalysis\Attributes\Mixin does not have the method target.' => 11,
3434
];
3535

tests/ParamAttributeTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function testFunctionParamAttribute(): void
1616
$this->assertCount(0, $errors);
1717
}
1818

19-
public function testInvalidMethodReturnsAttribute(): void
19+
public function testInvalidMethodParamAttribute(): void
2020
{
2121
$errors = $this->analyse(__DIR__ . '/data/Param/InvalidMethodParamAttribute.php');
2222

tests/PropertyAttributeTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function testInvalidClassPropertyAttribute(): void
3030
'PHPDoc tag @property has invalid value (): Unexpected token "\n * ", expected type at offset 16' => 7,
3131
'PHPDoc tag @property has invalid value (count($a) $name): Unexpected token "(", expected variable at offset 55' => 7,
3232
'PHPDoc tag @property has invalid value (string): Unexpected token "\n * ", expected variable at offset 36' => 7,
33-
'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\Property constructor expects string, int given.' => 7,
33+
'Parameter #1 ...$properties of attribute class PhpStaticAnalysis\Attributes\Property constructor expects string, int given.' => 7,
3434
'Attribute class PhpStaticAnalysis\Attributes\Property does not have the method target.' => 12,
3535
];
3636

tests/PropertyReadAttributeTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function testInvalidClassPropertyReadAttribute(): void
3030
'PHPDoc tag @property-read has invalid value (): Unexpected token "\n * ", expected type at offset 21' => 7,
3131
'PHPDoc tag @property-read has invalid value (count($a) $name): Unexpected token "(", expected variable at offset 70' => 7,
3232
'PHPDoc tag @property-read has invalid value (string): Unexpected token "\n * ", expected variable at offset 46' => 7,
33-
'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\PropertyRead constructor expects string, int given.' => 7,
33+
'Parameter #1 ...$properties of attribute class PhpStaticAnalysis\Attributes\PropertyRead constructor expects string, int given.' => 7,
3434
'Attribute class PhpStaticAnalysis\Attributes\PropertyRead does not have the method target.' => 13,
3535
'Property test\PhpStaticAnalysis\PHPStanExtension\data\PropertyRead\ClassPropertyReadAttribute::$age is not writable.' => 21,
3636
];

tests/PropertyWriteAttributeTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function testInvalidClassPropertyWriteAttribute(): void
3030
'PHPDoc tag @property-write has invalid value (): Unexpected token "\n * ", expected type at offset 22' => 7,
3131
'PHPDoc tag @property-write has invalid value (count($a) $name): Unexpected token "(", expected variable at offset 73' => 7,
3232
'PHPDoc tag @property-write has invalid value (string): Unexpected token "\n * ", expected variable at offset 48' => 7,
33-
'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\PropertyWrite constructor expects string, int given.' => 7,
33+
'Parameter #1 ...$properties of attribute class PhpStaticAnalysis\Attributes\PropertyWrite constructor expects string, int given.' => 7,
3434
'Attribute class PhpStaticAnalysis\Attributes\PropertyWrite does not have the method target.' => 13,
3535
'Property test\PhpStaticAnalysis\PHPStanExtension\data\PropertyWrite\ClassPropertyWriteAttribute::$age is not readable.' => 21,
3636
];
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension;
4+
5+
class TemplateExtendsAttributeTest extends BaseAttributeTestCase
6+
{
7+
public function testClassTemplateExtendsAttribute(): void
8+
{
9+
$errors = $this->analyse(__DIR__ . '/data/TemplateExtends/ClassTemplateExtendsAttribute.php');
10+
$this->assertCount(0, $errors);
11+
}
12+
13+
public function testInvalidClassTemplateExtendsAttribute(): void
14+
{
15+
$errors = $this->analyse(__DIR__ . '/data/TemplateExtends/InvalidClassTemplateExtendsAttribute.php');
16+
17+
$expectedErrors = [
18+
'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends\InvalidClassTemplateExtendsAttributeChild extends generic class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends\InvalidClassTemplateExtendsAttribute but does not specify its types: T' => 13,
19+
'PHPDoc tag @template-extends has invalid value (): Unexpected token "\n ", expected type at offset 24' => 13,
20+
'Parameter #1 $class of attribute class PhpStaticAnalysis\Attributes\TemplateExtends constructor expects string, int given.' => 13,
21+
'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends\InvalidClassTemplateExtendsAttributeChild2 extends generic class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends\InvalidClassTemplateExtendsAttribute but does not specify its types: T' => 18,
22+
'PHPDoc tag @template-extends has invalid value (+5): Unexpected token "+5", expected type at offset 25' => 18,
23+
'Attribute class PhpStaticAnalysis\Attributes\TemplateExtends is not repeatable but is already present above the class.' => 24,
24+
'Attribute class PhpStaticAnalysis\Attributes\TemplateExtends does not have the property target.' => 27,
25+
];
26+
27+
$this->checkExpectedErrors($errors, $expectedErrors);
28+
}
29+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension;
4+
5+
class TemplateImplementsAttributeTest extends BaseAttributeTestCase
6+
{
7+
public function testInterfaceTemplateImplementsAttribute(): void
8+
{
9+
$errors = $this->analyse(__DIR__ . '/data/TemplateImplements/InterfaceTemplateImplementsAttribute.php');
10+
$this->assertCount(0, $errors);
11+
}
12+
13+
public function testInvalidInterfaceTemplateImplementsAttribute(): void
14+
{
15+
$errors = $this->analyse(__DIR__ . '/data/TemplateImplements/InvalidInterfaceTemplateImplementsAttribute.php');
16+
17+
$expectedErrors = [
18+
'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements\InvalidClassTemplateImplementsAttribute implements generic interface test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements\InvalidInterfaceTemplateImplementsAttribute but does not specify its types: T' => 13,
19+
'PHPDoc tag @template-implements has invalid value (): Unexpected token "\n ", expected type at offset 27' => 13,
20+
'Parameter #1 ...$interfaces of attribute class PhpStaticAnalysis\Attributes\TemplateImplements constructor expects string, int given.' => 13,
21+
'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements\InvalidClassTemplateImplementsAttribute2 implements generic interface test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements\InvalidInterfaceTemplateImplementsAttribute but does not specify its types: T' => 18,
22+
'PHPDoc tag @template-implements has invalid value (+5): Unexpected token "+5", expected type at offset 28' => 18,
23+
'Attribute class PhpStaticAnalysis\Attributes\TemplateImplements does not have the property target.' => 21,
24+
];
25+
26+
$this->checkExpectedErrors($errors, $expectedErrors);
27+
}
28+
}

tests/TemplateUseAttributeTest.php

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension;
4+
5+
class TemplateUseAttributeTest extends BaseAttributeTestCase
6+
{
7+
public function testTraitTemplateUseAttribute(): void
8+
{
9+
$errors = $this->analyse(__DIR__ . '/data/TemplateUse/TraitTemplateUseAttribute.php');
10+
$this->assertCount(0, $errors);
11+
}
12+
13+
public function testInvalidTraitTemplateUseAttribute(): void
14+
{
15+
$errors = $this->analyse(__DIR__ . '/data/TemplateUse/InvalidTraitTemplateUseAttribute.php');
16+
17+
$expectedErrors = [
18+
'Parameter #1 ...$traits of attribute class PhpStaticAnalysis\Attributes\TemplateUse constructor expects string, int given.' => 13,
19+
'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse\InvalidClassTemplateUseAttribute uses generic trait test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse\InvalidTraitTemplateUseAttribute but does not specify its types: T' => 16,
20+
'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse\InvalidClassTemplateUseAttribute2 uses generic trait test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse\InvalidTraitTemplateUseAttribute but does not specify its types: T' => 22,
21+
'Attribute class PhpStaticAnalysis\Attributes\TemplateUse does not have the property target.' => 24,
22+
];
23+
24+
$this->checkExpectedErrors($errors, $expectedErrors);
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends;
4+
5+
use PhpStaticAnalysis\Attributes\Template;
6+
use PhpStaticAnalysis\Attributes\TemplateExtends;
7+
8+
#[Template('T')]
9+
class ClassTemplateExtendsAttribute
10+
{
11+
}
12+
13+
#[TemplateExtends('ClassTemplateExtendsAttribute<int>')] // this class extends the base template
14+
class ClassTemplateExtendsAttributeChild extends ClassTemplateExtendsAttribute
15+
{
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends;
4+
5+
use PhpStaticAnalysis\Attributes\Template;
6+
use PhpStaticAnalysis\Attributes\TemplateExtends;
7+
8+
#[Template('T')]
9+
class InvalidClassTemplateExtendsAttribute
10+
{
11+
}
12+
13+
#[TemplateExtends(0)]
14+
class InvalidClassTemplateExtendsAttributeChild extends InvalidClassTemplateExtendsAttribute
15+
{
16+
}
17+
18+
#[TemplateExtends('+5')]
19+
class InvalidClassTemplateExtendsAttributeChild2 extends InvalidClassTemplateExtendsAttribute
20+
{
21+
}
22+
23+
#[TemplateExtends('InvalidClassTemplateExtendsAttribute<int>')]
24+
#[TemplateExtends('InvalidClassTemplateExtendsAttribute<int>')]
25+
class InvalidClassTemplateExtendsAttributeChild3 extends InvalidClassTemplateExtendsAttribute
26+
{
27+
#[TemplateExtends('InvalidClassTemplateExtendsAttribute<int>')]
28+
public string $name = '';
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements;
4+
5+
use PhpStaticAnalysis\Attributes\Template;
6+
use PhpStaticAnalysis\Attributes\TemplateImplements;
7+
8+
#[Template('T')]
9+
interface InterfaceTemplateImplementsAttribute
10+
{
11+
}
12+
13+
#[Template('T')]
14+
interface InterfaceTemplateImplementsAttribute2
15+
{
16+
}
17+
18+
#[Template('T')]
19+
interface InterfaceTemplateImplementsAttribute3
20+
{
21+
}
22+
23+
#[TemplateImplements('InterfaceTemplateImplementsAttribute<int>')] // this class implements the base interface
24+
#[TemplateImplements(
25+
'InterfaceTemplateImplementsAttribute2<int>',
26+
'InterfaceTemplateImplementsAttribute3<int>'
27+
)]
28+
class ClassTemplateImplementsAttribute implements InterfaceTemplateImplementsAttribute, InterfaceTemplateImplementsAttribute2, InterfaceTemplateImplementsAttribute3
29+
{
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements;
4+
5+
use PhpStaticAnalysis\Attributes\Template;
6+
use PhpStaticAnalysis\Attributes\TemplateImplements;
7+
8+
#[Template('T')]
9+
interface InvalidInterfaceTemplateImplementsAttribute
10+
{
11+
}
12+
13+
#[TemplateImplements(0)]
14+
class InvalidClassTemplateImplementsAttribute implements InvalidInterfaceTemplateImplementsAttribute
15+
{
16+
}
17+
18+
#[TemplateImplements('+5')]
19+
class InvalidClassTemplateImplementsAttribute2 implements InvalidInterfaceTemplateImplementsAttribute
20+
{
21+
#[TemplateImplements('InvalidInterfaceTemplateImplementsAttribute<int>')]
22+
public string $name = '';
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse;
4+
5+
use PhpStaticAnalysis\Attributes\Template;
6+
use PhpStaticAnalysis\Attributes\TemplateUse;
7+
8+
#[Template('T')]
9+
trait InvalidTraitTemplateUseAttribute
10+
{
11+
}
12+
13+
#[TemplateUse(0)]
14+
class InvalidClassTemplateUseAttribute
15+
{
16+
use InvalidTraitTemplateUseAttribute;
17+
}
18+
19+
#[TemplateUse('+5')]
20+
class InvalidClassTemplateUseAttribute2
21+
{
22+
use InvalidTraitTemplateUseAttribute;
23+
24+
#[TemplateUse('InvalidTraitTemplateUseAttribute<int>')]
25+
public string $name = '';
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse;
4+
5+
use PhpStaticAnalysis\Attributes\Template;
6+
use PhpStaticAnalysis\Attributes\TemplateUse;
7+
8+
#[Template('T')]
9+
trait TraitTemplateUseAttribute
10+
{
11+
}
12+
13+
#[Template('T')]
14+
trait TraitTemplateUseAttribute2
15+
{
16+
}
17+
18+
#[Template('T')]
19+
trait TraitTemplateUseAttribute3
20+
{
21+
}
22+
23+
#[TemplateUse('TraitTemplateUseAttribute<int>')] // this class uses the base trait
24+
#[TemplateUse(
25+
'TraitTemplateUseAttribute2<int>',
26+
'TraitTemplateUseAttribute3<int>'
27+
)]
28+
class ClassTemplateUseAttribute
29+
{
30+
use TraitTemplateUseAttribute;
31+
use TraitTemplateUseAttribute2, TraitTemplateUseAttribute3;
32+
}

0 commit comments

Comments
 (0)