Skip to content

Commit 3aaf8e1

Browse files
Add Assert, AssertIfTrue and AssertIfFalse attributes
1 parent 44f151d commit 3aaf8e1

15 files changed

+497
-4
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ These are the available attributes and their corresponding PHPDoc annotations:
9595

9696
| Attribute | PHPDoc Annotations |
9797
|-------------------------------------------------------------------------------------------------------------------|--------------------------------------|
98+
| [Assert](https://github.com/php-static-analysis/attributes/blob/main/doc/Assert.md) | `@assert` |
9899
| [DefineType](https://github.com/php-static-analysis/attributes/blob/main/doc/DefineType.md) | `@type` |
99100
| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` |
100101
| [Immmutable](https://github.com/php-static-analysis/attributes/blob/main/doc/Immmutable.md) | `@immmutable` |

composer.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@
2424
"prefer-stable": true,
2525
"require": {
2626
"php": ">=8.0",
27-
"php-static-analysis/attributes": "^0.3.0 || dev-main",
28-
"php-static-analysis/node-visitor": "^0.3.0 || dev-main",
29-
"phpstan/phpstan": "^1.8"
27+
"php-static-analysis/attributes": "^0.3.1 || dev-main",
28+
"php-static-analysis/node-visitor": "^0.3.1 || dev-main",
29+
"phpstan/phpstan": "^1.8",
30+
"webmozart/assert": "^1.11"
3031
},
3132
"require-dev": {
3233
"php-static-analysis/psalm-plugin": "dev-main",

src/Parser/AttributeParser.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PhpStaticAnalysis\Attributes\Param;
1111
use PhpStaticAnalysis\Attributes\Returns;
1212
use PhpStaticAnalysis\NodeVisitor\AttributeNodeVisitor;
13+
use Webmozart\Assert\Assert;
1314

1415
class AttributeParser implements Parser
1516
{
@@ -39,7 +40,7 @@ private function traverseAst(array $ast): array
3940
$traverser->addVisitor($nodeVisitor);
4041

4142
$ast = $traverser->traverse($ast);
42-
/** @var Stmt[] $ast */
43+
Assert::allIsInstanceOf($ast, Stmt::class);
4344
return $ast;
4445
}
4546
}

tests/AssertAttributeTest.php

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension;
4+
5+
class AssertAttributeTest extends BaseAttributeTestCase
6+
{
7+
public function testMethodAssertAttribute(): void
8+
{
9+
$errors = $this->analyse(__DIR__ . '/data/Assert/MethodAssertAttribute.php');
10+
$this->assertCount(0, $errors);
11+
}
12+
13+
public function testFunctionAssertAttribute(): void
14+
{
15+
$errors = $this->analyse(__DIR__ . '/data/Assert/FunctionAssertAttribute.php');
16+
$this->assertCount(0, $errors);
17+
}
18+
19+
public function testInvalidMethodAssertAttribute(): void
20+
{
21+
$errors = $this->analyse(__DIR__ . '/data/Assert/InvalidMethodAssertAttribute.php');
22+
23+
$expectedErrors = [
24+
'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\Assert constructor expects string, int given.' => 9,
25+
'Attribute class PhpStaticAnalysis\Attributes\Assert does not have the property target.' => 14,
26+
];
27+
28+
$this->checkExpectedErrors($errors, $expectedErrors);
29+
}
30+
}

tests/AssertIfFalseAttributeTest.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
4+
use test\PhpStaticAnalysis\PHPStanExtension\BaseAttributeTestCase;
5+
6+
class AssertIfFalseAttributeTest extends BaseAttributeTestCase
7+
{
8+
public function testMethodAssertIfFalseAttribute(): void
9+
{
10+
$errors = $this->analyse(__DIR__ . '/data/AssertIfFalse/MethodAssertIfFalseAttribute.php');
11+
$this->assertCount(0, $errors);
12+
}
13+
14+
public function testFunctionAssertIfFalseAttribute(): void
15+
{
16+
$errors = $this->analyse(__DIR__ . '/data/AssertIfFalse/FunctionAssertIfFalseAttribute.php');
17+
$this->assertCount(0, $errors);
18+
}
19+
20+
public function testInvalidMethodAssertIfFalseAttribute(): void
21+
{
22+
$errors = $this->analyse(__DIR__ . '/data/AssertIfFalse/InvalidMethodAssertIfFalseAttribute.php');
23+
24+
$expectedErrors = [
25+
'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\AssertIfFalse constructor expects string, int given.' => 9,
26+
'Attribute class PhpStaticAnalysis\Attributes\AssertIfFalse does not have the property target.' => 15,
27+
];
28+
29+
$this->checkExpectedErrors($errors, $expectedErrors);
30+
}
31+
}

tests/AssertIfTrueAttributeTest.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
4+
use test\PhpStaticAnalysis\PHPStanExtension\BaseAttributeTestCase;
5+
6+
class AssertIfTrueAttributeTest extends BaseAttributeTestCase
7+
{
8+
public function testMethodAssertIfTrueAttribute(): void
9+
{
10+
$errors = $this->analyse(__DIR__ . '/data/AssertIfTrue/MethodAssertIfTrueAttribute.php');
11+
$this->assertCount(0, $errors);
12+
}
13+
14+
public function testFunctionAssertIfTrueAttribute(): void
15+
{
16+
$errors = $this->analyse(__DIR__ . '/data/AssertIfTrue/FunctionAssertIfTrueAttribute.php');
17+
$this->assertCount(0, $errors);
18+
}
19+
20+
public function testInvalidMethodAssertIfTrueAttribute(): void
21+
{
22+
$errors = $this->analyse(__DIR__ . '/data/AssertIfTrue/InvalidMethodAssertIfTrueAttribute.php');
23+
24+
$expectedErrors = [
25+
'Parameter #1 ...$params of attribute class PhpStaticAnalysis\Attributes\AssertIfTrue constructor expects string, int given.' => 9,
26+
'Attribute class PhpStaticAnalysis\Attributes\AssertIfTrue does not have the property target.' => 15,
27+
];
28+
29+
$this->checkExpectedErrors($errors, $expectedErrors);
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\Assert;
4+
5+
use Exception;
6+
use PhpStaticAnalysis\Attributes\Assert;
7+
8+
#[Assert(name: 'string')]
9+
function checkString(mixed $name): void
10+
{
11+
if (!is_string($name)) {
12+
throw new Exception();
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\Assert;
4+
5+
use PhpStaticAnalysis\Attributes\Assert;
6+
7+
class InvalidMethodAssertAttribute
8+
{
9+
#[Assert(0)]
10+
public function checkString(mixed $name): void
11+
{
12+
}
13+
14+
#[Assert(property: 'string')]
15+
public string $property;
16+
}
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\Assert;
4+
5+
use Exception;
6+
use PhpStaticAnalysis\Attributes\Assert;
7+
8+
class MethodAssertAttribute
9+
{
10+
#[Assert(name: 'string')] // checks name is string
11+
public function checkString(mixed $name): void
12+
{
13+
if (!is_string($name)) {
14+
throw new Exception();
15+
}
16+
}
17+
18+
#[Assert(exception: Exception::class)]
19+
public function checkException(mixed $exception): void
20+
{
21+
if (!$exception instanceof Exception) {
22+
throw new Exception();
23+
}
24+
}
25+
26+
#[Assert('string $name')]
27+
public function checkOtherString(mixed $name): void
28+
{
29+
if (!is_string($name)) {
30+
throw new Exception();
31+
}
32+
}
33+
34+
/**
35+
* @deprecated
36+
*/
37+
#[Assert(name: 'string')]
38+
public function checkAnotherString(mixed $name): void
39+
{
40+
if (!is_string($name)) {
41+
throw new Exception();
42+
}
43+
}
44+
45+
/**
46+
* @assert int $name
47+
*/
48+
#[Assert(name: 'string')]
49+
public function checkEvenMoreString(mixed $name): void
50+
{
51+
if (!is_string($name)) {
52+
throw new Exception();
53+
}
54+
}
55+
56+
#[Assert(
57+
name1: 'string',
58+
name2: 'string'
59+
)]
60+
public function checkStrings(mixed $name1, mixed $name2): void
61+
{
62+
if (!is_string($name1) || !is_string($name2)) {
63+
throw new Exception();
64+
}
65+
}
66+
67+
#[Assert(name1: 'string')]
68+
#[Assert(name2: 'string')]
69+
public function checkOtherStrings(mixed $name1, mixed $name2): void
70+
{
71+
if (!is_string($name1) || !is_string($name2)) {
72+
throw new Exception();
73+
}
74+
}
75+
76+
/**
77+
* @assert string $name
78+
*/
79+
public function checkMoreAndMoreString(mixed $name): void
80+
{
81+
if (!is_string($name)) {
82+
throw new Exception();
83+
}
84+
}
85+
86+
public function checkStringInParam(
87+
#[Assert('string')]
88+
mixed $name
89+
): void {
90+
if (!is_string($name)) {
91+
throw new Exception();
92+
}
93+
}
94+
95+
public function checkStringInParamWithName(
96+
#[Assert(name: 'string')]
97+
mixed $name
98+
): void {
99+
if (!is_string($name)) {
100+
throw new Exception();
101+
}
102+
}
103+
104+
public function checkStringInTwoParams(
105+
#[Assert('string')]
106+
mixed $name1,
107+
#[Assert('string')]
108+
mixed $name2
109+
): void {
110+
if (!is_string($name1) || !is_string($name2)) {
111+
throw new Exception();
112+
}
113+
}
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\AssertIfFalse;
4+
5+
use Exception;
6+
use PhpStaticAnalysis\Attributes\AssertIfFalse;
7+
8+
#[AssertIfFalse(name: 'string')]
9+
function checkString(mixed $name): bool
10+
{
11+
return !is_string($name);
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PHPStanExtension\data\AssertIfFalse;
4+
5+
use PhpStaticAnalysis\Attributes\AssertIfFalse;
6+
7+
class InvalidMethodAssertIfFalseAttribute
8+
{
9+
#[AssertIfFalse(0)]
10+
public function checkString(mixed $name): bool
11+
{
12+
return false;
13+
}
14+
15+
#[AssertIfFalse(property: 'string')]
16+
public string $property;
17+
}

0 commit comments

Comments
 (0)