Skip to content

Commit

Permalink
Add RequireExtends and RequireImplements attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-granados committed Feb 24, 2024
1 parent 3394d5d commit 31eac4f
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 22 deletions.
42 changes: 22 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,27 +94,29 @@ This extension works by interacting with the parser that PHPStan uses to parse t

These are the available attributes and their corresponding PHPDoc annotations:

| Attribute | PHPDoc Annotations |
|-------------------------------------------------------------------------------------------------------------------|--------------------------------------|
| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` |
| [Internal](https://github.com/php-static-analysis/attributes/blob/main/doc/Internal.md) | `@internal` |
| [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |
| [Method](https://github.com/php-static-analysis/attributes/blob/main/doc/Method.md) | `@method` |
| [Mixin](https://github.com/php-static-analysis/attributes/blob/main/doc/Mixin.md) | `@mixin` |
| [Param](https://github.com/php-static-analysis/attributes/blob/main/doc/Param.md) | `@param` |
| [ParamOut](https://github.com/php-static-analysis/attributes/blob/main/doc/ParamOut.md) | `@param-out` |
| [Property](https://github.com/php-static-analysis/attributes/blob/main/doc/Property.md) | `@property` `@var` |
| [PropertyRead](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyRead.md) | `@property-read` |
| [PropertyWrite](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyWrite.md) | `@property-write` |
| [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
| [SelfOut](https://github.com/php-static-analysis/attributes/blob/main/doc/SelfOut.md) | `@self-out` `@this-out` |
| [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
| Attribute | PHPDoc Annotations |
|------------------------------------------------------------------------------------------------------------|--------------------------------------|
| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` |
| [Internal](https://github.com/php-static-analysis/attributes/blob/main/doc/Internal.md) | `@internal` |
| [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |
| [Method](https://github.com/php-static-analysis/attributes/blob/main/doc/Method.md) | `@method` |
| [Mixin](https://github.com/php-static-analysis/attributes/blob/main/doc/Mixin.md) | `@mixin` |
| [Param](https://github.com/php-static-analysis/attributes/blob/main/doc/Param.md) | `@param` |
| [ParamOut](https://github.com/php-static-analysis/attributes/blob/main/doc/ParamOut.md) | `@param-out` |
| [Property](https://github.com/php-static-analysis/attributes/blob/main/doc/Property.md) | `@property` `@var` |
| [PropertyRead](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyRead.md) | `@property-read` |
| [PropertyWrite](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyWrite.md) | `@property-write` |
| [RequireExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireExtends.md) | `@require-extends` |
| [RequireImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireImplements.md) | `@require-implements` |
| [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
| [SelfOut](https://github.com/php-static-analysis/attributes/blob/main/doc/SelfOut.md) | `@self-out` `@this-out` |
| [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
| [TemplateContravariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateContravariant.md) | `@template-contravariant` |
| [TemplateCovariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateCovariant.md) | `@template-covariant` |
| [TemplateExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateExtends.md) | `@extends` `@template-extends` |
| [TemplateImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateImplements.md) | `@implements` `@template-implements` |
| [TemplateUse](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateUse.md) | `@use` `@template-use` |
| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` |
| [TemplateCovariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateCovariant.md) | `@template-covariant` |
| [TemplateExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateExtends.md) | `@extends` `@template-extends` |
| [TemplateImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateImplements.md) | `@implements` `@template-implements` |
| [TemplateUse](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateUse.md) | `@use` `@template-use` |
| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` |



Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"prefer-stable": true,
"require": {
"php": ">=8.0",
"php-static-analysis/attributes": "^0.1.13 || dev-main",
"php-static-analysis/node-visitor": "^0.1.13 || dev-main",
"php-static-analysis/attributes": "^0.1.14 || dev-main",
"php-static-analysis/node-visitor": "^0.1.14 || dev-main",
"phpstan/phpstan": "^1.8"
},
"require-dev": {
Expand Down
32 changes: 32 additions & 0 deletions tests/RequireExtendsAttributeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace test\PhpStaticAnalysis\PHPStanExtension;

class RequireExtendsAttributeTest extends BaseAttributeTestCase
{
public function testClassRequireExtendsAttribute(): void
{
$errors = $this->analyse(__DIR__ . '/data/RequireExtends/TraitRequireExtendsAttribute.php');
$expectedErrors = [
'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,
];

$this->checkExpectedErrors($errors, $expectedErrors);
}

public function testInvalidClassRequireExtendsAttribute(): void
{
$errors = $this->analyse(__DIR__ . '/data/RequireExtends/InvalidTraitRequireExtendsAttribute.php');

$expectedErrors = [
'PHPDoc tag @phpstan-require-extends has invalid value (): Unexpected token "\n ", expected type at offset 31' => 7,
'Parameter #1 $class of attribute class PhpStaticAnalysis\Attributes\RequireExtends constructor expects string, int given.' => 7,
'PHPDoc tag @phpstan-require-extends contains non-object type int.' => 12,
'PHPDoc tag @phpstan-require-extends can only be used once.' => 17,
'Attribute class PhpStaticAnalysis\Attributes\RequireExtends is not repeatable but is already present above the class.' => 18,
'Attribute class PhpStaticAnalysis\Attributes\RequireExtends does not have the property target.' => 21,
];

$this->checkExpectedErrors($errors, $expectedErrors);
}
}
30 changes: 30 additions & 0 deletions tests/RequireImplementsAttributeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace test\PhpStaticAnalysis\PHPStanExtension;

class RequireImplementsAttributeTest extends BaseAttributeTestCase
{
public function testClassRequireImplementsAttribute(): void
{
$errors = $this->analyse(__DIR__ . '/data/RequireImplements/TraitRequireImplementsAttribute.php');
$expectedErrors = [
'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,
];

$this->checkExpectedErrors($errors, $expectedErrors);
}

public function testInvalidClassRequireImplementsAttribute(): void
{
$errors = $this->analyse(__DIR__ . '/data/RequireImplements/InvalidTraitRequireImplementsAttribute.php');

$expectedErrors = [
'PHPDoc tag @phpstan-require-implements has invalid value (): Unexpected token "\n ", expected type at offset 34' => 7,
'Parameter #1 ...$interfaces of attribute class PhpStaticAnalysis\Attributes\RequireImplements constructor expects string, int given.' => 7,
'PHPDoc tag @phpstan-require-implements contains non-object type int.' => 12,
'Attribute class PhpStaticAnalysis\Attributes\RequireImplements does not have the property target.' => 15,
];

$this->checkExpectedErrors($errors, $expectedErrors);
}
}
32 changes: 32 additions & 0 deletions tests/data/RequireExtends/InvalidTraitRequireExtendsAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends;

use PhpStaticAnalysis\Attributes\RequireExtends;

#[RequireExtends(0)]
trait InvalidTraitRequireExtendsAttribute2
{
}

#[RequireExtends('+5')]
trait InvalidTraitRequireExtendsAttributeChild3
{
}

#[RequireExtends('InvalidClassRequireExtendsAttribute')]
#[RequireExtends('InvalidClassRequireExtendsAttribute')]
trait InvalidTraitRequireExtendsAttribute
{
#[RequireExtends('InvalidClassRequireExtendsAttribute')]
public string $name = '';
}

class InvalidClassRequireExtendsAttribute
{
}

class InvalidClassRequireExtendsAttributeChild extends InvalidClassRequireExtendsAttribute
{
use InvalidTraitRequireExtendsAttribute;
}
24 changes: 24 additions & 0 deletions tests/data/RequireExtends/TraitRequireExtendsAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends;

use PhpStaticAnalysis\Attributes\RequireExtends;

#[RequireExtends('ClassRequireExtendsAttribute')] // the class using this trait needs to extend this class
trait TraitRequireExtendsAttribute
{
}

class ClassRequireExtendsAttribute
{
}

class ClassRequireExtendsAttributeChild extends ClassRequireExtendsAttribute
{
use TraitRequireExtendsAttribute;
}

class ClassRequireExtendsAttributeChild2
{
use TraitRequireExtendsAttribute;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements;

use PhpStaticAnalysis\Attributes\RequireImplements;

#[RequireImplements(0)]
trait InvalidTraitRequireImplementsAttribute2
{
}

#[RequireImplements('+5')]
trait InvalidTraitRequireImplementsAttribute
{
#[RequireImplements('InvalidInterfaceRequireImplementsAttribute')]
public string $name = '';
}

interface InvalidInterfaceRequireImplementsAttribute
{
}

class InvalidClassRequireImplementsAttribute implements InvalidInterfaceRequireImplementsAttribute
{
use InvalidTraitRequireImplementsAttribute;
}
36 changes: 36 additions & 0 deletions tests/data/RequireImplements/TraitRequireImplementsAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements;

use PhpStaticAnalysis\Attributes\RequireImplements;

#[RequireImplements('InterfaceRequireImplementsAttribute')] // the class that uses this trait needs to implement these interfaces
#[RequireImplements(
'InterfaceRequireImplementsAttribute2',
'InterfaceRequireImplementsAttribute3'
)]
trait TraitRequireImplementsAttribute
{
}

interface InterfaceRequireImplementsAttribute
{
}

interface InterfaceRequireImplementsAttribute2
{
}

interface InterfaceRequireImplementsAttribute3
{
}

class ClassRequireImplementsAttribute implements InterfaceRequireImplementsAttribute, InterfaceRequireImplementsAttribute2, InterfaceRequireImplementsAttribute3
{
use TraitRequireImplementsAttribute;
}

class ClassRequireImplementsAttribute2 implements InterfaceRequireImplementsAttribute, InterfaceRequireImplementsAttribute2
{
use TraitRequireImplementsAttribute;
}

0 comments on commit 31eac4f

Please sign in to comment.