Skip to content

Commit b62b26c

Browse files
Add Assert, AssertIfTrue and AssertIfFalse attributes
1 parent 377c5f1 commit b62b26c

15 files changed

+520
-30
lines changed

README.md

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -96,32 +96,35 @@ This extension works by interacting with the parser that Psalm uses to parse the
9696

9797
These are the available attributes and their corresponding PHPDoc annotations:
9898

99-
| Attribute | PHPDoc Annotations |
100-
|-----------------------------------------------------------------------------------------------------------------|--------------------|
101-
| [DefineType](https://github.com/php-static-analysis/attributes/blob/main/doc/DefineType.md) | `@type` |
102-
| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` |
103-
| [Immmutable](https://github.com/php-static-analysis/attributes/blob/main/doc/Immmutable.md) | `@immmutable` |
104-
| [ImportType](https://github.com/php-static-analysis/attributes/blob/main/doc/ImportType.md) | `@import-type` |
105-
| [Internal](https://github.com/php-static-analysis/attributes/blob/main/doc/Internal.md) | `@internal` |
106-
| [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |
107-
| [Method](https://github.com/php-static-analysis/attributes/blob/main/doc/Method.md) | `@method` |
108-
| [Mixin](https://github.com/php-static-analysis/attributes/blob/main/doc/Mixin.md) | `@mixin` |
109-
| [Param](https://github.com/php-static-analysis/attributes/blob/main/doc/Param.md) | `@param` |
110-
| [ParamOut](https://github.com/php-static-analysis/attributes/blob/main/doc/ParamOut.md) | `@param-out` |
111-
| [Property](https://github.com/php-static-analysis/attributes/blob/main/doc/Property.md) | `@property` `@var` |
112-
| [PropertyRead](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyRead.md) | `@property-read` |
113-
| [PropertyWrite](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyWrite.md) | `@property-write` |
114-
| [Pure](https://github.com/php-static-analysis/attributes/blob/main/doc/Pure.md) | `@pure` |
115-
| [RequireExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireExtends.md) | `@require-extends` |
116-
| [RequireImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireImplements.md) | `@require-implements` |
117-
| [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
118-
| [SelfOut](https://github.com/php-static-analysis/attributes/blob/main/doc/SelfOut.md) | `@self-out` `@this-out` |
119-
| [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
120-
| [TemplateCovariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateCovariant.md) | `@template-covariant` |
121-
| [TemplateExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateExtends.md) | `@extends` `@template-extends` |
122-
| [TemplateImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateImplements.md) | `@implements` `@template-implements` |
123-
| [TemplateUse](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateUse.md) | `@use` `@template-use` |
124-
| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` |
99+
| Attribute | PHPDoc Annotations |
100+
|-------------------------------------------------------------------------------------------------------------|--------------------------------------|
101+
| [Assert](https://github.com/php-static-analysis/attributes/blob/main/doc/Assert.md) | `@assert` |
102+
| [AssertIfFalse](https://github.com/php-static-analysis/attributes/blob/main/doc/AssertIfFalse.md) | `@assert-if-false` |
103+
| [AssertIfTrue](https://github.com/php-static-analysis/attributes/blob/main/doc/AssertIfTrue.md) | `@assert-if-true` |
104+
| [DefineType](https://github.com/php-static-analysis/attributes/blob/main/doc/DefineType.md) | `@type` |
105+
| [Deprecated](https://github.com/php-static-analysis/attributes/blob/main/doc/Deprecated.md) | `@deprecated` |
106+
| [Immmutable](https://github.com/php-static-analysis/attributes/blob/main/doc/Immmutable.md) | `@immmutable` |
107+
| [ImportType](https://github.com/php-static-analysis/attributes/blob/main/doc/ImportType.md) | `@import-type` |
108+
| [Internal](https://github.com/php-static-analysis/attributes/blob/main/doc/Internal.md) | `@internal` |
109+
| [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |
110+
| [Method](https://github.com/php-static-analysis/attributes/blob/main/doc/Method.md) | `@method` |
111+
| [Mixin](https://github.com/php-static-analysis/attributes/blob/main/doc/Mixin.md) | `@mixin` |
112+
| [Param](https://github.com/php-static-analysis/attributes/blob/main/doc/Param.md) | `@param` |
113+
| [ParamOut](https://github.com/php-static-analysis/attributes/blob/main/doc/ParamOut.md) | `@param-out` |
114+
| [Property](https://github.com/php-static-analysis/attributes/blob/main/doc/Property.md) | `@property` `@var` |
115+
| [PropertyRead](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyRead.md) | `@property-read` |
116+
| [PropertyWrite](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyWrite.md) | `@property-write` |
117+
| [Pure](https://github.com/php-static-analysis/attributes/blob/main/doc/Pure.md) | `@pure` |
118+
| [RequireExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireExtends.md) | `@require-extends` |
119+
| [RequireImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireImplements.md) | `@require-implements` |
120+
| [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
121+
| [SelfOut](https://github.com/php-static-analysis/attributes/blob/main/doc/SelfOut.md) | `@self-out` `@this-out` |
122+
| [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
123+
| [TemplateCovariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateCovariant.md) | `@template-covariant` |
124+
| [TemplateExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateExtends.md) | `@extends` `@template-extends` |
125+
| [TemplateImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateImplements.md) | `@implements` `@template-implements` |
126+
| [TemplateUse](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateUse.md) | `@use` `@template-use` |
127+
| [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` |
125128

126129
## Sponsor this project
127130

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@
2626
"require": {
2727
"php": ">=8.0",
2828
"ext-simplexml": "*",
29-
"php-static-analysis/attributes": "^0.3.0 || dev-main",
30-
"php-static-analysis/node-visitor": "^0.3.0 || dev-main",
31-
"vimeo/psalm": "^5"
29+
"php-static-analysis/attributes": "^0.3.1 || dev-main",
30+
"php-static-analysis/node-visitor": "^0.3.1 || dev-main",
31+
"vimeo/psalm": "^5",
32+
"webmozart/assert": "^1.11"
3233
},
3334
"require-dev": {
3435
"php-static-analysis/phpstan-extension": "dev-main",

src/Provider/AttributeStatementProvider.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PhpStaticAnalysis\NodeVisitor\AttributeNodeVisitor;
1313
use Psalm\Internal\Provider\StatementsProvider;
1414
use Psalm\Progress\Progress;
15+
use Webmozart\Assert\Assert;
1516

1617
class AttributeStatementProvider
1718
{
@@ -71,7 +72,7 @@ private function traverseAst(array $ast): array
7172
$traverser->addVisitor($nodeVisitor);
7273

7374
$ast = $traverser->traverse($ast);
74-
/** @var Stmt[] $ast */
75+
Assert::allIsInstanceOf($ast, Stmt::class);
7576
return $ast;
7677
}
7778
}

tests/AssertAttributeTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace test\PhpStaticAnalysis\PsalmPlugin;
6+
7+
class AssertAttributeTest extends BaseAttributeTestCase
8+
{
9+
public function testFunctionAssertAttribute(): void
10+
{
11+
$errors = $this->analyzeTestFile('/data/Assert/FunctionAssertAttribute.php');
12+
$this->assertCount(0, $errors);
13+
}
14+
15+
public function testMethodAssertAttribute(): void
16+
{
17+
$errors = $this->analyzeTestFile('/data/Assert/MethodAssertAttribute.php');
18+
$this->assertCount(0, $errors);
19+
}
20+
21+
public function testInvalidMethodAssertAttribute(): void
22+
{
23+
$errors = $this->analyzeTestFile('/data/Assert/InvalidMethodAssertAttribute.php');
24+
$this->checkExpectedErrors($errors,[
25+
'Argument 1 of PhpStaticAnalysis\Attributes\Assert::__construct expects string, but 0 provided' => 9,
26+
'Attribute Assert cannot be used on a property' => 14,
27+
]);
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace test\PhpStaticAnalysis\PsalmPlugin;
6+
7+
class AssertIfFalseAttributeTest extends BaseAttributeTestCase
8+
{
9+
public function testFunctionAssertIfFalseAttribute(): void
10+
{
11+
$errors = $this->analyzeTestFile('/data/AssertIfFalse/FunctionAssertIfFalseAttribute.php');
12+
$this->assertCount(0, $errors);
13+
}
14+
15+
public function testMethodAssertIfFalseAttribute(): void
16+
{
17+
$errors = $this->analyzeTestFile('/data/AssertIfFalse/MethodAssertIfFalseAttribute.php');
18+
$this->assertCount(0, $errors);
19+
}
20+
21+
public function testInvalidMethodAssertIfFalseAttribute(): void
22+
{
23+
$errors = $this->analyzeTestFile('/data/AssertIfFalse/InvalidMethodAssertIfFalseAttribute.php');
24+
$this->checkExpectedErrors($errors,[
25+
'Argument 1 of PhpStaticAnalysis\Attributes\AssertIfFalse::__construct expects string, but 0 provided' => 9,
26+
'Attribute AssertIfFalse cannot be used on a property' => 15,
27+
]);
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace test\PhpStaticAnalysis\PsalmPlugin;
6+
7+
class AssertIfTrueAttributeTest extends BaseAttributeTestCase
8+
{
9+
public function testFunctionAssertIfTrueAttribute(): void
10+
{
11+
$errors = $this->analyzeTestFile('/data/AssertIfTrue/FunctionAssertIfTrueAttribute.php');
12+
$this->assertCount(0, $errors);
13+
}
14+
15+
public function testMethodAssertIfTrueAttribute(): void
16+
{
17+
$errors = $this->analyzeTestFile('/data/AssertIfTrue/MethodAssertIfTrueAttribute.php');
18+
$this->assertCount(0, $errors);
19+
}
20+
21+
public function testInvalidMethodAssertIfTrueAttribute(): void
22+
{
23+
$errors = $this->analyzeTestFile('/data/AssertIfTrue/InvalidMethodAssertIfTrueAttribute.php');
24+
$this->checkExpectedErrors($errors,[
25+
'Argument 1 of PhpStaticAnalysis\Attributes\AssertIfTrue::__construct expects string, but 0 provided' => 9,
26+
'Attribute AssertIfTrue cannot be used on a property' => 15,
27+
]);
28+
}
29+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PsalmPlugin\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+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PsalmPlugin\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+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PsalmPlugin\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+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace test\PhpStaticAnalysis\PsalmPlugin\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+
}

0 commit comments

Comments
 (0)