├── .github ├── FUNDING.yml └── workflows │ └── all_tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── ecs.php ├── extension.neon ├── phpstan.neon ├── phpunit.xml ├── psalm.xml ├── src └── Parser │ └── AttributeParser.php └── tests ├── AssertAttributeTest.php ├── AssertIfFalseAttributeTest.php ├── AssertIfTrueAttributeTest.php ├── BaseAttributeTestCase.php ├── DefineTypeAttributeTest.php ├── DeprecatedAttributeTest.php ├── ImmutableAttributeTest.php ├── ImportTypeAttributeTest.php ├── ImpureAttributeTest.php ├── InternalAttributeTest.php ├── IsReadOnlyAttributeTest.php ├── MethodAttributeTest.php ├── MixinAttributeTest.php ├── ParamAttributeTest.php ├── ParamOutAttributeTest.php ├── PropertyAttributeTest.php ├── PropertyReadAttributeTest.php ├── PropertyWriteAttributeTest.php ├── PureAttributeTest.php ├── RequireExtendsAttributeTest.php ├── RequireImplementsAttributeTest.php ├── ReturnsAttributeTest.php ├── SelfOutAttributeTest.php ├── TemplateAttributeTest.php ├── TemplateContravariantAttributeTest.php ├── TemplateCovariantAttributeTest.php ├── TemplateExtendsAttributeTest.php ├── TemplateImplementsAttributeTest.php ├── TemplateUseAttributeTest.php ├── ThrowsAttributeTest.php ├── TypeAttributeTest.php ├── conf ├── deprecated.neon ├── readonly.neon └── throws.neon └── data ├── Assert ├── FunctionAssertAttribute.php ├── InvalidMethodAssertAttribute.php └── MethodAssertAttribute.php ├── AssertIfFalse ├── FunctionAssertIfFalseAttribute.php ├── InvalidMethodAssertIfFalseAttribute.php └── MethodAssertIfFalseAttribute.php ├── AssertIfTrue ├── FunctionAssertIfTrueAttribute.php ├── InvalidMethodAssertIfTrueAttribute.php └── MethodAssertIfTrueAttribute.php ├── DefineType ├── ClassDefineTypeAttribute.php ├── InterfaceDefineTypeAttribute.php ├── InvalidClassDefineTypeAttribute.php └── TraitDefineTypeAttribute.php ├── Deprecated ├── ClassDeprecatedAttribute.php ├── FunctionDeprecatedAttribute.php ├── InterfaceDeprecatedAttribute.php ├── InvalidMethodDeprecatedAttribute.php ├── MethodDeprecatedAttribute.php ├── PropertyDeprecatedAttribute.php └── TraitDeprecatedAttribute.php ├── Immutable ├── ClassImmutableAttribute.php ├── InterfaceImmutableAttribute.php ├── InvalidClassImmutableAttribute.php └── TraitImmutableAttribute.php ├── ImportType ├── ClassImportTypeAttribute.php ├── InterfaceImportTypeAttribute.php ├── InvalidClassImportTypeAttribute.php └── TraitImportTypeAttribute.php ├── Impure ├── FunctionImpureAttribute.php ├── InvalidMethodImpureAttribute.php └── MethodImpureAttribute.php ├── Internal ├── ClassInternalAttribute.php ├── FunctionInternalAttribute.php ├── InterfaceInternalAttribute.php ├── InvalidMethodInternalAttribute.php ├── MethodInternalAttribute.php ├── PropertyInternalAttribute.php └── TraitInternalAttribute.php ├── IsReadOnly ├── InvalidPropertyIsReadOnlyAttribute.php └── PropertyIsReadOnlyAttribute.php ├── Method ├── ClassMethodAttribute.php ├── InterfaceMethodAttribute.php ├── InvalidClassMethodAttribute.php └── TraitMethodAttribute.php ├── Mixin ├── ClassMixinAttribute.php ├── InterfaceMixinAttribute.php ├── InvalidClassMixinAttribute.php └── TraitMixinAttribute.php ├── Param ├── FunctionParamAttribute.php ├── InvalidMethodParamAttribute.php └── MethodParamAttribute.php ├── ParamOut ├── FunctionParamOutAttribute.php ├── InvalidMethodParamOutAttribute.php └── MethodParamOutAttribute.php ├── Property ├── ClassPropertyAttribute.php ├── InterfacePropertyAttribute.php ├── InvalidClassPropertyAttribute.php └── TraitPropertyAttribute.php ├── PropertyRead ├── ClassPropertyReadAttribute.php ├── InterfacePropertyReadAttribute.php ├── InvalidClassPropertyReadAttribute.php └── TraitPropertyReadAttribute.php ├── PropertyWrite ├── ClassPropertyWriteAttribute.php ├── InterfacePropertyWriteAttribute.php ├── InvalidClassPropertyWriteAttribute.php └── TraitPropertyWriteAttribute.php ├── Pure ├── FunctionPureAttribute.php ├── InvalidMethodPureAttribute.php └── MethodPureAttribute.php ├── RequireExtends ├── InvalidTraitRequireExtendsAttribute.php └── TraitRequireExtendsAttribute.php ├── RequireImplements ├── InvalidTraitRequireImplementsAttribute.php └── TraitRequireImplementsAttribute.php ├── Returns ├── FunctionReturnsAttribute.php ├── InvalidMethodReturnsAttribute.php └── MethodReturnsAttribute.php ├── SelfOut ├── InvalidMethodSelfOutAttribute.php └── MethodSelfOutAttribute.php ├── Template ├── ClassTemplateAttribute.php ├── FunctionTemplateAttribute.php ├── InterfaceTemplateAttribute.php ├── InvalidMethodTemplateAttribute.php ├── MethodTemplateAttribute.php └── TraitTemplateAttribute.php ├── TemplateContravariant ├── ClassTemplateContravariantAttribute.php ├── InterfaceTemplateContravariantAttribute.php ├── InvalidClassTemplateContravariantAttribute.php └── TraitTemplateContravariantAttribute.php ├── TemplateCovariant ├── ClassTemplateCovariantAttribute.php ├── InterfaceTemplateCovariantAttribute.php ├── InvalidClassTemplateCovariantAttribute.php └── TraitTemplateCovariantAttribute.php ├── TemplateExtends ├── ClassTemplateExtendsAttribute.php └── InvalidClassTemplateExtendsAttribute.php ├── TemplateImplements ├── InterfaceTemplateImplementsAttribute.php └── InvalidInterfaceTemplateImplementsAttribute.php ├── TemplateUse ├── InvalidTraitTemplateUseAttribute.php └── TraitTemplateUseAttribute.php ├── Throws ├── FunctionThrowsAttribute.php ├── InvalidMethodThrowsAttribute.php └── MethodThrowsAttribute.php └── Type ├── InvalidPropertyTypeAttribute.php └── PropertyTypeAttribute.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [carlos-granados] 2 | -------------------------------------------------------------------------------- /.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" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .idea 4 | .phpunit.cache 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 php-static-analysis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Static Analysis Attributes PHPStan Extension 2 | [![Continuous Integration](https://github.com/php-static-analysis/phpstan-extension/workflows/All%20Tests/badge.svg)](https://github.com/php-static-analysis/phpstan-extension/actions) 3 | [![Latest Stable Version](https://poser.pugx.org/php-static-analysis/phpstan-extension/v/stable)](https://packagist.org/packages/php-static-analysis/phpstan-extension) 4 | [![License](https://poser.pugx.org/php-static-analysis/phpstan-extension/license)](https://github.com/php-static-analysis/phpstan-extension/blob/main/LICENSE) 5 | [![Total Downloads](https://poser.pugx.org/php-static-analysis/phpstan-extension/downloads)](https://packagist.org/packages/php-static-analysis/phpstan-extension/stats) 6 | 7 | Since the release of PHP 8.0 more and more libraries, frameworks and tools have been updated to use attributes instead of annotations in PHPDocs. 8 | 9 | However, static analysis tools like PHPStan have not made this transition to attributes and they still rely on annotations in PHPDocs for a lot of their functionality. 10 | 11 | This is a PHPStan extension that allows PHPStan to understand a new set of attributes that replace the PHPDoc annotations. These attributes are defined in [this repository](https://github.com/php-static-analysis/attributes) 12 | 13 | ## Example 14 | 15 | In order to show how code would look with these attributes, we can look at the following example. This is how a class looks like with the current annotations: 16 | 17 | ```php 18 | */ 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ecs.php: -------------------------------------------------------------------------------- 1 | withPaths([ 10 | __DIR__ . '/src', 11 | __DIR__ . '/tests', 12 | ]) 13 | ->withPreparedSets( 14 | psr12: true, 15 | ); 16 | -------------------------------------------------------------------------------- /extension.neon: -------------------------------------------------------------------------------- 1 | services: 2 | attributeParser: 3 | class: PhpStaticAnalysis\PHPStanExtension\Parser\AttributeParser 4 | arguments: 5 | parser: @pathRoutingParser 6 | autowired: false 7 | 8 | defaultAnalysisParser: 9 | class: PHPStan\Parser\CachedParser 10 | arguments: 11 | originalParser: @attributeParser 12 | cachedNodesByStringCountMax: %cache.nodesByStringCountMax% 13 | autowired: false 14 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - extension.neon 3 | 4 | parameters: 5 | level: max 6 | paths: 7 | - src 8 | - tests 9 | excludePaths: 10 | - tests/data/* 11 | ignoreErrors: 12 | - 13 | identifier: phpstanApi.classConstant 14 | path: tests/BaseAttributeTestCase.php 15 | - 16 | identifier: phpstanApi.method 17 | path: tests/BaseAttributeTestCase.php 18 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | tests 17 | 18 | 19 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /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/AssertAttributeTest.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/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/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/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/DeprecatedAttributeTest.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/ImmutableAttributeTest.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/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/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/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/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/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/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/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/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/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/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/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/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/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/SelfOutAttributeTest.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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/conf/deprecated.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - ../../vendor/phpstan/phpstan-deprecation-rules/rules.neon 3 | -------------------------------------------------------------------------------- /tests/conf/readonly.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | featureToggles: 3 | readOnlyByPhpDoc: true 4 | -------------------------------------------------------------------------------- /tests/conf/throws.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | exceptions: 3 | check: 4 | tooWideThrowType: true -------------------------------------------------------------------------------- /tests/data/Assert/FunctionAssertAttribute.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/AssertIfTrue/FunctionAssertIfTrueAttribute.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/DefineType/ClassDefineTypeAttribute.php: -------------------------------------------------------------------------------- 1 | returnDeprecated(); 32 | -------------------------------------------------------------------------------- /tests/data/Deprecated/PropertyDeprecatedAttribute.php: -------------------------------------------------------------------------------- 1 | name = 'John'; 15 | -------------------------------------------------------------------------------- /tests/data/Immutable/InterfaceImmutableAttribute.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/Internal/FunctionInternalAttribute.php: -------------------------------------------------------------------------------- 1 | invalidProperty = 'Mike'; 25 | $this->otherInvalidProperty = 'John'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/data/IsReadOnly/PropertyIsReadOnlyAttribute.php: -------------------------------------------------------------------------------- 1 | name = 'Mike'; 15 | } 16 | } 17 | 18 | $p = new PropertyIsReadOnlyAttribute(); 19 | $p->name = 'John'; 20 | -------------------------------------------------------------------------------- /tests/data/Method/ClassMethodAttribute.php: -------------------------------------------------------------------------------- 1 | getString(); 28 | $class->setString($foo); 29 | $bar = ClassMethodAttribute::staticGetter(); 30 | -------------------------------------------------------------------------------- /tests/data/Method/InterfaceMethodAttribute.php: -------------------------------------------------------------------------------- 1 | badFunction(); 32 | -------------------------------------------------------------------------------- /tests/data/Method/TraitMethodAttribute.php: -------------------------------------------------------------------------------- 1 | $name(...$arguments); 32 | } 33 | } 34 | 35 | $proxy = new ClassMixinAttributeProxy(); 36 | $proxy->proxied(); 37 | -------------------------------------------------------------------------------- /tests/data/Mixin/InterfaceMixinAttribute.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/Property/InterfacePropertyAttribute.php: -------------------------------------------------------------------------------- 1 | name; 25 | $bar = $class->age; 26 | $indexes = $class->index1 + $class->index2; 27 | -------------------------------------------------------------------------------- /tests/data/PropertyRead/InterfacePropertyReadAttribute.php: -------------------------------------------------------------------------------- 1 | age = 7; 22 | -------------------------------------------------------------------------------- /tests/data/PropertyRead/TraitPropertyReadAttribute.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 | -------------------------------------------------------------------------------- /tests/data/PropertyWrite/InterfacePropertyWriteAttribute.php: -------------------------------------------------------------------------------- 1 | age; 22 | -------------------------------------------------------------------------------- /tests/data/PropertyWrite/TraitPropertyWriteAttribute.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/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/data/Template/ClassTemplateAttribute.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/TemplateContravariant/InterfaceTemplateContravariantAttribute.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 | -------------------------------------------------------------------------------- /tests/data/TemplateCovariant/InterfaceTemplateCovariantAttribute.php: -------------------------------------------------------------------------------- 1 | ')] // this class extends the base template 14 | class ClassTemplateExtendsAttributeChild extends ClassTemplateExtendsAttribute 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /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/data/TemplateImplements/InterfaceTemplateImplementsAttribute.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/TemplateImplements/InvalidInterfaceTemplateImplementsAttribute.php: -------------------------------------------------------------------------------- 1 | ')] 22 | public string $name = ''; 23 | } 24 | -------------------------------------------------------------------------------- /tests/data/TemplateUse/InvalidTraitTemplateUseAttribute.php: -------------------------------------------------------------------------------- 1 | ')] 25 | public string $name = ''; 26 | } 27 | -------------------------------------------------------------------------------- /tests/data/TemplateUse/TraitTemplateUseAttribute.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/Throws/FunctionThrowsAttribute.php: -------------------------------------------------------------------------------- 1 |