Skip to content

Commit

Permalink
Add Assert, AssertIfTrue and AssertIfFalse attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-granados committed Sep 12, 2024
1 parent 377c5f1 commit b62b26c
Show file tree
Hide file tree
Showing 15 changed files with 520 additions and 30 deletions.
55 changes: 29 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion src/Provider/AttributeStatementProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use PhpStaticAnalysis\NodeVisitor\AttributeNodeVisitor;
use Psalm\Internal\Provider\StatementsProvider;
use Psalm\Progress\Progress;
use Webmozart\Assert\Assert;

class AttributeStatementProvider
{
Expand Down Expand Up @@ -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;
}
}
29 changes: 29 additions & 0 deletions tests/AssertAttributeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace test\PhpStaticAnalysis\PsalmPlugin;

class AssertAttributeTest extends BaseAttributeTestCase
{
public function testFunctionAssertAttribute(): void
{
$errors = $this->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,
]);
}
}
29 changes: 29 additions & 0 deletions tests/AssertIfFalseAttributeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace test\PhpStaticAnalysis\PsalmPlugin;

class AssertIfFalseAttributeTest extends BaseAttributeTestCase
{
public function testFunctionAssertIfFalseAttribute(): void
{
$errors = $this->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,
]);
}
}
29 changes: 29 additions & 0 deletions tests/AssertIfTrueAttributeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace test\PhpStaticAnalysis\PsalmPlugin;

class AssertIfTrueAttributeTest extends BaseAttributeTestCase
{
public function testFunctionAssertIfTrueAttribute(): void
{
$errors = $this->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,
]);
}
}
14 changes: 14 additions & 0 deletions tests/data/Assert/FunctionAssertAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace test\PhpStaticAnalysis\PsalmPlugin\data\Assert;

use Exception;
use PhpStaticAnalysis\Attributes\Assert;

#[Assert(name: 'string')]
function checkString(mixed $name): void
{
if (!is_string($name)) {
throw new Exception();
}
}
16 changes: 16 additions & 0 deletions tests/data/Assert/InvalidMethodAssertAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace test\PhpStaticAnalysis\PsalmPlugin\data\Assert;

use PhpStaticAnalysis\Attributes\Assert;

class InvalidMethodAssertAttribute
{
#[Assert(0)]
public function checkString(mixed $name): void
{
}

#[Assert(property: 'string')]
public string $property = '';
}
114 changes: 114 additions & 0 deletions tests/data/Assert/MethodAssertAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace test\PhpStaticAnalysis\PsalmPlugin\data\Assert;

use Exception;
use PhpStaticAnalysis\Attributes\Assert;

class MethodAssertAttribute
{
#[Assert(name: 'string')] // checks name is string
public function checkString(mixed $name): void
{
if (!is_string($name)) {
throw new Exception();
}
}

#[Assert(exception: Exception::class)]
public function checkException(mixed $exception): void
{
if (!$exception instanceof Exception) {
throw new Exception();
}
}

#[Assert('string $name')]
public function checkOtherString(mixed $name): void
{
if (!is_string($name)) {
throw new Exception();
}
}

/**
* @deprecated
*/
#[Assert(name: 'string')]
public function checkAnotherString(mixed $name): void
{
if (!is_string($name)) {
throw new Exception();
}
}

/**
* @assert int $name
*/
#[Assert(name: 'string')]
public function checkEvenMoreString(mixed $name): void
{
if (!is_string($name)) {
throw new Exception();
}
}

#[Assert(
name1: 'string',
name2: 'string'
)]
public function checkStrings(mixed $name1, mixed $name2): void
{
if (!is_string($name1) || !is_string($name2)) {
throw new Exception();
}
}

#[Assert(name1: 'string')]
#[Assert(name2: 'string')]
public function checkOtherStrings(mixed $name1, mixed $name2): void
{
if (!is_string($name1) || !is_string($name2)) {
throw new Exception();
}
}

/**
* @assert string $name
*/
public function checkMoreAndMoreString(mixed $name): void
{
if (!is_string($name)) {
throw new Exception();
}
}

public function checkStringInParam(
#[Assert('string')]
mixed $name
): void {
if (!is_string($name)) {
throw new Exception();
}
}

public function checkStringInParamWithName(
#[Assert(name: 'string')]
mixed $name
): void {
if (!is_string($name)) {
throw new Exception();
}
}

public function checkStringInTwoParams(
#[Assert('string')]
mixed $name1,
#[Assert('string')]
mixed $name2
): void {
if (!is_string($name1) || !is_string($name2)) {
throw new Exception();
}
}
}
12 changes: 12 additions & 0 deletions tests/data/AssertIfFalse/FunctionAssertIfFalseAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace test\PhpStaticAnalysis\PsalmPlugin\data\AssertIfFalse;

use Exception;
use PhpStaticAnalysis\Attributes\AssertIfFalse;

#[AssertIfFalse(name: 'string')]
function checkString(mixed $name): bool
{
return !is_string($name);
}
17 changes: 17 additions & 0 deletions tests/data/AssertIfFalse/InvalidMethodAssertIfFalseAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace test\PhpStaticAnalysis\PsalmPlugin\data\AssertIfFalse;

use PhpStaticAnalysis\Attributes\AssertIfFalse;

class InvalidMethodAssertIfFalseAttribute
{
#[AssertIfFalse(0)]
public function checkString(mixed $name): bool
{
return false;
}

#[AssertIfFalse(property: 'string')]
public string $property = '';
}
Loading

0 comments on commit b62b26c

Please sign in to comment.