From b62b26ca6820c439b61edd3bf52e4690b8136c82 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Thu, 12 Sep 2024 19:37:15 +0200 Subject: [PATCH] Add Assert, AssertIfTrue and AssertIfFalse attributes --- README.md | 55 +++++---- composer.json | 7 +- src/Provider/AttributeStatementProvider.php | 3 +- tests/AssertAttributeTest.php | 29 +++++ tests/AssertIfFalseAttributeTest.php | 29 +++++ tests/AssertIfTrueAttributeTest.php | 29 +++++ tests/data/Assert/FunctionAssertAttribute.php | 14 +++ .../Assert/InvalidMethodAssertAttribute.php | 16 +++ tests/data/Assert/MethodAssertAttribute.php | 114 ++++++++++++++++++ .../FunctionAssertIfFalseAttribute.php | 12 ++ .../InvalidMethodAssertIfFalseAttribute.php | 17 +++ .../MethodAssertIfFalseAttribute.php | 98 +++++++++++++++ .../FunctionAssertIfTrueAttribute.php | 12 ++ .../InvalidMethodAssertIfTrueAttribute.php | 17 +++ .../MethodAssertIfTrueAttribute.php | 98 +++++++++++++++ 15 files changed, 520 insertions(+), 30 deletions(-) create mode 100644 tests/AssertAttributeTest.php create mode 100644 tests/AssertIfFalseAttributeTest.php create mode 100644 tests/AssertIfTrueAttributeTest.php create mode 100644 tests/data/Assert/FunctionAssertAttribute.php create mode 100644 tests/data/Assert/InvalidMethodAssertAttribute.php create mode 100644 tests/data/Assert/MethodAssertAttribute.php create mode 100644 tests/data/AssertIfFalse/FunctionAssertIfFalseAttribute.php create mode 100644 tests/data/AssertIfFalse/InvalidMethodAssertIfFalseAttribute.php create mode 100644 tests/data/AssertIfFalse/MethodAssertIfFalseAttribute.php create mode 100644 tests/data/AssertIfTrue/FunctionAssertIfTrueAttribute.php create mode 100644 tests/data/AssertIfTrue/InvalidMethodAssertIfTrueAttribute.php create mode 100644 tests/data/AssertIfTrue/MethodAssertIfTrueAttribute.php diff --git a/README.md b/README.md index 14e23d2..751d106 100644 --- a/README.md +++ b/README.md @@ -96,32 +96,35 @@ This extension works by interacting with the parser that Psalm uses to parse the These are the available attributes and their corresponding PHPDoc annotations: -| Attribute | PHPDoc Annotations | -|-----------------------------------------------------------------------------------------------------------------|--------------------| -| [DefineType](https://github.com/php-static-analysis/attributes/blob/main/doc/DefineType.md) | `@type` | -| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` | -| [Immmutable](https://github.com/php-static-analysis/attributes/blob/main/doc/Immmutable.md) | `@immmutable` | -| [ImportType](https://github.com/php-static-analysis/attributes/blob/main/doc/ImportType.md) | `@import-type` | -| [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` | -| [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 | +|-------------------------------------------------------------------------------------------------------------|--------------------------------------| +| [Assert](https://github.com/php-static-analysis/attributes/blob/main/doc/Assert.md) | `@assert` | +| [AssertIfFalse](https://github.com/php-static-analysis/attributes/blob/main/doc/AssertIfFalse.md) | `@assert-if-false` | +| [AssertIfTrue](https://github.com/php-static-analysis/attributes/blob/main/doc/AssertIfTrue.md) | `@assert-if-true` | +| [DefineType](https://github.com/php-static-analysis/attributes/blob/main/doc/DefineType.md) | `@type` | +| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` | +| [Immmutable](https://github.com/php-static-analysis/attributes/blob/main/doc/Immmutable.md) | `@immmutable` | +| [ImportType](https://github.com/php-static-analysis/attributes/blob/main/doc/ImportType.md) | `@import-type` | +| [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` | +| [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` | ## Sponsor this project diff --git a/composer.json b/composer.json index f1f98ce..8ea5c70 100644 --- a/composer.json +++ b/composer.json @@ -26,9 +26,10 @@ "require": { "php": ">=8.0", "ext-simplexml": "*", - "php-static-analysis/attributes": "^0.3.0 || dev-main", - "php-static-analysis/node-visitor": "^0.3.0 || dev-main", - "vimeo/psalm": "^5" + "php-static-analysis/attributes": "^0.3.1 || dev-main", + "php-static-analysis/node-visitor": "^0.3.1 || dev-main", + "vimeo/psalm": "^5", + "webmozart/assert": "^1.11" }, "require-dev": { "php-static-analysis/phpstan-extension": "dev-main", diff --git a/src/Provider/AttributeStatementProvider.php b/src/Provider/AttributeStatementProvider.php index 364d638..ad90685 100644 --- a/src/Provider/AttributeStatementProvider.php +++ b/src/Provider/AttributeStatementProvider.php @@ -12,6 +12,7 @@ use PhpStaticAnalysis\NodeVisitor\AttributeNodeVisitor; use Psalm\Internal\Provider\StatementsProvider; use Psalm\Progress\Progress; +use Webmozart\Assert\Assert; class AttributeStatementProvider { @@ -71,7 +72,7 @@ private function traverseAst(array $ast): array $traverser->addVisitor($nodeVisitor); $ast = $traverser->traverse($ast); - /** @var Stmt[] $ast */ + Assert::allIsInstanceOf($ast, Stmt::class); return $ast; } } diff --git a/tests/AssertAttributeTest.php b/tests/AssertAttributeTest.php new file mode 100644 index 0000000..0ecd052 --- /dev/null +++ b/tests/AssertAttributeTest.php @@ -0,0 +1,29 @@ +analyzeTestFile('/data/Assert/FunctionAssertAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testMethodAssertAttribute(): void + { + $errors = $this->analyzeTestFile('/data/Assert/MethodAssertAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testInvalidMethodAssertAttribute(): void + { + $errors = $this->analyzeTestFile('/data/Assert/InvalidMethodAssertAttribute.php'); + $this->checkExpectedErrors($errors,[ + 'Argument 1 of PhpStaticAnalysis\Attributes\Assert::__construct expects string, but 0 provided' => 9, + 'Attribute Assert cannot be used on a property' => 14, + ]); + } +} diff --git a/tests/AssertIfFalseAttributeTest.php b/tests/AssertIfFalseAttributeTest.php new file mode 100644 index 0000000..7a7557e --- /dev/null +++ b/tests/AssertIfFalseAttributeTest.php @@ -0,0 +1,29 @@ +analyzeTestFile('/data/AssertIfFalse/FunctionAssertIfFalseAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testMethodAssertIfFalseAttribute(): void + { + $errors = $this->analyzeTestFile('/data/AssertIfFalse/MethodAssertIfFalseAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testInvalidMethodAssertIfFalseAttribute(): void + { + $errors = $this->analyzeTestFile('/data/AssertIfFalse/InvalidMethodAssertIfFalseAttribute.php'); + $this->checkExpectedErrors($errors,[ + 'Argument 1 of PhpStaticAnalysis\Attributes\AssertIfFalse::__construct expects string, but 0 provided' => 9, + 'Attribute AssertIfFalse cannot be used on a property' => 15, + ]); + } +} diff --git a/tests/AssertIfTrueAttributeTest.php b/tests/AssertIfTrueAttributeTest.php new file mode 100644 index 0000000..ae43580 --- /dev/null +++ b/tests/AssertIfTrueAttributeTest.php @@ -0,0 +1,29 @@ +analyzeTestFile('/data/AssertIfTrue/FunctionAssertIfTrueAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testMethodAssertIfTrueAttribute(): void + { + $errors = $this->analyzeTestFile('/data/AssertIfTrue/MethodAssertIfTrueAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testInvalidMethodAssertIfTrueAttribute(): void + { + $errors = $this->analyzeTestFile('/data/AssertIfTrue/InvalidMethodAssertIfTrueAttribute.php'); + $this->checkExpectedErrors($errors,[ + 'Argument 1 of PhpStaticAnalysis\Attributes\AssertIfTrue::__construct expects string, but 0 provided' => 9, + 'Attribute AssertIfTrue cannot be used on a property' => 15, + ]); + } +} diff --git a/tests/data/Assert/FunctionAssertAttribute.php b/tests/data/Assert/FunctionAssertAttribute.php new file mode 100644 index 0000000..1279ec2 --- /dev/null +++ b/tests/data/Assert/FunctionAssertAttribute.php @@ -0,0 +1,14 @@ +name')] + public function checkOtherPropertyString(mixed $name): bool + { + return !is_string($name); + } + + /** + * @deprecated + */ + #[AssertIfFalse(name: 'string')] + public function checkAnotherString(mixed $name): bool + { + return !is_string($name); + } + + /** + * @assert int $name + */ + #[AssertIfFalse(name: 'string')] + public function checkEvenMoreString(mixed $name): bool + { + return !is_string($name); + } + + #[AssertIfFalse( + name1: 'string', + name2: 'string' + )] + public function checkStrings(mixed $name1, mixed $name2): bool + { + return !is_string($name1) || !is_string($name2); + } + + #[AssertIfFalse(name1: 'string')] + #[AssertIfFalse(name2: 'string')] + public function checkOtherStrings(mixed $name1, mixed $name2): bool + { + return !is_string($name1) || !is_string($name2); + } + + /** + * @assert string $name + */ + public function checkMoreAndMoreString(mixed $name): bool + { + return !is_string($name); + } + + public function checkStringInParam( + #[AssertIfFalse('string')] + mixed $name + ): bool { + return !is_string($name); + } + + public function checkStringInParamWithName( + #[AssertIfFalse(name: 'string')] + mixed $name + ): bool { + return !is_string($name); + } + + public function checkStringInTwoParams( + #[AssertIfFalse('string')] + mixed $name1, + #[AssertIfFalse('string')] + mixed $name2 + ): bool { + return !is_string($name1) || !is_string($name2); + } +} diff --git a/tests/data/AssertIfTrue/FunctionAssertIfTrueAttribute.php b/tests/data/AssertIfTrue/FunctionAssertIfTrueAttribute.php new file mode 100644 index 0000000..d771173 --- /dev/null +++ b/tests/data/AssertIfTrue/FunctionAssertIfTrueAttribute.php @@ -0,0 +1,12 @@ +name')] + public function checkOtherPropertyString(mixed $name): bool + { + return !is_string($name); + } + + /** + * @deprecated + */ + #[AssertIfTrue(name: 'string')] + public function checkAnotherString(mixed $name): bool + { + return !is_string($name); + } + + /** + * @assert int $name + */ + #[AssertIfTrue(name: 'string')] + public function checkEvenMoreString(mixed $name): bool + { + return !is_string($name); + } + + #[AssertIfTrue( + name1: 'string', + name2: 'string' + )] + public function checkStrings(mixed $name1, mixed $name2): bool + { + return !is_string($name1) || !is_string($name2); + } + + #[AssertIfTrue(name1: 'string')] + #[AssertIfTrue(name2: 'string')] + public function checkOtherStrings(mixed $name1, mixed $name2): bool + { + return !is_string($name1) || !is_string($name2); + } + + /** + * @assert string $name + */ + public function checkMoreAndMoreString(mixed $name): bool + { + return !is_string($name); + } + + public function checkStringInParam( + #[AssertIfTrue('string')] + mixed $name + ): bool { + return !is_string($name); + } + + public function checkStringInParamWithName( + #[AssertIfTrue(name: 'string')] + mixed $name + ): bool { + return !is_string($name); + } + + public function checkStringInTwoParams( + #[AssertIfTrue('string')] + mixed $name1, + #[AssertIfTrue('string')] + mixed $name2 + ): bool { + return !is_string($name1) || !is_string($name2); + } +}