From 69c03bbd1e30e50a48808882e0a2a786c1723cd1 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Sun, 25 Feb 2024 20:47:43 +0100 Subject: [PATCH] Add Throws attribute --- README.md | 51 +++++------ composer.json | 4 +- src/Parser/AttributeParser.php | 2 +- tests/ThrowsAttributeTest.php | 47 ++++++++++ tests/conf/throws.neon | 4 + tests/data/Mixin/ClassMixinAttribute.php | 6 +- tests/data/Param/MethodParamAttribute.php | 7 ++ .../data/ParamOut/MethodParamOutAttribute.php | 7 ++ .../data/Property/ClassPropertyAttribute.php | 2 + .../ClassPropertyReadAttribute.php | 2 + .../ClassPropertyWriteAttribute.php | 2 + .../TraitRequireExtendsAttribute.php | 2 +- .../TraitRequireImplementsAttribute.php | 6 +- tests/data/Returns/MethodReturnsAttribute.php | 7 ++ tests/data/Throws/FunctionThrowsAttribute.php | 15 ++++ .../Throws/InvalidMethodThrowsAttribute.php | 24 +++++ tests/data/Throws/MethodThrowsAttribute.php | 88 +++++++++++++++++++ tests/data/Type/PropertyTypeAttribute.php | 4 + 18 files changed, 245 insertions(+), 35 deletions(-) create mode 100644 tests/ThrowsAttributeTest.php create mode 100644 tests/conf/throws.neon create mode 100644 tests/data/Throws/FunctionThrowsAttribute.php create mode 100644 tests/data/Throws/InvalidMethodThrowsAttribute.php create mode 100644 tests/data/Throws/MethodThrowsAttribute.php diff --git a/README.md b/README.md index f6b1b5a..8c37236 100644 --- a/README.md +++ b/README.md @@ -94,31 +94,32 @@ 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` | -| [Impure](https://github.com/php-static-analysis/attributes/blob/main/doc/Impure.md) | `@impure` | -| [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` | -| [Pure](https://github.com/php-static-analysis/attributes/blob/main/doc/Pure.md) | `@pure` | -| [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` | +| Attribute | PHPDoc Annotations | +|-------------------------------------------------------------------------------------------------------------------|--------------------------------------| +| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` | +| [Impure](https://github.com/php-static-analysis/attributes/blob/main/doc/Impure.md) | `@impure` | +| [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` | +| [Pure](https://github.com/php-static-analysis/attributes/blob/main/doc/Pure.md) | `@pure` | +| [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` | +| [Throws](https://github.com/php-static-analysis/attributes/blob/main/doc/Throws.md) | `@throws` | +| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` | diff --git a/composer.json b/composer.json index f1bb957..8b755b6 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,8 @@ "prefer-stable": true, "require": { "php": ">=8.0", - "php-static-analysis/attributes": "^0.1.15 || dev-main", - "php-static-analysis/node-visitor": "^0.1.15 || dev-main", + "php-static-analysis/attributes": "^0.1.16 || dev-main", + "php-static-analysis/node-visitor": "^0.1.16 || dev-main", "phpstan/phpstan": "^1.8" }, "require-dev": { diff --git a/src/Parser/AttributeParser.php b/src/Parser/AttributeParser.php index 65119e6..82e2ec4 100644 --- a/src/Parser/AttributeParser.php +++ b/src/Parser/AttributeParser.php @@ -35,7 +35,7 @@ public function parseString(string $sourceCode): array private function traverseAst(array $ast): array { $traverser = new NodeTraverser(); - $nodeVisitor = new AttributeNodeVisitor('phpstan'); + $nodeVisitor = new AttributeNodeVisitor(AttributeNodeVisitor::TOOL_PHPSTAN); $traverser->addVisitor($nodeVisitor); $ast = $traverser->traverse($ast); diff --git a/tests/ThrowsAttributeTest.php b/tests/ThrowsAttributeTest.php new file mode 100644 index 0000000..ed4f363 --- /dev/null +++ b/tests/ThrowsAttributeTest.php @@ -0,0 +1,47 @@ +analyse(__DIR__ . '/data/Throws/MethodThrowsAttribute.php'); + $expectedErrors = [ + 'Method test\PhpStaticAnalysis\PHPStanExtension\data\Throws\MethodThrowsAttribute::countNoErrorName() has Exception in PHPDoc @throws tag but it\'s not thrown.' => 72, + ]; + + $this->checkExpectedErrors($errors, $expectedErrors); + } + + public function testFunctionThrowsAttribute(): void + { + $errors = $this->analyse(__DIR__ . '/data/Throws/FunctionThrowsAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testInvalidMethodThrowsAttribute(): void + { + $errors = $this->analyse(__DIR__ . '/data/Throws/InvalidMethodThrowsAttribute.php'); + + $expectedErrors = [ + 'PHPDoc tag @throws has invalid value (): Unexpected token "\n ", expected type at offset 14' => 10, + 'Parameter #1 ...$exceptions of attribute class PhpStaticAnalysis\Attributes\Throws constructor expects string, int given.' => 10, + 'Method test\PhpStaticAnalysis\PHPStanExtension\data\Throws\InvalidMethodThrowsAttribute::getOtherNameLength() has string in PHPDoc @throws tag but it\'s not thrown.' => 16, + 'PHPDoc tag @throws with type string is not subtype of Throwable' => 16, + 'Attribute class PhpStaticAnalysis\Attributes\Throws does not have the property target.' => 22, + ]; + + $this->checkExpectedErrors($errors, $expectedErrors); + } + + public static function getAdditionalConfigFiles(): array + { + return array_merge( + parent::getAdditionalConfigFiles(), + [ + __DIR__ . '/conf/throws.neon', + ] + ); + } +} diff --git a/tests/conf/throws.neon b/tests/conf/throws.neon new file mode 100644 index 0000000..b748ffe --- /dev/null +++ b/tests/conf/throws.neon @@ -0,0 +1,4 @@ +parameters: + exceptions: + check: + tooWideThrowType: true \ No newline at end of file diff --git a/tests/data/Mixin/ClassMixinAttribute.php b/tests/data/Mixin/ClassMixinAttribute.php index db47c9e..2d23f0b 100644 --- a/tests/data/Mixin/ClassMixinAttribute.php +++ b/tests/data/Mixin/ClassMixinAttribute.php @@ -19,10 +19,10 @@ class Another { } -#[Mixin('ClassMixinAttribute')] // this is the proxied class +#[Mixin(ClassMixinAttribute::class)] // this is the proxied class #[Mixin( - 'MyClass', - 'Another', + MyClass::class, + Another::class, )] class ClassMixinAttributeProxy { diff --git a/tests/data/Param/MethodParamAttribute.php b/tests/data/Param/MethodParamAttribute.php index 618c6c2..4fde00f 100644 --- a/tests/data/Param/MethodParamAttribute.php +++ b/tests/data/Param/MethodParamAttribute.php @@ -2,6 +2,7 @@ namespace test\PhpStaticAnalysis\PHPStanExtension\data\Param; +use Exception; use PhpStaticAnalysis\Attributes\Param; class MethodParamAttribute @@ -12,6 +13,12 @@ public function countNames(array $names): int return count($names); } + #[Param(exception: Exception::class)] + public function throwException($exception): void + { + throw $exception; + } + #[Param('string[] $names')] public function countUnnamedNames(array $names): int { diff --git a/tests/data/ParamOut/MethodParamOutAttribute.php b/tests/data/ParamOut/MethodParamOutAttribute.php index 07d7452..6124e6e 100644 --- a/tests/data/ParamOut/MethodParamOutAttribute.php +++ b/tests/data/ParamOut/MethodParamOutAttribute.php @@ -2,6 +2,7 @@ namespace test\PhpStaticAnalysis\PHPStanExtension\data\ParamOut; +use Exception; use PhpStaticAnalysis\Attributes\ParamOut; class MethodParamOutAttribute @@ -12,6 +13,12 @@ public function setNames(mixed &$names): void $names = 1; } + #[ParamOut(exception: Exception::class)] + public function setException(mixed &$exception): void + { + $exception = new Exception(); + } + #[ParamOut('int $names')] public function setUnnamedNames(mixed &$names): void { diff --git a/tests/data/Property/ClassPropertyAttribute.php b/tests/data/Property/ClassPropertyAttribute.php index 73c78ea..141809b 100644 --- a/tests/data/Property/ClassPropertyAttribute.php +++ b/tests/data/Property/ClassPropertyAttribute.php @@ -2,9 +2,11 @@ namespace test\PhpStaticAnalysis\PHPStanExtension\data\Property; +use Exception; use PhpStaticAnalysis\Attributes\Property; #[Property(name: 'string')] // the name of the user +#[Property(exception: Exception::class)] #[Property('int $age')] #[Property( index1: 'string[]', diff --git a/tests/data/PropertyRead/ClassPropertyReadAttribute.php b/tests/data/PropertyRead/ClassPropertyReadAttribute.php index ee8fc95..1fcb0fb 100644 --- a/tests/data/PropertyRead/ClassPropertyReadAttribute.php +++ b/tests/data/PropertyRead/ClassPropertyReadAttribute.php @@ -2,9 +2,11 @@ namespace test\PhpStaticAnalysis\PHPStanExtension\data\PropertyRead; +use Exception; use PhpStaticAnalysis\Attributes\PropertyRead; #[PropertyRead(name: 'string')] // cannot be written to +#[PropertyRead(exception: Exception::class)] #[PropertyRead('int $age')] #[PropertyRead( index1: 'string[]', diff --git a/tests/data/PropertyWrite/ClassPropertyWriteAttribute.php b/tests/data/PropertyWrite/ClassPropertyWriteAttribute.php index 0d86da0..accdb63 100644 --- a/tests/data/PropertyWrite/ClassPropertyWriteAttribute.php +++ b/tests/data/PropertyWrite/ClassPropertyWriteAttribute.php @@ -2,9 +2,11 @@ namespace test\PhpStaticAnalysis\PHPStanExtension\data\PropertyWrite; +use Exception; use PhpStaticAnalysis\Attributes\PropertyWrite; #[PropertyWrite(name: 'string')] // cannot be read +#[PropertyWrite(exception: Exception::class)] #[PropertyWrite('int $age')] #[PropertyWrite( index1: 'string[]', diff --git a/tests/data/RequireExtends/TraitRequireExtendsAttribute.php b/tests/data/RequireExtends/TraitRequireExtendsAttribute.php index 627ef7f..30339ef 100644 --- a/tests/data/RequireExtends/TraitRequireExtendsAttribute.php +++ b/tests/data/RequireExtends/TraitRequireExtendsAttribute.php @@ -4,7 +4,7 @@ use PhpStaticAnalysis\Attributes\RequireExtends; -#[RequireExtends('ClassRequireExtendsAttribute')] // the class using this trait needs to extend this class +#[RequireExtends(ClassRequireExtendsAttribute::class)] // the class using this trait needs to extend this class trait TraitRequireExtendsAttribute { } diff --git a/tests/data/RequireImplements/TraitRequireImplementsAttribute.php b/tests/data/RequireImplements/TraitRequireImplementsAttribute.php index c34f9bf..22ff9c3 100644 --- a/tests/data/RequireImplements/TraitRequireImplementsAttribute.php +++ b/tests/data/RequireImplements/TraitRequireImplementsAttribute.php @@ -4,10 +4,10 @@ use PhpStaticAnalysis\Attributes\RequireImplements; -#[RequireImplements('InterfaceRequireImplementsAttribute')] // the class that uses this trait needs to implement these interfaces +#[RequireImplements(InterfaceRequireImplementsAttribute::class)] // the class that uses this trait needs to implement these interfaces #[RequireImplements( - 'InterfaceRequireImplementsAttribute2', - 'InterfaceRequireImplementsAttribute3' + InterfaceRequireImplementsAttribute2::class, + InterfaceRequireImplementsAttribute3::class )] trait TraitRequireImplementsAttribute { diff --git a/tests/data/Returns/MethodReturnsAttribute.php b/tests/data/Returns/MethodReturnsAttribute.php index e3c9aba..e7124ca 100644 --- a/tests/data/Returns/MethodReturnsAttribute.php +++ b/tests/data/Returns/MethodReturnsAttribute.php @@ -2,6 +2,7 @@ namespace test\PhpStaticAnalysis\PHPStanExtension\data\Returns; +use Exception; use PhpStaticAnalysis\Attributes\Returns; class MethodReturnsAttribute @@ -12,6 +13,12 @@ public function getNames(): array return ['hello', 'world']; } + #[Returns(Exception::class)] + public function getException() + { + return new Exception(); + } + /** * @deprecated */ diff --git a/tests/data/Throws/FunctionThrowsAttribute.php b/tests/data/Throws/FunctionThrowsAttribute.php new file mode 100644 index 0000000..4de821d --- /dev/null +++ b/tests/data/Throws/FunctionThrowsAttribute.php @@ -0,0 +1,15 @@ +