diff --git a/README.md b/README.md index 282e69e..b44b6d0 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ These are the available attributes and their corresponding PHPDoc annotations: | Attribute | PHPDoc Annotations | |-------------------------------------------------------------------------------------------------------------------|--------------------------------------| +| [Assert](https://github.com/php-static-analysis/attributes/blob/main/doc/Assert.md) | `@assert` | | [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` | diff --git a/composer.json b/composer.json index 512bbfc..6d53369 100644 --- a/composer.json +++ b/composer.json @@ -24,9 +24,10 @@ "prefer-stable": true, "require": { "php": ">=8.0", - "php-static-analysis/attributes": "^0.3.0 || dev-main", - "php-static-analysis/node-visitor": "^0.3.0 || dev-main", - "phpstan/phpstan": "^1.8" + "php-static-analysis/attributes": "^0.3.1 || dev-main", + "php-static-analysis/node-visitor": "^0.3.1 || dev-main", + "phpstan/phpstan": "^1.8", + "webmozart/assert": "^1.11" }, "require-dev": { "php-static-analysis/psalm-plugin": "dev-main", diff --git a/src/Parser/AttributeParser.php b/src/Parser/AttributeParser.php index 82e2ec4..75be956 100644 --- a/src/Parser/AttributeParser.php +++ b/src/Parser/AttributeParser.php @@ -10,6 +10,7 @@ use PhpStaticAnalysis\Attributes\Param; use PhpStaticAnalysis\Attributes\Returns; use PhpStaticAnalysis\NodeVisitor\AttributeNodeVisitor; +use Webmozart\Assert\Assert; class AttributeParser implements Parser { @@ -39,7 +40,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..724216c --- /dev/null +++ b/tests/AssertAttributeTest.php @@ -0,0 +1,30 @@ +analyse(__DIR__ . '/data/Assert/MethodAssertAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testFunctionAssertAttribute(): void + { + $errors = $this->analyse(__DIR__ . '/data/Assert/FunctionAssertAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testInvalidMethodAssertAttribute(): void + { + $errors = $this->analyse(__DIR__ . '/data/Assert/InvalidMethodAssertAttribute.php'); + + $expectedErrors = [ + 'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\Assert constructor expects string, int given.' => 9, + 'Attribute class PhpStaticAnalysis\Attributes\Assert does not have the property target.' => 14, + ]; + + $this->checkExpectedErrors($errors, $expectedErrors); + } +} diff --git a/tests/AssertIfFalseAttributeTest.php b/tests/AssertIfFalseAttributeTest.php new file mode 100644 index 0000000..387a94e --- /dev/null +++ b/tests/AssertIfFalseAttributeTest.php @@ -0,0 +1,31 @@ +analyse(__DIR__ . '/data/AssertIfFalse/MethodAssertIfFalseAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testFunctionAssertIfFalseAttribute(): void + { + $errors = $this->analyse(__DIR__ . '/data/AssertIfFalse/FunctionAssertIfFalseAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testInvalidMethodAssertIfFalseAttribute(): void + { + $errors = $this->analyse(__DIR__ . '/data/AssertIfFalse/InvalidMethodAssertIfFalseAttribute.php'); + + $expectedErrors = [ + 'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\AssertIfFalse constructor expects string, int given.' => 9, + 'Attribute class PhpStaticAnalysis\Attributes\AssertIfFalse does not have the property target.' => 15, + ]; + + $this->checkExpectedErrors($errors, $expectedErrors); + } +} diff --git a/tests/AssertIfTrueAttributeTest.php b/tests/AssertIfTrueAttributeTest.php new file mode 100644 index 0000000..16bee61 --- /dev/null +++ b/tests/AssertIfTrueAttributeTest.php @@ -0,0 +1,31 @@ +analyse(__DIR__ . '/data/AssertIfTrue/MethodAssertIfTrueAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testFunctionAssertIfTrueAttribute(): void + { + $errors = $this->analyse(__DIR__ . '/data/AssertIfTrue/FunctionAssertIfTrueAttribute.php'); + $this->assertCount(0, $errors); + } + + public function testInvalidMethodAssertIfTrueAttribute(): void + { + $errors = $this->analyse(__DIR__ . '/data/AssertIfTrue/InvalidMethodAssertIfTrueAttribute.php'); + + $expectedErrors = [ + 'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\AssertIfTrue constructor expects string, int given.' => 9, + 'Attribute class PhpStaticAnalysis\Attributes\AssertIfTrue does not have the property target.' => 15, + ]; + + $this->checkExpectedErrors($errors, $expectedErrors); + } +} diff --git a/tests/data/Assert/FunctionAssertAttribute.php b/tests/data/Assert/FunctionAssertAttribute.php new file mode 100644 index 0000000..6c7d8f5 --- /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..ce0d939 --- /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); + } +}