├── .github
├── FUNDING.yml
└── workflows
│ └── all_tests.yml
├── .gitignore
├── tests
├── conf
│ ├── throws.neon
│ ├── readonly.neon
│ └── deprecated.neon
├── data
│ ├── Internal
│ │ ├── FunctionInternalAttribute.php
│ │ ├── TraitInternalAttribute.php
│ │ ├── InterfaceInternalAttribute.php
│ │ ├── PropertyInternalAttribute.php
│ │ ├── InvalidMethodInternalAttribute.php
│ │ ├── MethodInternalAttribute.php
│ │ └── ClassInternalAttribute.php
│ ├── Deprecated
│ │ ├── FunctionDeprecatedAttribute.php
│ │ ├── TraitDeprecatedAttribute.php
│ │ ├── InterfaceDeprecatedAttribute.php
│ │ ├── ClassDeprecatedAttribute.php
│ │ ├── PropertyDeprecatedAttribute.php
│ │ ├── InvalidMethodDeprecatedAttribute.php
│ │ └── MethodDeprecatedAttribute.php
│ ├── Immutable
│ │ ├── TraitImmutableAttribute.php
│ │ ├── InterfaceImmutableAttribute.php
│ │ ├── ClassImmutableAttribute.php
│ │ └── InvalidClassImmutableAttribute.php
│ ├── Method
│ │ ├── TraitMethodAttribute.php
│ │ ├── InterfaceMethodAttribute.php
│ │ ├── InvalidClassMethodAttribute.php
│ │ └── ClassMethodAttribute.php
│ ├── Property
│ │ ├── TraitPropertyAttribute.php
│ │ ├── InterfacePropertyAttribute.php
│ │ ├── InvalidClassPropertyAttribute.php
│ │ └── ClassPropertyAttribute.php
│ ├── Pure
│ │ ├── FunctionPureAttribute.php
│ │ ├── InvalidMethodPureAttribute.php
│ │ └── MethodPureAttribute.php
│ ├── Mixin
│ │ ├── TraitMixinAttribute.php
│ │ ├── InterfaceMixinAttribute.php
│ │ ├── InvalidClassMixinAttribute.php
│ │ └── ClassMixinAttribute.php
│ ├── DefineType
│ │ ├── TraitDefineTypeAttribute.php
│ │ ├── InterfaceDefineTypeAttribute.php
│ │ ├── InvalidClassDefineTypeAttribute.php
│ │ └── ClassDefineTypeAttribute.php
│ ├── PropertyRead
│ │ ├── TraitPropertyReadAttribute.php
│ │ ├── InterfacePropertyReadAttribute.php
│ │ ├── InvalidClassPropertyReadAttribute.php
│ │ └── ClassPropertyReadAttribute.php
│ ├── Returns
│ │ ├── FunctionReturnsAttribute.php
│ │ ├── InvalidMethodReturnsAttribute.php
│ │ └── MethodReturnsAttribute.php
│ ├── Param
│ │ ├── FunctionParamAttribute.php
│ │ ├── InvalidMethodParamAttribute.php
│ │ └── MethodParamAttribute.php
│ ├── ParamOut
│ │ ├── FunctionParamOutAttribute.php
│ │ ├── InvalidMethodParamOutAttribute.php
│ │ └── MethodParamOutAttribute.php
│ ├── PropertyWrite
│ │ ├── TraitPropertyWriteAttribute.php
│ │ ├── InterfacePropertyWriteAttribute.php
│ │ ├── InvalidClassPropertyWriteAttribute.php
│ │ └── ClassPropertyWriteAttribute.php
│ ├── Impure
│ │ ├── FunctionImpureAttribute.php
│ │ ├── InvalidMethodImpureAttribute.php
│ │ └── MethodImpureAttribute.php
│ ├── AssertIfTrue
│ │ ├── FunctionAssertIfTrueAttribute.php
│ │ ├── InvalidMethodAssertIfTrueAttribute.php
│ │ └── MethodAssertIfTrueAttribute.php
│ ├── AssertIfFalse
│ │ ├── FunctionAssertIfFalseAttribute.php
│ │ ├── InvalidMethodAssertIfFalseAttribute.php
│ │ └── MethodAssertIfFalseAttribute.php
│ ├── Assert
│ │ ├── FunctionAssertAttribute.php
│ │ ├── InvalidMethodAssertAttribute.php
│ │ └── MethodAssertAttribute.php
│ ├── Throws
│ │ ├── FunctionThrowsAttribute.php
│ │ ├── InvalidMethodThrowsAttribute.php
│ │ └── MethodThrowsAttribute.php
│ ├── Template
│ │ ├── FunctionTemplateAttribute.php
│ │ ├── InterfaceTemplateAttribute.php
│ │ ├── TraitTemplateAttribute.php
│ │ ├── ClassTemplateAttribute.php
│ │ ├── InvalidMethodTemplateAttribute.php
│ │ └── MethodTemplateAttribute.php
│ ├── ImportType
│ │ ├── TraitImportTypeAttribute.php
│ │ ├── InterfaceImportTypeAttribute.php
│ │ ├── InvalidClassImportTypeAttribute.php
│ │ └── ClassImportTypeAttribute.php
│ ├── TemplateCovariant
│ │ ├── InterfaceTemplateCovariantAttribute.php
│ │ ├── InvalidClassTemplateCovariantAttribute.php
│ │ ├── TraitTemplateCovariantAttribute.php
│ │ └── ClassTemplateCovariantAttribute.php
│ ├── TemplateContravariant
│ │ ├── InterfaceTemplateContravariantAttribute.php
│ │ ├── InvalidClassTemplateContravariantAttribute.php
│ │ ├── TraitTemplateContravariantAttribute.php
│ │ └── ClassTemplateContravariantAttribute.php
│ ├── IsReadOnly
│ │ ├── PropertyIsReadOnlyAttribute.php
│ │ └── InvalidPropertyIsReadOnlyAttribute.php
│ ├── TemplateExtends
│ │ ├── ClassTemplateExtendsAttribute.php
│ │ └── InvalidClassTemplateExtendsAttribute.php
│ ├── RequireExtends
│ │ ├── TraitRequireExtendsAttribute.php
│ │ └── InvalidTraitRequireExtendsAttribute.php
│ ├── Type
│ │ ├── InvalidPropertyTypeAttribute.php
│ │ └── PropertyTypeAttribute.php
│ ├── TemplateUse
│ │ ├── InvalidTraitTemplateUseAttribute.php
│ │ └── TraitTemplateUseAttribute.php
│ ├── RequireImplements
│ │ ├── InvalidTraitRequireImplementsAttribute.php
│ │ └── TraitRequireImplementsAttribute.php
│ ├── TemplateImplements
│ │ ├── InvalidInterfaceTemplateImplementsAttribute.php
│ │ └── InterfaceTemplateImplementsAttribute.php
│ └── SelfOut
│ │ ├── InvalidMethodSelfOutAttribute.php
│ │ └── MethodSelfOutAttribute.php
├── SelfOutAttributeTest.php
├── ImpureAttributeTest.php
├── TypeAttributeTest.php
├── IsReadOnlyAttributeTest.php
├── AssertAttributeTest.php
├── PureAttributeTest.php
├── AssertIfTrueAttributeTest.php
├── AssertIfFalseAttributeTest.php
├── ReturnsAttributeTest.php
├── ParamAttributeTest.php
├── ParamOutAttributeTest.php
├── ImportTypeAttributeTest.php
├── MixinAttributeTest.php
├── TemplateUseAttributeTest.php
├── RequireImplementsAttributeTest.php
├── RequireExtendsAttributeTest.php
├── MethodAttributeTest.php
├── PropertyAttributeTest.php
├── ImmutableAttributeTest.php
├── TemplateExtendsAttributeTest.php
├── TemplateContravariantAttributeTest.php
├── BaseAttributeTestCase.php
├── DefineTypeAttributeTest.php
├── TemplateImplementsAttributeTest.php
├── TemplateCovariantAttributeTest.php
├── PropertyReadAttributeTest.php
├── PropertyWriteAttributeTest.php
├── InternalAttributeTest.php
├── ThrowsAttributeTest.php
├── TemplateAttributeTest.php
└── DeprecatedAttributeTest.php
├── ecs.php
├── extension.neon
├── phpstan.neon
├── psalm.xml
├── phpunit.xml
├── LICENSE
├── src
└── Parser
│ └── AttributeParser.php
├── composer.json
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [carlos-granados]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | composer.lock
3 | .idea
4 | .phpunit.cache
5 |
--------------------------------------------------------------------------------
/tests/conf/throws.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | exceptions:
3 | check:
4 | tooWideThrowType: true
--------------------------------------------------------------------------------
/tests/conf/readonly.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | featureToggles:
3 | readOnlyByPhpDoc: true
4 |
--------------------------------------------------------------------------------
/tests/conf/deprecated.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - ../../vendor/phpstan/phpstan-deprecation-rules/rules.neon
3 |
--------------------------------------------------------------------------------
/tests/data/Internal/FunctionInternalAttribute.php:
--------------------------------------------------------------------------------
1 | withPaths([
10 | __DIR__ . '/src',
11 | __DIR__ . '/tests',
12 | ])
13 | ->withPreparedSets(
14 | psr12: true,
15 | );
16 |
--------------------------------------------------------------------------------
/tests/data/Assert/FunctionAssertAttribute.php:
--------------------------------------------------------------------------------
1 | name = 'John';
15 |
--------------------------------------------------------------------------------
/tests/data/Throws/FunctionThrowsAttribute.php:
--------------------------------------------------------------------------------
1 | name = 'Mike';
15 | }
16 | }
17 |
18 | $p = new PropertyIsReadOnlyAttribute();
19 | $p->name = 'John';
20 |
--------------------------------------------------------------------------------
/tests/data/Template/TraitTemplateAttribute.php:
--------------------------------------------------------------------------------
1 | ')] // this class extends the base template
14 | class ClassTemplateExtendsAttributeChild extends ClassTemplateExtendsAttribute
15 | {
16 | }
17 |
--------------------------------------------------------------------------------
/tests/data/TemplateCovariant/TraitTemplateCovariantAttribute.php:
--------------------------------------------------------------------------------
1 | age = 7;
22 |
--------------------------------------------------------------------------------
/tests/data/TemplateContravariant/TraitTemplateContravariantAttribute.php:
--------------------------------------------------------------------------------
1 | age;
22 |
--------------------------------------------------------------------------------
/tests/data/Deprecated/InvalidMethodDeprecatedAttribute.php:
--------------------------------------------------------------------------------
1 | ')]
25 | public string $name = '';
26 | }
27 |
--------------------------------------------------------------------------------
/tests/data/IsReadOnly/InvalidPropertyIsReadOnlyAttribute.php:
--------------------------------------------------------------------------------
1 | invalidProperty = 'Mike';
25 | $this->otherInvalidProperty = 'John';
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/data/Pure/MethodPureAttribute.php:
--------------------------------------------------------------------------------
1 | returnDeprecated();
32 |
--------------------------------------------------------------------------------
/tests/data/ParamOut/InvalidMethodParamOutAttribute.php:
--------------------------------------------------------------------------------
1 | name;
25 | $bar = $class->age;
26 | $indexes = $class->index1 + $class->index2;
27 |
--------------------------------------------------------------------------------
/tests/data/Param/InvalidMethodParamAttribute.php:
--------------------------------------------------------------------------------
1 | $name = $value;
20 | }
21 | }
22 |
23 | $class = new ClassPropertyWriteAttribute();
24 | $class->name = 'John';
25 | $class->age = 7;
26 | $class->index1 = [];
27 | $class->index2 = [];
28 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/tests/data/Mixin/ClassMixinAttribute.php:
--------------------------------------------------------------------------------
1 | $name(...$arguments);
32 | }
33 | }
34 |
35 | $proxy = new ClassMixinAttributeProxy();
36 | $proxy->proxied();
37 |
--------------------------------------------------------------------------------
/tests/data/RequireImplements/InvalidTraitRequireImplementsAttribute.php:
--------------------------------------------------------------------------------
1 | ')]
22 | public string $name = '';
23 | }
24 |
--------------------------------------------------------------------------------
/tests/data/Impure/MethodImpureAttribute.php:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | tests
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/data/Template/InvalidMethodTemplateAttribute.php:
--------------------------------------------------------------------------------
1 | ')] // this class uses the base trait
24 | #[TemplateUse(
25 | 'TraitTemplateUseAttribute2',
26 | 'TraitTemplateUseAttribute3'
27 | )]
28 | class ClassTemplateUseAttribute
29 | {
30 | use TraitTemplateUseAttribute;
31 | use TraitTemplateUseAttribute2, TraitTemplateUseAttribute3;
32 | }
33 |
--------------------------------------------------------------------------------
/tests/data/SelfOut/InvalidMethodSelfOutAttribute.php:
--------------------------------------------------------------------------------
1 | ')]
22 | #[SelfOut('self')]
23 | public function addMore($item): void
24 | {
25 | }
26 |
27 | #[SelfOut('self')]
28 | public string $property;
29 | }
30 |
--------------------------------------------------------------------------------
/tests/data/Returns/InvalidMethodReturnsAttribute.php:
--------------------------------------------------------------------------------
1 | badFunction();
32 |
--------------------------------------------------------------------------------
/tests/data/Property/ClassPropertyAttribute.php:
--------------------------------------------------------------------------------
1 | $name = $value;
28 | }
29 | }
30 |
31 | $class = new ClassPropertyAttribute();
32 | $foo = $class->name;
33 | $class->age = 7;
34 | $indexes = $class->index1 + $class->index2;
35 |
--------------------------------------------------------------------------------
/tests/data/Method/ClassMethodAttribute.php:
--------------------------------------------------------------------------------
1 | getString();
28 | $class->setString($foo);
29 | $bar = ClassMethodAttribute::staticGetter();
30 |
--------------------------------------------------------------------------------
/tests/data/RequireExtends/InvalidTraitRequireExtendsAttribute.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/SelfOut/MethodSelfOutAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInvalidMethodSelfOutAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/SelfOut/InvalidMethodSelfOutAttribute.php');
16 |
17 | $expectedErrors = [
18 | 'PHPDoc tag @phpstan-self-out has invalid value (): Unexpected token "\n ", expected type at offset 75 on line 4' => 15,
19 | 'Attribute class PhpStaticAnalysis\Attributes\SelfOut does not have the property target.' => 27,
20 | ];
21 |
22 | $this->checkExpectedErrors($errors, $expectedErrors);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/data/Internal/ClassInternalAttribute.php:
--------------------------------------------------------------------------------
1 | myFunction();
25 | }
26 | }
27 |
28 | namespace newNamespace\other;
29 |
30 | class otherClass
31 | {
32 | public function otherFunction(): void
33 | {
34 | $class = new \test\PhpStaticAnalysis\PHPStanExtension\data\Internal\ClassInternalAttribute();
35 |
36 | $class->myFunction();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/data/ImportType/ClassImportTypeAttribute.php:
--------------------------------------------------------------------------------
1 | ')] // this class implements the base interface
24 | #[TemplateImplements(
25 | 'InterfaceTemplateImplementsAttribute2',
26 | 'InterfaceTemplateImplementsAttribute3'
27 | )]
28 | class ClassTemplateImplementsAttribute implements InterfaceTemplateImplementsAttribute, InterfaceTemplateImplementsAttribute2, InterfaceTemplateImplementsAttribute3
29 | {
30 | }
31 |
--------------------------------------------------------------------------------
/tests/data/TemplateExtends/InvalidClassTemplateExtendsAttribute.php:
--------------------------------------------------------------------------------
1 | ')]
24 | #[TemplateExtends('InvalidClassTemplateExtendsAttribute')]
25 | class InvalidClassTemplateExtendsAttributeChild3 extends InvalidClassTemplateExtendsAttribute
26 | {
27 | #[TemplateExtends('InvalidClassTemplateExtendsAttribute')]
28 | public string $name = '';
29 | }
30 |
--------------------------------------------------------------------------------
/tests/ImpureAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Impure/MethodImpureAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testFunctionImpureAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Impure/FunctionImpureAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testInvalidMethodImpureAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Impure/InvalidMethodImpureAttribute.php');
22 |
23 | $expectedErrors = [
24 | 'Attribute class PhpStaticAnalysis\Attributes\Impure does not have the property target.' => 11,
25 | ];
26 |
27 | $this->checkExpectedErrors($errors, $expectedErrors);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/TypeAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Type/PropertyTypeAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInvalidPropertyTypeAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Type/InvalidPropertyTypeAttribute.php');
16 |
17 | $expectedErrors = [
18 | 'PHPDoc tag @var has invalid value (): Unexpected token "\n ", expected type at offset 11 on line 2' => 10,
19 | 'PHPDoc tag @var has invalid value ($a + $b): Unexpected token "$a", expected type at offset 12 on line 2' => 20,
20 | 'Attribute class PhpStaticAnalysis\Attributes\Type does not have the parameter target.' => 23,
21 | ];
22 |
23 | $this->checkExpectedErrors($errors, $expectedErrors);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/IsReadOnlyAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/IsReadOnly/PropertyIsReadOnlyAttribute.php');
10 | $expectedErrors = [
11 | '@readonly property test\PhpStaticAnalysis\PHPStanExtension\data\IsReadOnly\PropertyIsReadOnlyAttribute::$name is assigned outside of its declaring class.' => 19,
12 | ];
13 |
14 | $this->checkExpectedErrors($errors, $expectedErrors);
15 | }
16 |
17 | public function testInvalidPropertyIsReadOnlyAttribute(): void
18 | {
19 | $errors = $this->analyse(__DIR__ . '/data/IsReadOnly/InvalidPropertyIsReadOnlyAttribute.php');
20 |
21 | $expectedErrors = [
22 | 'Attribute class PhpStaticAnalysis\Attributes\IsReadOnly does not have the method target.' => 16,
23 | ];
24 |
25 | $this->checkExpectedErrors($errors, $expectedErrors);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/data/Returns/MethodReturnsAttribute.php:
--------------------------------------------------------------------------------
1 | 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 | 'PHPDoc tag @phpstan-assert has invalid value (): Unexpected token "\n ", expected type at offset 22 on line 2' => 10,
25 | 'Attribute class PhpStaticAnalysis\Attributes\Assert does not have the property target.' => 14,
26 | ];
27 |
28 | $this->checkExpectedErrors($errors, $expectedErrors);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/PureAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Pure/MethodPureAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testFunctionPureAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Pure/FunctionPureAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testInvalidMethodPureAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Pure/InvalidMethodPureAttribute.php');
22 |
23 | $expectedErrors = [
24 | 'Attribute class PhpStaticAnalysis\Attributes\Pure does not have the property target.' => 11,
25 | 'Method test\PhpStaticAnalysis\PHPStanExtension\data\Pure\InvalidMethodPureAttribute::getMoreName() is marked as pure but returns void.' => 14,
26 | ];
27 |
28 | $this->checkExpectedErrors($errors, $expectedErrors);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/data/RequireImplements/TraitRequireImplementsAttribute.php:
--------------------------------------------------------------------------------
1 | ')]
26 | function getClassTemplateContravariantAttributeConsumer(): Consumer
27 | {
28 | return new Consumer();
29 | }
30 |
31 | #[Param(consumer: 'Consumer')]
32 | function someFunctionThatConsumes(Consumer $consumer): void
33 | {
34 | $consumer->consume(new ClassTemplateContravariantAttributeChild());
35 | }
36 |
37 | someFunctionThatConsumes(getClassTemplateContravariantAttributeConsumer());
38 |
--------------------------------------------------------------------------------
/tests/data/SelfOut/MethodSelfOutAttribute.php:
--------------------------------------------------------------------------------
1 | ')] // we specify the new type
15 | public function add($item): void
16 | {
17 | }
18 |
19 | /**
20 | * @deprecated
21 | */
22 | #[Template('TItemValue')]
23 | #[Param(item: 'TItemValue')]
24 | #[SelfOut('self')]
25 | public function addMore($item): void
26 | {
27 | }
28 |
29 | /**
30 | * @self-out self
31 | */
32 | #[Template('TItemValue')]
33 | #[Param(item: 'TItemValue')]
34 | #[SelfOut('self')]
35 | public function addEvenMore($item): void
36 | {
37 | }
38 |
39 | /**
40 | * @self-out self
41 | */
42 | #[Template('TItemValue')]
43 | #[Param(item: 'TItemValue')]
44 | public function addMoreAndMore($item): void
45 | {
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/AssertIfTrueAttributeTest.php:
--------------------------------------------------------------------------------
1 | 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 | 'PHPDoc tag @phpstan-assert-if-true has invalid value (): Unexpected token "\n ", expected type at offset 30 on line 2' => 10,
26 | 'Attribute class PhpStaticAnalysis\Attributes\AssertIfTrue does not have the property target.' => 15,
27 | ];
28 |
29 | $this->checkExpectedErrors($errors, $expectedErrors);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/AssertIfFalseAttributeTest.php:
--------------------------------------------------------------------------------
1 | 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 | 'PHPDoc tag @phpstan-assert-if-false has invalid value (): Unexpected token "\n ", expected type at offset 31 on line 2' => 10,
26 | 'Attribute class PhpStaticAnalysis\Attributes\AssertIfFalse does not have the property target.' => 15,
27 | ];
28 |
29 | $this->checkExpectedErrors($errors, $expectedErrors);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/ReturnsAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Returns/MethodReturnsAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testFunctionReturnsAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Returns/FunctionReturnsAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testInvalidMethodReturnsAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Returns/InvalidMethodReturnsAttribute.php');
22 |
23 | $expectedErrors = [
24 | 'PHPDoc tag @return has invalid value (): Unexpected token "\n ", expected type at offset 14 on line 2' => 10,
25 | 'PHPDoc tag @return has invalid value ($a + $b): Unexpected token "$a", expected type at offset 15 on line 2' => 29,
26 | 'Attribute class PhpStaticAnalysis\Attributes\Returns does not have the property target.' => 34,
27 | ];
28 |
29 | $this->checkExpectedErrors($errors, $expectedErrors);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/data/TemplateCovariant/ClassTemplateCovariantAttribute.php:
--------------------------------------------------------------------------------
1 | className();
29 | return $object;
30 | }
31 | }
32 |
33 | #[Returns('Creator')]
34 | function getClassTemplateCovariantAttributeCreator(): Creator
35 | {
36 | return new Creator(ClassTemplateCovariantAttributeChild::class);
37 | }
38 |
39 | #[Param(creator: 'Creator')]
40 | function someFunctionThatCreates(Creator $creator): void
41 | {
42 | $creator->create();
43 | }
44 |
45 | someFunctionThatCreates(getClassTemplateCovariantAttributeCreator());
46 |
--------------------------------------------------------------------------------
/src/Parser/AttributeParser.php:
--------------------------------------------------------------------------------
1 | parser->parseFile($file);
25 | return $this->traverseAst($ast);
26 | }
27 |
28 | public function parseString(string $sourceCode): array
29 | {
30 | $ast = $this->parser->parseString($sourceCode);
31 | return $this->traverseAst($ast);
32 | }
33 |
34 | #[Param(ast: 'Stmt[]')]
35 | #[Returns('Stmt[]')]
36 | private function traverseAst(array $ast): array
37 | {
38 | $traverser = new NodeTraverser();
39 | $nodeVisitor = new AttributeNodeVisitor(AttributeNodeVisitor::TOOL_PHPSTAN);
40 | $traverser->addVisitor($nodeVisitor);
41 |
42 | $ast = $traverser->traverse($ast);
43 | Assert::allIsInstanceOf($ast, Stmt::class);
44 | return $ast;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/ParamAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Param/MethodParamAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testFunctionParamAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Param/FunctionParamAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testInvalidMethodParamAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Param/InvalidMethodParamAttribute.php');
22 |
23 | $expectedErrors = [
24 | 'PHPDoc tag @param has invalid value (): Unexpected token "\n ", expected type at offset 13 on line 2' => 10,
25 | 'PHPDoc tag @param has invalid value (string): Unexpected token "\n ", expected variable at offset 20 on line 2' => 16,
26 | 'PHPDoc tag @param has invalid value (count($a) $name): Unexpected token "(", expected variable at offset 19 on line 2' => 22,
27 | 'Attribute class PhpStaticAnalysis\Attributes\Param does not have the property target.' => 27,
28 | ];
29 |
30 | $this->checkExpectedErrors($errors, $expectedErrors);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/ParamOutAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/ParamOut/MethodParamOutAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testFunctionParamOutAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/ParamOut/FunctionParamOutAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testInvalidMethodParamOutAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/ParamOut/InvalidMethodParamOutAttribute.php');
22 |
23 | $expectedErrors = [
24 | 'PHPDoc tag @param-out has invalid value (): Unexpected token "\n ", expected type at offset 17 on line 2' => 10,
25 | 'PHPDoc tag @param-out has invalid value (string): Unexpected token "\n ", expected variable at offset 24 on line 2' => 16,
26 | 'PHPDoc tag @param-out has invalid value (count($a) $name): Unexpected token "(", expected variable at offset 23 on line 2' => 22,
27 | 'Attribute class PhpStaticAnalysis\Attributes\ParamOut does not have the property target.' => 27,
28 | ];
29 |
30 | $this->checkExpectedErrors($errors, $expectedErrors);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/ImportTypeAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/ImportType/ClassImportTypeAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInterfaceImportTypeAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/ImportType/InterfaceImportTypeAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testTraitImportTypeAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/ImportType/TraitImportTypeAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testInvalidClassImportTypeAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/ImportType/InvalidClassImportTypeAttribute.php');
28 |
29 | $expectedErrors = [
30 | 'PHPDoc tag @phpstan-import-type has invalid value (Unexpected token "(", expected \'*/\' at offset 98 on line 4): Unexpected token "(", expected \'*/\' at offset 98 on line 4' => 11,
31 | 'Attribute class PhpStaticAnalysis\Attributes\ImportType does not have the method target.' => 13,
32 | ];
33 |
34 | $this->checkExpectedErrors($errors, $expectedErrors);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/MixinAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Mixin/ClassMixinAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInterfaceMixinAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Mixin/InterfaceMixinAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testTraitMixinAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Mixin/TraitMixinAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testInvalidClassMixinAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/Mixin/InvalidClassMixinAttribute.php');
28 |
29 | $expectedErrors = [
30 | 'PHPDoc tag @mixin has invalid value (): Unexpected token "\n * ", expected type at offset 13 on line 2' => 8,
31 | 'PHPDoc tag @mixin has invalid value (count($a)): Unexpected token "(", expected TOKEN_HORIZONTAL_WS at offset 29 on line 3' => 9,
32 | 'Attribute class PhpStaticAnalysis\Attributes\Mixin does not have the method target.' => 11,
33 | ];
34 |
35 | $this->checkExpectedErrors($errors, $expectedErrors);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/TemplateUseAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/TemplateUse/TraitTemplateUseAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInvalidTraitTemplateUseAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/TemplateUse/InvalidTraitTemplateUseAttribute.php');
16 |
17 | $expectedErrors = [
18 | 'Parameter #1 ...$traits of attribute class PhpStaticAnalysis\Attributes\TemplateUse constructor expects string, int given.' => 13,
19 | 'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse\InvalidClassTemplateUseAttribute uses generic trait test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse\InvalidTraitTemplateUseAttribute but does not specify its types: T' => 16,
20 | 'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse\InvalidClassTemplateUseAttribute2 uses generic trait test\PhpStaticAnalysis\PHPStanExtension\data\TemplateUse\InvalidTraitTemplateUseAttribute but does not specify its types: T' => 22,
21 | 'Attribute class PhpStaticAnalysis\Attributes\TemplateUse does not have the property target.' => 24,
22 | ];
23 |
24 | $this->checkExpectedErrors($errors, $expectedErrors);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/RequireImplementsAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/RequireImplements/TraitRequireImplementsAttribute.php');
10 | $expectedErrors = [
11 | 'Trait test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements\TraitRequireImplementsAttribute requires using class to implement test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements\InterfaceRequireImplementsAttribute3, but test\PhpStaticAnalysis\PHPStanExtension\data\RequireImplements\ClassRequireImplementsAttribute2 does not.' => 33,
12 | ];
13 |
14 | $this->checkExpectedErrors($errors, $expectedErrors);
15 | }
16 |
17 | public function testInvalidClassRequireImplementsAttribute(): void
18 | {
19 | $errors = $this->analyse(__DIR__ . '/data/RequireImplements/InvalidTraitRequireImplementsAttribute.php');
20 |
21 | $expectedErrors = [
22 | 'PHPDoc tag @phpstan-require-implements has invalid value (): Unexpected token "\n ", expected type at offset 34 on line 2' => 8,
23 | 'PHPDoc tag @phpstan-require-implements contains non-object type int.' => 12,
24 | 'Attribute class PhpStaticAnalysis\Attributes\RequireImplements does not have the property target.' => 15,
25 | ];
26 |
27 | $this->checkExpectedErrors($errors, $expectedErrors);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/RequireExtendsAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/RequireExtends/TraitRequireExtendsAttribute.php');
10 | $expectedErrors = [
11 | 'Trait test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends\TraitRequireExtendsAttribute requires using class to extend test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends\ClassRequireExtendsAttribute, but test\PhpStaticAnalysis\PHPStanExtension\data\RequireExtends\ClassRequireExtendsAttributeChild2 does not.' => 21,
12 | ];
13 |
14 | $this->checkExpectedErrors($errors, $expectedErrors);
15 | }
16 |
17 | public function testInvalidClassRequireExtendsAttribute(): void
18 | {
19 | $errors = $this->analyse(__DIR__ . '/data/RequireExtends/InvalidTraitRequireExtendsAttribute.php');
20 |
21 | $expectedErrors = [
22 | 'PHPDoc tag @phpstan-require-extends has invalid value (): Unexpected token "\n ", expected type at offset 31 on line 2' => 8,
23 | 'PHPDoc tag @phpstan-require-extends contains non-object type int.' => 12,
24 | 'PHPDoc tag @phpstan-require-extends can only be used once.' => 17,
25 | 'Attribute class PhpStaticAnalysis\Attributes\RequireExtends does not have the property target.' => 21,
26 | ];
27 |
28 | $this->checkExpectedErrors($errors, $expectedErrors);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/MethodAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Method/ClassMethodAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInterfaceMethodAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Method/InterfaceMethodAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testTraitMethodAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Method/TraitMethodAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testInvalidClassMethodAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/Method/InvalidClassMethodAttribute.php');
28 |
29 | $expectedErrors = [
30 | 'PHPDoc tag @method has invalid value (): Unexpected token "\n * ", expected type at offset 14 on line 2' => 9,
31 | 'PHPDoc tag @method has invalid value (string): Unexpected token "\n * ", expected \'(\' at offset 32 on line 3' => 10,
32 | 'Attribute class PhpStaticAnalysis\Attributes\Method does not have the method target.' => 13,
33 | 'Call to an undefined method test\PhpStaticAnalysis\PHPStanExtension\data\Method\InvalidClassMethodAttribute::badFunction().' => 31,
34 | ];
35 |
36 | $this->checkExpectedErrors($errors, $expectedErrors);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/PropertyAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Property/ClassPropertyAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInterfacePropertyAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Property/InterfacePropertyAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testTraitPropertyAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Property/TraitPropertyAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testInvalidClassPropertyAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/Property/InvalidClassPropertyAttribute.php');
28 |
29 | $expectedErrors = [
30 | 'PHPDoc tag @property has invalid value (): Unexpected token "\n * ", expected type at offset 16 on line 2' => 8,
31 | 'PHPDoc tag @property has invalid value (string): Unexpected token "\n * ", expected variable at offset 36 on line 3' => 9,
32 | 'PHPDoc tag @property has invalid value (count($a) $name): Unexpected token "(", expected variable at offset 55 on line 4' => 10,
33 | 'Attribute class PhpStaticAnalysis\Attributes\Property does not have the method target.' => 12,
34 | ];
35 |
36 | $this->checkExpectedErrors($errors, $expectedErrors);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/data/Template/MethodTemplateAttribute.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Immutable/ClassImmutableAttribute.php');
11 | $expectedErrors = [
12 | '@readonly property cannot have a default value.' => 10,
13 | '@readonly property test\PhpStaticAnalysis\PHPStanExtension\data\Immutable\ClassImmutableAttribute::$name is assigned outside of its declaring class.' => 14,
14 | ];
15 |
16 | $this->checkExpectedErrors($errors, $expectedErrors);
17 | }
18 |
19 | public function testTraitImmutableAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Immutable/TraitImmutableAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testInterfaceImmutableAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/Immutable/InterfaceImmutableAttribute.php');
28 | $this->assertCount(0, $errors);
29 | }
30 |
31 | public function testInvalidClassImmutableAttribute(): void
32 | {
33 | $errors = $this->analyse(__DIR__ . '/data/Immutable/InvalidClassImmutableAttribute.php');
34 |
35 | $expectedErrors = [
36 | 'Attribute class PhpStaticAnalysis\Attributes\Immutable does not have the property target.' => 13,
37 | '@readonly property cannot have a default value.' => 14,
38 | ];
39 |
40 | $this->checkExpectedErrors($errors, $expectedErrors);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/TemplateExtendsAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/TemplateExtends/ClassTemplateExtendsAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInvalidClassTemplateExtendsAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/TemplateExtends/InvalidClassTemplateExtendsAttribute.php');
16 |
17 | $expectedErrors = [
18 | 'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends\InvalidClassTemplateExtendsAttributeChild extends generic class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends\InvalidClassTemplateExtendsAttribute but does not specify its types: T' => 13,
19 | 'PHPDoc tag @template-extends has invalid value (): Unexpected token "\n ", expected type at offset 24 on line 2' => 14,
20 | 'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends\InvalidClassTemplateExtendsAttributeChild2 extends generic class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateExtends\InvalidClassTemplateExtendsAttribute but does not specify its types: T' => 18,
21 | 'PHPDoc tag @template-extends has invalid value (+5): Unexpected token "+5", expected type at offset 25 on line 2' => 19,
22 | 'Attribute class PhpStaticAnalysis\Attributes\TemplateExtends does not have the property target.' => 27,
23 | ];
24 |
25 | $this->checkExpectedErrors($errors, $expectedErrors);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/TemplateContravariantAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/TemplateContravariant/ClassTemplateContravariantAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testTraitTemplateContravariantAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/TemplateContravariant/TraitTemplateContravariantAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testInterfaceTemplateContravariantAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/TemplateContravariant/InterfaceTemplateContravariantAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testInvalidClassTemplateContravariantAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/TemplateContravariant/InvalidClassTemplateContravariantAttribute.php');
28 |
29 | $expectedErrors = [
30 | 'PHPDoc tag @template-contravariant has invalid value (): Unexpected token "\n * ", expected type at offset 30 on line 2' => 8,
31 | 'PHPDoc tag @template-contravariant has invalid value (+5): Unexpected token "+5", expected type at offset 58 on line 3' => 9,
32 | 'Attribute class PhpStaticAnalysis\Attributes\TemplateContravariant does not have the property target.' => 12,
33 | ];
34 |
35 | $this->checkExpectedErrors($errors, $expectedErrors);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.github/workflows/all_tests.yml:
--------------------------------------------------------------------------------
1 | name: "All Tests"
2 |
3 | on:
4 | pull_request:
5 | push:
6 |
7 | jobs:
8 | test:
9 | name: "Run all checks for all supported PHP versions"
10 |
11 | runs-on: "ubuntu-22.04"
12 |
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | php-version:
17 | - "8.1"
18 | - "8.2"
19 | - "8.3"
20 | - "8.4"
21 |
22 | steps:
23 | - name: "Checkout"
24 | uses: "actions/checkout@v4"
25 |
26 | - name: "Install PHP"
27 | uses: "shivammathur/setup-php@v2"
28 | with:
29 | php-version: "${{ matrix.php-version }}"
30 | tools: composer
31 |
32 | - name: Get composer cache directory
33 | id: composercache
34 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
35 |
36 | - name: Cache dependencies
37 | uses: actions/cache@v4
38 | with:
39 | path: ${{ steps.composercache.outputs.dir }}
40 | key: "php-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}"
41 | restore-keys: "php-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}"
42 |
43 | - name: "Install composer dependencies"
44 | run: "composer install --no-interaction --no-progress"
45 |
46 | - name: "Run tests"
47 | run: "composer tests-without-psalm"
48 |
49 | - name: "Run Psalm"
50 | if: matrix.php-version != '8.4' # Psalm does not fully support PHP 8.4 yet
51 | run: "composer psalm"
--------------------------------------------------------------------------------
/tests/BaseAttributeTestCase.php:
--------------------------------------------------------------------------------
1 | getFileHelper()->normalizePath($file);
18 | $analyser = self::getContainer()->getByType(Analyser::class);
19 | $fileHelper = self::getContainer()->getByType(FileHelper::class);
20 | $errors = $analyser->analyse([$file], null, null, true)->getErrors();
21 | foreach ($errors as $error) {
22 | $this->assertSame($fileHelper->normalizePath($file), $error->getFilePath());
23 | }
24 |
25 | return $errors;
26 | }
27 |
28 | #[Param(
29 | errors: 'Error[]',
30 | expectedErrors: 'array',
31 | )]
32 | protected function checkExpectedErrors(
33 | array $errors,
34 | array $expectedErrors
35 | ): void {
36 | $this->assertCount(count($expectedErrors), $errors);
37 |
38 | $errorNum = 0;
39 | foreach ($expectedErrors as $error => $line) {
40 | $this->assertSame($error, $errors[$errorNum]->getMessage());
41 | $this->assertSame($line, $errors[$errorNum]->getLine());
42 | $errorNum++;
43 | }
44 | }
45 |
46 | public static function getAdditionalConfigFiles(): array
47 | {
48 | return [
49 | __DIR__ . '/../extension.neon',
50 | ];
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/DefineTypeAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/DefineType/ClassDefineTypeAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInterfaceDefineTypeAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/DefineType/InterfaceDefineTypeAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testTraitDefineTypeAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/DefineType/TraitDefineTypeAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testInvalidClassDefineTypeAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/DefineType/InvalidClassDefineTypeAttribute.php');
28 |
29 | $expectedErrors = [
30 | 'Type alias has an invalid name: string.' => 7,
31 | 'PHPDoc tag @phpstan-type has invalid value (): Unexpected token "\n * ", expected type at offset 20 on line 2' => 8,
32 | 'PHPDoc tag @phpstan-type string has invalid value: Unexpected token "\n * ", expected type at offset 44 on line 3' => 9,
33 | 'PHPDoc tag @phpstan-type name has invalid value: Unexpected token "(", expected TOKEN_PHPDOC_EOL at offset 72 on line 4' => 10,
34 | 'Attribute class PhpStaticAnalysis\Attributes\DefineType does not have the method target.' => 12,
35 | ];
36 |
37 | $this->checkExpectedErrors($errors, $expectedErrors);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/TemplateImplementsAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/TemplateImplements/InterfaceTemplateImplementsAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInvalidInterfaceTemplateImplementsAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/TemplateImplements/InvalidInterfaceTemplateImplementsAttribute.php');
16 |
17 | $expectedErrors = [
18 | 'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements\InvalidClassTemplateImplementsAttribute implements generic interface test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements\InvalidInterfaceTemplateImplementsAttribute but does not specify its types: T' => 13,
19 | 'PHPDoc tag @template-implements has invalid value (): Unexpected token "\n ", expected type at offset 27 on line 2' => 14,
20 | 'Class test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements\InvalidClassTemplateImplementsAttribute2 implements generic interface test\PhpStaticAnalysis\PHPStanExtension\data\TemplateImplements\InvalidInterfaceTemplateImplementsAttribute but does not specify its types: T' => 18,
21 | 'PHPDoc tag @template-implements has invalid value (+5): Unexpected token "+5", expected type at offset 28 on line 2' => 19,
22 | 'Attribute class PhpStaticAnalysis\Attributes\TemplateImplements does not have the property target.' => 21,
23 | ];
24 |
25 | $this->checkExpectedErrors($errors, $expectedErrors);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/TemplateCovariantAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/TemplateCovariant/ClassTemplateCovariantAttribute.php');
10 | $expectedErrors = [
11 | 'PHPDoc tag @var with type TCreable is not subtype of native type object.' => 28,
12 | ];
13 | $this->checkExpectedErrors($errors, $expectedErrors);
14 | }
15 |
16 | public function testTraitTemplateCovariantAttribute(): void
17 | {
18 | $errors = $this->analyse(__DIR__ . '/data/TemplateCovariant/TraitTemplateCovariantAttribute.php');
19 | $this->assertCount(0, $errors);
20 | }
21 |
22 | public function testInterfaceTemplateCovariantAttribute(): void
23 | {
24 | $errors = $this->analyse(__DIR__ . '/data/TemplateCovariant/InterfaceTemplateCovariantAttribute.php');
25 | $this->assertCount(0, $errors);
26 | }
27 |
28 | public function testInvalidClassTemplateCovariantAttribute(): void
29 | {
30 | $errors = $this->analyse(__DIR__ . '/data/TemplateCovariant/InvalidClassTemplateCovariantAttribute.php');
31 |
32 | $expectedErrors = [
33 | 'PHPDoc tag @template-covariant has invalid value (): Unexpected token "\n * ", expected type at offset 26 on line 2' => 8,
34 | 'PHPDoc tag @template-covariant has invalid value (+5): Unexpected token "+5", expected type at offset 50 on line 3' => 9,
35 | 'Attribute class PhpStaticAnalysis\Attributes\TemplateCovariant does not have the property target.' => 12,
36 | ];
37 |
38 | $this->checkExpectedErrors($errors, $expectedErrors);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/PropertyReadAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/PropertyRead/ClassPropertyReadAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInterfacePropertyReadAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/PropertyRead/InterfacePropertyReadAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testTraitPropertyReadAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/PropertyRead/TraitPropertyReadAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testInvalidClassPropertyReadAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/PropertyRead/InvalidClassPropertyReadAttribute.php');
28 |
29 | $expectedErrors = [
30 | 'PHPDoc tag @property-read has invalid value (): Unexpected token "\n * ", expected type at offset 21 on line 2' => 8,
31 | 'PHPDoc tag @property-read has invalid value (string): Unexpected token "\n * ", expected variable at offset 46 on line 3' => 9,
32 | 'PHPDoc tag @property-read has invalid value (count($a) $name): Unexpected token "(", expected variable at offset 70 on line 4' => 10,
33 | 'Attribute class PhpStaticAnalysis\Attributes\PropertyRead does not have the method target.' => 13,
34 | 'Property test\PhpStaticAnalysis\PHPStanExtension\data\PropertyRead\ClassPropertyReadAttribute::$age is not writable.' => 21,
35 | ];
36 |
37 | $this->checkExpectedErrors($errors, $expectedErrors);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/PropertyWriteAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/PropertyWrite/ClassPropertyWriteAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testInterfacePropertyWriteAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/PropertyWrite/InterfacePropertyWriteAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testTraitPropertyWriteAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/PropertyWrite/TraitPropertyWriteAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testInvalidClassPropertyWriteAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/PropertyWrite/InvalidClassPropertyWriteAttribute.php');
28 |
29 | $expectedErrors = [
30 | 'PHPDoc tag @property-write has invalid value (): Unexpected token "\n * ", expected type at offset 22 on line 2' => 8,
31 | 'PHPDoc tag @property-write has invalid value (string): Unexpected token "\n * ", expected variable at offset 48 on line 3' => 9,
32 | 'PHPDoc tag @property-write has invalid value (count($a) $name): Unexpected token "(", expected variable at offset 73 on line 4' => 10,
33 | 'Attribute class PhpStaticAnalysis\Attributes\PropertyWrite does not have the method target.' => 13,
34 | 'Property test\PhpStaticAnalysis\PHPStanExtension\data\PropertyWrite\ClassPropertyWriteAttribute::$age is not readable.' => 21,
35 | ];
36 |
37 | $this->checkExpectedErrors($errors, $expectedErrors);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/InternalAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Internal/ClassInternalAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testTraitInternalAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Internal/TraitInternalAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testInterfaceInternalAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Internal/InterfaceInternalAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testMethodInternalAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/Internal/MethodInternalAttribute.php');
28 | $this->assertCount(0, $errors);
29 | }
30 |
31 | public function testFunctionInternalAttribute(): void
32 | {
33 | $errors = $this->analyse(__DIR__ . '/data/Internal/FunctionInternalAttribute.php');
34 | $this->assertCount(0, $errors);
35 | }
36 |
37 | public function testPropertyInternalAttribute(): void
38 | {
39 | $errors = $this->analyse(__DIR__ . '/data/Internal/PropertyInternalAttribute.php');
40 | $this->assertCount(0, $errors);
41 | }
42 |
43 | public function testInvalidMethodInternalAttribute(): void
44 | {
45 | $errors = $this->analyse(__DIR__ . '/data/Internal/InvalidMethodInternalAttribute.php');
46 | $expectedErrors = [
47 | 'Attribute class PhpStaticAnalysis\Attributes\Internal does not have the parameter target.' => 15,
48 | ];
49 |
50 | $this->checkExpectedErrors($errors, $expectedErrors);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/ThrowsAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Throws/MethodThrowsAttribute.php');
10 | $expectedErrors = [
11 | 'Method test\PhpStaticAnalysis\PHPStanExtension\data\Throws\MethodThrowsAttribute::countNoErrorName() has Exception in PHPDoc @throws tag but it\'s not thrown.' => 72,
12 | ];
13 |
14 | $this->checkExpectedErrors($errors, $expectedErrors);
15 | }
16 |
17 | public function testFunctionThrowsAttribute(): void
18 | {
19 | $errors = $this->analyse(__DIR__ . '/data/Throws/FunctionThrowsAttribute.php');
20 | $this->assertCount(0, $errors);
21 | }
22 |
23 | public function testInvalidMethodThrowsAttribute(): void
24 | {
25 | $errors = $this->analyse(__DIR__ . '/data/Throws/InvalidMethodThrowsAttribute.php');
26 |
27 | $expectedErrors = [
28 | 'PHPDoc tag @throws has invalid value (): Unexpected token "\n ", expected type at offset 14 on line 2' => 11,
29 | 'Method test\PhpStaticAnalysis\PHPStanExtension\data\Throws\InvalidMethodThrowsAttribute::getOtherNameLength() has string in PHPDoc @throws tag but it\'s not thrown.' => 16,
30 | 'PHPDoc tag @throws with type string is not subtype of Throwable' => 16,
31 | 'Attribute class PhpStaticAnalysis\Attributes\Throws does not have the property target.' => 22,
32 | ];
33 |
34 | $this->checkExpectedErrors($errors, $expectedErrors);
35 | }
36 |
37 | public static function getAdditionalConfigFiles(): array
38 | {
39 | return array_merge(
40 | parent::getAdditionalConfigFiles(),
41 | [
42 | __DIR__ . '/conf/throws.neon',
43 | ]
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/TemplateAttributeTest.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Template/ClassTemplateAttribute.php');
10 | $this->assertCount(0, $errors);
11 | }
12 |
13 | public function testTraitTemplateAttribute(): void
14 | {
15 | $errors = $this->analyse(__DIR__ . '/data/Template/TraitTemplateAttribute.php');
16 | $this->assertCount(0, $errors);
17 | }
18 |
19 | public function testInterfaceTemplateAttribute(): void
20 | {
21 | $errors = $this->analyse(__DIR__ . '/data/Template/InterfaceTemplateAttribute.php');
22 | $this->assertCount(0, $errors);
23 | }
24 |
25 | public function testMethodTemplateAttribute(): void
26 | {
27 | $errors = $this->analyse(__DIR__ . '/data/Template/MethodTemplateAttribute.php');
28 | $this->assertCount(0, $errors);
29 | }
30 |
31 | public function testFunctionTemplateAttribute(): void
32 | {
33 | $errors = $this->analyse(__DIR__ . '/data/Template/FunctionTemplateAttribute.php');
34 | $this->assertCount(0, $errors);
35 | }
36 |
37 | public function testInvalidMethodTemplateAttribute(): void
38 | {
39 | $errors = $this->analyse(__DIR__ . '/data/Template/InvalidMethodTemplateAttribute.php');
40 |
41 | $expectedErrors = [
42 | 'PHPDoc tag @template has invalid value (): Unexpected token "\n ", expected type at offset 16 on line 2' => 12,
43 | 'PHPDoc tag @template has invalid value (+5): Unexpected token "+5", expected type at offset 17 on line 2' => 18,
44 | 'Attribute class PhpStaticAnalysis\Attributes\Template does not have the property target.' => 23,
45 | ];
46 |
47 | $this->checkExpectedErrors($errors, $expectedErrors);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-static-analysis/phpstan-extension",
3 | "description": "PHPStan extension to read static analysis attributes",
4 | "type": "phpstan-extension",
5 | "keywords": ["dev", "static analysis"],
6 | "license": "MIT",
7 | "autoload": {
8 | "psr-4": {
9 | "PhpStaticAnalysis\\PHPStanExtension\\": "src/"
10 | }
11 | },
12 | "autoload-dev": {
13 | "psr-4": {
14 | "test\\PhpStaticAnalysis\\PHPStanExtension\\": "tests/"
15 | }
16 | },
17 | "authors": [
18 | {
19 | "name": "Carlos Granados",
20 | "email": "carlos@fastdebug.io"
21 | }
22 | ],
23 | "minimum-stability": "dev",
24 | "prefer-stable": true,
25 | "require": {
26 | "php": ">=8.1",
27 | "php-static-analysis/attributes": "^0.5.0 || dev-main",
28 | "php-static-analysis/node-visitor": "^0.5.0 || dev-main",
29 | "phpstan/phpstan": "^2.0",
30 | "webmozart/assert": "^1.11"
31 | },
32 | "require-dev": {
33 | "php-static-analysis/psalm-plugin": "^0.5.0 || dev-main",
34 | "phpstan/phpstan-deprecation-rules": "^2.0",
35 | "phpunit/phpunit": "^9.0",
36 | "symplify/easy-coding-standard": "^12.1",
37 | "vimeo/psalm": "^6"
38 | },
39 | "extra": {
40 | "phpstan": {
41 | "includes": [
42 | "extension.neon"
43 | ]
44 | }
45 | },
46 | "config": {
47 | "sort-packages": true,
48 | "allow-plugins": {
49 | "php-static-analysis/psalm-plugin": true
50 | }
51 | },
52 | "scripts": {
53 | "phpstan": "phpstan analyse",
54 | "phpstan-debug": "phpstan analyse --xdebug --debug",
55 | "ecs": "ecs",
56 | "ecs-fix": "ecs --fix",
57 | "phpunit": "phpunit",
58 | "psalm": "psalm",
59 | "tests": [
60 | "@ecs",
61 | "@phpstan",
62 | "@phpunit",
63 | "@psalm"
64 | ],
65 | "tests-without-psalm": [
66 | "@ecs",
67 | "@phpstan",
68 | "@phpunit"
69 | ]
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/data/Throws/MethodThrowsAttribute.php:
--------------------------------------------------------------------------------
1 | analyse(__DIR__ . '/data/Deprecated/ClassDeprecatedAttribute.php');
10 | $expectedErrors = [
11 | 'Instantiation of deprecated class test\PhpStaticAnalysis\PHPStanExtension\data\Deprecated\ClassDeprecatedAttribute.' => 12,
12 | ];
13 |
14 | $this->checkExpectedErrors($errors, $expectedErrors);
15 | }
16 |
17 | public function testTraitDeprecatedAttribute(): void
18 | {
19 | $errors = $this->analyse(__DIR__ . '/data/Deprecated/TraitDeprecatedAttribute.php');
20 | $this->assertCount(0, $errors);
21 | }
22 |
23 | public function testInterfaceDeprecatedAttribute(): void
24 | {
25 | $errors = $this->analyse(__DIR__ . '/data/Deprecated/InterfaceDeprecatedAttribute.php');
26 | $this->assertCount(0, $errors);
27 | }
28 |
29 | public function testMethodDeprecatedAttribute(): void
30 | {
31 | $errors = $this->analyse(__DIR__ . '/data/Deprecated/MethodDeprecatedAttribute.php');
32 | $expectedErrors = [
33 | 'Call to deprecated method returnDeprecated() of class test\PhpStaticAnalysis\PHPStanExtension\data\Deprecated\MethodDeprecatedAttribute.' => 31,
34 | ];
35 |
36 | $this->checkExpectedErrors($errors, $expectedErrors);
37 | }
38 |
39 | public function testFunctionDeprecatedAttribute(): void
40 | {
41 | $errors = $this->analyse(__DIR__ . '/data/Deprecated/FunctionDeprecatedAttribute.php');
42 | $this->assertCount(0, $errors);
43 | }
44 |
45 | public function testPropertyDeprecatedAttribute(): void
46 | {
47 | $errors = $this->analyse(__DIR__ . '/data/Deprecated/PropertyDeprecatedAttribute.php');
48 | $this->assertCount(0, $errors);
49 | }
50 |
51 | public function testInvalidMethodDeprecatedAttribute(): void
52 | {
53 | $errors = $this->analyse(__DIR__ . '/data/Deprecated/InvalidMethodDeprecatedAttribute.php');
54 |
55 | $expectedErrors = [
56 | 'Attribute class PhpStaticAnalysis\Attributes\Deprecated does not have the parameter target.' => 12,
57 | ];
58 |
59 | $this->checkExpectedErrors($errors, $expectedErrors);
60 | }
61 |
62 | public static function getAdditionalConfigFiles(): array
63 | {
64 | return array_merge(
65 | parent::getAdditionalConfigFiles(),
66 | [
67 | __DIR__ . '/conf/deprecated.neon',
68 | ]
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/data/AssertIfTrue/MethodAssertIfTrueAttribute.php:
--------------------------------------------------------------------------------
1 | name')]
31 | public function checkOtherPropertyString(mixed $name): bool
32 | {
33 | return is_string($this->name);
34 | }
35 |
36 | /**
37 | * @deprecated
38 | */
39 | #[AssertIfTrue(name: 'string')]
40 | public function checkAnotherString(mixed $name): bool
41 | {
42 | return is_string($name);
43 | }
44 |
45 | /**
46 | * @assert int $name
47 | */
48 | #[AssertIfTrue(name: 'string')]
49 | public function checkEvenMoreString(mixed $name): bool
50 | {
51 | return is_string($name);
52 | }
53 |
54 | #[AssertIfTrue(
55 | name1: 'string',
56 | name2: 'string'
57 | )]
58 | public function checkStrings(mixed $name1, mixed $name2): bool
59 | {
60 | return is_string($name1) && is_string($name2);
61 | }
62 |
63 | #[AssertIfTrue(name1: 'string')]
64 | #[AssertIfTrue(name2: 'string')]
65 | public function checkOtherStrings(mixed $name1, mixed $name2): bool
66 | {
67 | return is_string($name1) && is_string($name2);
68 | }
69 |
70 | /**
71 | * @assert string $name
72 | */
73 | public function checkMoreAndMoreString(mixed $name): bool
74 | {
75 | return is_string($name);
76 | }
77 |
78 | public function checkStringInParam(
79 | #[AssertIfTrue('string')]
80 | mixed $name
81 | ): bool {
82 | return is_string($name);
83 | }
84 |
85 | public function checkStringInParamWithName(
86 | #[AssertIfTrue(name: 'string')]
87 | mixed $name
88 | ): bool {
89 | return is_string($name);
90 | }
91 |
92 | public function checkStringInTwoParams(
93 | #[AssertIfTrue('string')]
94 | mixed $name1,
95 | #[AssertIfTrue('string')]
96 | mixed $name2
97 | ): bool {
98 | return is_string($name1) && is_string($name2);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/tests/data/AssertIfFalse/MethodAssertIfFalseAttribute.php:
--------------------------------------------------------------------------------
1 | name')]
31 | public function checkOtherPropertyString(mixed $name): bool
32 | {
33 | return !is_string($this->name);
34 | }
35 |
36 | /**
37 | * @deprecated
38 | */
39 | #[AssertIfFalse(name: 'string')]
40 | public function checkAnotherString(mixed $name): bool
41 | {
42 | return !is_string($name);
43 | }
44 |
45 | /**
46 | * @assert int $name
47 | */
48 | #[AssertIfFalse(name: 'string')]
49 | public function checkEvenMoreString(mixed $name): bool
50 | {
51 | return !is_string($name);
52 | }
53 |
54 | #[AssertIfFalse(
55 | name1: 'string',
56 | name2: 'string'
57 | )]
58 | public function checkStrings(mixed $name1, mixed $name2): bool
59 | {
60 | return !is_string($name1) || !is_string($name2);
61 | }
62 |
63 | #[AssertIfFalse(name1: 'string')]
64 | #[AssertIfFalse(name2: 'string')]
65 | public function checkOtherStrings(mixed $name1, mixed $name2): bool
66 | {
67 | return !is_string($name1) || !is_string($name2);
68 | }
69 |
70 | /**
71 | * @assert string $name
72 | */
73 | public function checkMoreAndMoreString(mixed $name): bool
74 | {
75 | return !is_string($name);
76 | }
77 |
78 | public function checkStringInParam(
79 | #[AssertIfFalse('string')]
80 | mixed $name
81 | ): bool {
82 | return !is_string($name);
83 | }
84 |
85 | public function checkStringInParamWithName(
86 | #[AssertIfFalse(name: 'string')]
87 | mixed $name
88 | ): bool {
89 | return !is_string($name);
90 | }
91 |
92 | public function checkStringInTwoParams(
93 | #[AssertIfFalse('string')]
94 | mixed $name1,
95 | #[AssertIfFalse('string')]
96 | mixed $name2
97 | ): bool {
98 | return !is_string($name1) || !is_string($name2);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/tests/data/Assert/MethodAssertAttribute.php:
--------------------------------------------------------------------------------
1 | */
23 | private array $result;
24 |
25 | /**
26 | * @param array $array1
27 | * @param array $array2
28 | * @return array
29 | */
30 | public function addArrays(array $array1, array $array2): array
31 | {
32 | $this->result = $array1 + $array2;
33 | return $this->result;
34 | }
35 | }
36 | ```
37 |
38 | And this is how it would look like using the new attributes:
39 |
40 | ```php
41 | ')]
50 | private array $result;
51 |
52 | #[Param(array1: 'array')]
53 | #[Param(array2: 'array')]
54 | #[Returns('array')]
55 | public function addArrays(array $array1, array $array2): array
56 | {
57 | $this->array = $array1 + $array2;
58 | return $this->array;
59 | }
60 | }
61 | ```
62 |
63 | ## Installation
64 |
65 | First of all, to make the attributes available for your codebase use:
66 |
67 | ```
68 | composer require php-static-analysis/attributes
69 | ```
70 |
71 | To use this extension, require it in Composer:
72 |
73 | ```
74 | composer require --dev php-static-analysis/phpstan-extension
75 | ```
76 |
77 | If you also install [phpstan/extension-installer](https://github.com/phpstan/extension-installer) then you're all set!
78 |
79 |
80 | Manual installation
81 |
82 | If you don't want to use `phpstan/extension-installer`, include `extension.neon` in your project's PHPStan config:
83 |
84 | ```
85 | includes:
86 | - vendor/php-static-analysis/phpstan-extension/extension.neon
87 | ```
88 |
89 |
90 | ## Using the extension
91 |
92 | This extension works by interacting with the parser that PHPStan uses to parse the code and replacing the new Attributes with PHPDoc annotations that PHPStan can understand. The functionality provided by the attribute is exactly the same as the one provided by the corresponding PHPDoc annotation.
93 |
94 | These are the available attributes and their corresponding PHPDoc annotations:
95 |
96 | | Attribute | PHPDoc Annotations |
97 | |-------------------------------------------------------------------------------------------------------------------|--------------------------------------|
98 | | [Assert](https://github.com/php-static-analysis/attributes/blob/main/doc/Assert.md) | `@assert` |
99 | | [AssertIfFalse](https://github.com/php-static-analysis/attributes/blob/main/doc/AssertIfFalse.md) | `@assert-if-false` |
100 | | [AssertIfTrue](https://github.com/php-static-analysis/attributes/blob/main/doc/AssertIfTrue.md) | `@assert-if-true` |
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 | | [Impure](https://github.com/php-static-analysis/attributes/blob/main/doc/Impure.md) | `@impure` |
106 | | [Internal](https://github.com/php-static-analysis/attributes/blob/main/doc/Internal.md) | `@internal` |
107 | | [IsReadOnly](https://github.com/php-static-analysis/attributes/blob/main/doc/IsReadOnly.md) | `@readonly` |
108 | | [Method](https://github.com/php-static-analysis/attributes/blob/main/doc/Method.md) | `@method` |
109 | | [Mixin](https://github.com/php-static-analysis/attributes/blob/main/doc/Mixin.md) | `@mixin` |
110 | | [Param](https://github.com/php-static-analysis/attributes/blob/main/doc/Param.md) | `@param` |
111 | | [ParamOut](https://github.com/php-static-analysis/attributes/blob/main/doc/ParamOut.md) | `@param-out` |
112 | | [Property](https://github.com/php-static-analysis/attributes/blob/main/doc/Property.md) | `@property` `@var` |
113 | | [PropertyRead](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyRead.md) | `@property-read` |
114 | | [PropertyWrite](https://github.com/php-static-analysis/attributes/blob/main/doc/PropertyWrite.md) | `@property-write` |
115 | | [Pure](https://github.com/php-static-analysis/attributes/blob/main/doc/Pure.md) | `@pure` |
116 | | [RequireExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireExtends.md) | `@require-extends` |
117 | | [RequireImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/RequireImplements.md) | `@require-implements` |
118 | | [Returns](https://github.com/php-static-analysis/attributes/blob/main/doc/Returns.md) | `@return` |
119 | | [SelfOut](https://github.com/php-static-analysis/attributes/blob/main/doc/SelfOut.md) | `@self-out` `@this-out` |
120 | | [Template](https://github.com/php-static-analysis/attributes/blob/main/doc/Template.md) | `@template` |
121 | | [TemplateContravariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateContravariant.md) | `@template-contravariant` |
122 | | [TemplateCovariant](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateCovariant.md) | `@template-covariant` |
123 | | [TemplateExtends](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateExtends.md) | `@extends` `@template-extends` |
124 | | [TemplateImplements](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateImplements.md) | `@implements` `@template-implements` |
125 | | [TemplateUse](https://github.com/php-static-analysis/attributes/blob/main/doc/TemplateUse.md) | `@use` `@template-use` |
126 | | [Throws](https://github.com/php-static-analysis/attributes/blob/main/doc/Throws.md) | `@throws` |
127 | | [Type](https://github.com/php-static-analysis/attributes/blob/main/doc/Type.md) | `@var` `@return` |
128 |
129 | ## Sponsor this project
130 |
131 | If you would like to support the development of this project, please consider [sponsoring me](https://github.com/sponsors/carlos-granados)
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------