Update dependency tomasvotruba/type-coverage to ^2.3.0
This commit is contained in:
parent
724ed522d7
commit
c8c60658d5
|
|
@ -3,7 +3,7 @@
|
||||||
"phpstan/phpstan": "^2.2.7",
|
"phpstan/phpstan": "^2.2.7",
|
||||||
"phpstan/extension-installer": "^1.4.3",
|
"phpstan/extension-installer": "^1.4.3",
|
||||||
"phpstan/phpstan-strict-rules": "^2.0.12",
|
"phpstan/phpstan-strict-rules": "^2.0.12",
|
||||||
"tomasvotruba/type-coverage": "^2.2.2",
|
"tomasvotruba/type-coverage": "^2.3.0",
|
||||||
"ergebnis/phpstan-rules": "^2.13.1"
|
"ergebnis/phpstan-rules": "^2.13.1"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f960036a066807c312a2d48672c1e045",
|
"content-hash": "3579343022b2a28c7195fe30b1752bfb",
|
||||||
"packages": [],
|
"packages": [],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
|
|
@ -246,29 +246,31 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tomasvotruba/type-coverage",
|
"name": "tomasvotruba/type-coverage",
|
||||||
"version": "2.2.2",
|
"version": "2.3.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/TomasVotruba/type-coverage.git",
|
"url": "https://github.com/TomasVotruba/type-coverage.git",
|
||||||
"reference": "25f298265c823e8fb0505169135855e1e6a91021"
|
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/25f298265c823e8fb0505169135855e1e6a91021",
|
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||||
"reference": "25f298265c823e8fb0505169135855e1e6a91021",
|
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.4",
|
"php": "^8.4",
|
||||||
"phpstan/phpstan": "^2.2"
|
"phpstan/phpstan": "^2.2",
|
||||||
|
"webmozart/assert": "^1.11 || ^2.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/extension-installer": "^1.4",
|
"phpstan/extension-installer": "^1.4",
|
||||||
"phpunit/phpunit": "^12.5",
|
"phpunit/phpunit": "^13.2",
|
||||||
"rector/jack": "^1.0",
|
"rector/jack": "^1.0",
|
||||||
"rector/rector": "^2.4",
|
"rector/rector": "^2.5",
|
||||||
"shipmonk/composer-dependency-analyser": "^1.8",
|
"shipmonk/composer-dependency-analyser": "^1.8",
|
||||||
"symplify/easy-coding-standard": "^13.1",
|
"symfony/dom-crawler": "^8.1",
|
||||||
|
"symplify/easy-coding-standard": "^13.2",
|
||||||
"tomasvotruba/unused-public": "^2.2",
|
"tomasvotruba/unused-public": "^2.2",
|
||||||
"tracy/tracy": "^2.12"
|
"tracy/tracy": "^2.12"
|
||||||
},
|
},
|
||||||
|
|
@ -276,12 +278,14 @@
|
||||||
"extra": {
|
"extra": {
|
||||||
"phpstan": {
|
"phpstan": {
|
||||||
"includes": [
|
"includes": [
|
||||||
"config/extension.neon"
|
"config/extension.neon",
|
||||||
|
"packages/type-perfect/config/extension.neon"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
"Rector\\TypePerfect\\": "packages/type-perfect/src",
|
||||||
"TomasVotruba\\TypeCoverage\\": "src"
|
"TomasVotruba\\TypeCoverage\\": "src"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -296,7 +300,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/TomasVotruba/type-coverage/issues",
|
"issues": "https://github.com/TomasVotruba/type-coverage/issues",
|
||||||
"source": "https://github.com/TomasVotruba/type-coverage/tree/2.2.2"
|
"source": "https://github.com/TomasVotruba/type-coverage/tree/2.3.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -308,7 +312,73 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-06-07T12:35:00+00:00"
|
"time": "2026-07-29T20:32:37+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "webmozart/assert",
|
||||||
|
"version": "2.4.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/webmozarts/assert.git",
|
||||||
|
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||||
|
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-date": "*",
|
||||||
|
"ext-filter": "*",
|
||||||
|
"php": "^8.2"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-intl": "",
|
||||||
|
"ext-simplexml": "",
|
||||||
|
"ext-spl": ""
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"psalm": {
|
||||||
|
"pluginClass": "Webmozart\\Assert\\PsalmPlugin"
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.0-dev",
|
||||||
|
"dev-feature/2-0": "2.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Webmozart\\Assert\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Bernhard Schussek",
|
||||||
|
"email": "bschussek@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Woody Gilk",
|
||||||
|
"email": "woody.gilk@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Assertions to validate method input/output with nice error messages.",
|
||||||
|
"keywords": [
|
||||||
|
"assert",
|
||||||
|
"check",
|
||||||
|
"validate"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/webmozarts/assert/issues",
|
||||||
|
"source": "https://github.com/webmozarts/assert/tree/2.4.1"
|
||||||
|
},
|
||||||
|
"time": "2026-06-15T15:31:57+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
|
|
|
||||||
|
|
@ -251,45 +251,49 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tomasvotruba/type-coverage",
|
"name": "tomasvotruba/type-coverage",
|
||||||
"version": "2.2.2",
|
"version": "2.3.0",
|
||||||
"version_normalized": "2.2.2.0",
|
"version_normalized": "2.3.0.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/TomasVotruba/type-coverage.git",
|
"url": "https://github.com/TomasVotruba/type-coverage.git",
|
||||||
"reference": "25f298265c823e8fb0505169135855e1e6a91021"
|
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/25f298265c823e8fb0505169135855e1e6a91021",
|
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||||
"reference": "25f298265c823e8fb0505169135855e1e6a91021",
|
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.4",
|
"php": "^8.4",
|
||||||
"phpstan/phpstan": "^2.2"
|
"phpstan/phpstan": "^2.2",
|
||||||
|
"webmozart/assert": "^1.11 || ^2.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/extension-installer": "^1.4",
|
"phpstan/extension-installer": "^1.4",
|
||||||
"phpunit/phpunit": "^12.5",
|
"phpunit/phpunit": "^13.2",
|
||||||
"rector/jack": "^1.0",
|
"rector/jack": "^1.0",
|
||||||
"rector/rector": "^2.4",
|
"rector/rector": "^2.5",
|
||||||
"shipmonk/composer-dependency-analyser": "^1.8",
|
"shipmonk/composer-dependency-analyser": "^1.8",
|
||||||
"symplify/easy-coding-standard": "^13.1",
|
"symfony/dom-crawler": "^8.1",
|
||||||
|
"symplify/easy-coding-standard": "^13.2",
|
||||||
"tomasvotruba/unused-public": "^2.2",
|
"tomasvotruba/unused-public": "^2.2",
|
||||||
"tracy/tracy": "^2.12"
|
"tracy/tracy": "^2.12"
|
||||||
},
|
},
|
||||||
"time": "2026-06-07T12:35:00+00:00",
|
"time": "2026-07-29T20:32:37+00:00",
|
||||||
"type": "phpstan-extension",
|
"type": "phpstan-extension",
|
||||||
"extra": {
|
"extra": {
|
||||||
"phpstan": {
|
"phpstan": {
|
||||||
"includes": [
|
"includes": [
|
||||||
"config/extension.neon"
|
"config/extension.neon",
|
||||||
|
"packages/type-perfect/config/extension.neon"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"installation-source": "dist",
|
"installation-source": "dist",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
"Rector\\TypePerfect\\": "packages/type-perfect/src",
|
||||||
"TomasVotruba\\TypeCoverage\\": "src"
|
"TomasVotruba\\TypeCoverage\\": "src"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -304,7 +308,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/TomasVotruba/type-coverage/issues",
|
"issues": "https://github.com/TomasVotruba/type-coverage/issues",
|
||||||
"source": "https://github.com/TomasVotruba/type-coverage/tree/2.2.2"
|
"source": "https://github.com/TomasVotruba/type-coverage/tree/2.3.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -317,6 +321,75 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"install-path": "../tomasvotruba/type-coverage"
|
"install-path": "../tomasvotruba/type-coverage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "webmozart/assert",
|
||||||
|
"version": "2.4.1",
|
||||||
|
"version_normalized": "2.4.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/webmozarts/assert.git",
|
||||||
|
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||||
|
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-date": "*",
|
||||||
|
"ext-filter": "*",
|
||||||
|
"php": "^8.2"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-intl": "",
|
||||||
|
"ext-simplexml": "",
|
||||||
|
"ext-spl": ""
|
||||||
|
},
|
||||||
|
"time": "2026-06-15T15:31:57+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"psalm": {
|
||||||
|
"pluginClass": "Webmozart\\Assert\\PsalmPlugin"
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.0-dev",
|
||||||
|
"dev-feature/2-0": "2.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Webmozart\\Assert\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Bernhard Schussek",
|
||||||
|
"email": "bschussek@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Woody Gilk",
|
||||||
|
"email": "woody.gilk@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Assertions to validate method input/output with nice error messages.",
|
||||||
|
"keywords": [
|
||||||
|
"assert",
|
||||||
|
"check",
|
||||||
|
"validate"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/webmozarts/assert/issues",
|
||||||
|
"source": "https://github.com/webmozarts/assert/tree/2.4.1"
|
||||||
|
},
|
||||||
|
"install-path": "../webmozart/assert"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
@ -325,6 +398,7 @@
|
||||||
"phpstan/extension-installer",
|
"phpstan/extension-installer",
|
||||||
"phpstan/phpstan",
|
"phpstan/phpstan",
|
||||||
"phpstan/phpstan-strict-rules",
|
"phpstan/phpstan-strict-rules",
|
||||||
"tomasvotruba/type-coverage"
|
"tomasvotruba/type-coverage",
|
||||||
|
"webmozart/assert"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
'name' => '__root__',
|
'name' => '__root__',
|
||||||
'pretty_version' => '7.0.x-dev',
|
'pretty_version' => '7.0.x-dev',
|
||||||
'version' => '7.0.9999999.9999999-dev',
|
'version' => '7.0.9999999.9999999-dev',
|
||||||
'reference' => '0107b5050a17e24a8d66433ce851e34c2144dc30',
|
'reference' => '724ed522d74918b0d84a50cdee4d0d2f7a5ba67c',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
'__root__' => array(
|
'__root__' => array(
|
||||||
'pretty_version' => '7.0.x-dev',
|
'pretty_version' => '7.0.x-dev',
|
||||||
'version' => '7.0.9999999.9999999-dev',
|
'version' => '7.0.9999999.9999999-dev',
|
||||||
'reference' => '0107b5050a17e24a8d66433ce851e34c2144dc30',
|
'reference' => '724ed522d74918b0d84a50cdee4d0d2f7a5ba67c',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
@ -56,13 +56,22 @@
|
||||||
'dev_requirement' => true,
|
'dev_requirement' => true,
|
||||||
),
|
),
|
||||||
'tomasvotruba/type-coverage' => array(
|
'tomasvotruba/type-coverage' => array(
|
||||||
'pretty_version' => '2.2.2',
|
'pretty_version' => '2.3.0',
|
||||||
'version' => '2.2.2.0',
|
'version' => '2.3.0.0',
|
||||||
'reference' => '25f298265c823e8fb0505169135855e1e6a91021',
|
'reference' => '7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644',
|
||||||
'type' => 'phpstan-extension',
|
'type' => 'phpstan-extension',
|
||||||
'install_path' => __DIR__ . '/../tomasvotruba/type-coverage',
|
'install_path' => __DIR__ . '/../tomasvotruba/type-coverage',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => true,
|
'dev_requirement' => true,
|
||||||
),
|
),
|
||||||
|
'webmozart/assert' => array(
|
||||||
|
'pretty_version' => '2.4.1',
|
||||||
|
'version' => '2.4.1.0',
|
||||||
|
'reference' => '2ccb7c2e821038c03a3e6e1700c570c158c55f70',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../webmozart/assert',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -138,3 +138,406 @@ vendor/bin/phpstan
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Happy coding!
|
Happy coding!
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Type Perfect
|
||||||
|
|
||||||
|
[](https://packagist.org/packages/rector/type-perfect/stats)
|
||||||
|
|
||||||
|
Next level type declaration check PHPStan rules.
|
||||||
|
|
||||||
|
We use these sets to improve code quality of our clients' code beyond PHPStan features.
|
||||||
|
|
||||||
|
* These rules make skipped object types explicit, param types narrow and help you to fill more accurate object type hints.
|
||||||
|
* **They're easy to enable, even if your code does not pass level 0**
|
||||||
|
* They're effortless to resolve and make your code instantly more solid and reliable.
|
||||||
|
|
||||||
|
If you care about code quality and type safety, add these 10 rules to your CI.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
These rules ship with the package [installed above](#install), there is nothing else to require.
|
||||||
|
|
||||||
|
*Migrating from `rector/type-perfect`? Remove it from your `composer.json`, the rules live here now.*
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Every rule is opt-in and disabled by default, so you can pick the ones that fit your project. See [Configure](#configure) below for the full list.
|
||||||
|
|
||||||
|
The 3 checks below are the simplest ones to start with:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
type_perfect:
|
||||||
|
no_isset_on_object: true
|
||||||
|
no_empty_on_object: true
|
||||||
|
no_array_access_on_object: true
|
||||||
|
no_param_type_removal: true
|
||||||
|
```
|
||||||
|
|
||||||
|
The first one makes sure we don't miss a chance to use `instanceof` to make further code know about exact object type:
|
||||||
|
|
||||||
|
```php
|
||||||
|
private ?SomeType $someType = null;
|
||||||
|
|
||||||
|
if (! empty($this->someType)) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($this->someType)) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we only know, that $this->someType is not empty/null
|
||||||
|
```
|
||||||
|
|
||||||
|
:no_good:
|
||||||
|
|
||||||
|
↓
|
||||||
|
|
||||||
|
|
||||||
|
```php
|
||||||
|
if (! $this->someType instanceof SomeType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we know $this->someType is exactly SomeType
|
||||||
|
```
|
||||||
|
|
||||||
|
:heavy_check_mark:
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Second rule (`no_array_access_on_object`) checks we use explicit object methods over magic array access:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$article = new Article();
|
||||||
|
|
||||||
|
$id = $article['id'];
|
||||||
|
// we have no idea, what the type is
|
||||||
|
```
|
||||||
|
|
||||||
|
:no_good:
|
||||||
|
|
||||||
|
↓
|
||||||
|
|
||||||
|
```php
|
||||||
|
$id = $article->getId();
|
||||||
|
// we know the type is int
|
||||||
|
```
|
||||||
|
|
||||||
|
:heavy_check_mark:
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Last rule (`no_param_type_removal`) checks that all interface implementations follow the same method signature as the interface:
|
||||||
|
|
||||||
|
```php
|
||||||
|
interface SomeInterface
|
||||||
|
{
|
||||||
|
public function doSomething(int $value): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class SomeClass implements SomeInterface
|
||||||
|
{
|
||||||
|
public function doSomething($value): void { // ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:no_good:
|
||||||
|
|
||||||
|
↓
|
||||||
|
|
||||||
|
```php
|
||||||
|
final class SomeClass implements SomeInterface
|
||||||
|
{
|
||||||
|
public function doSomething(int $value): void { // ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:heavy_check_mark:
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
|
||||||
|
All rules are enabled by configuration and disabled by default. We take them from the simplest to more powerful, in the same order we apply them on legacy projects.
|
||||||
|
|
||||||
|
You can enable them all at once:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
type_perfect:
|
||||||
|
# the 3 checks above
|
||||||
|
no_isset_on_object: true
|
||||||
|
no_empty_on_object: true
|
||||||
|
no_array_access_on_object: true
|
||||||
|
no_param_type_removal: true
|
||||||
|
|
||||||
|
no_mixed_property: true
|
||||||
|
no_mixed_caller: true
|
||||||
|
null_over_false: true
|
||||||
|
narrow_param: true
|
||||||
|
narrow_return: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Or one by one:
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 1. Null over False
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
type_perfect:
|
||||||
|
null_over_false: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Bool types are typically used for on/off, yes/no responses. But sometimes, the `false` is misused as *no-result* response, where `null` would be more accurate:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function getProduct()
|
||||||
|
{
|
||||||
|
if (...) {
|
||||||
|
return $product;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:no_good:
|
||||||
|
|
||||||
|
↓
|
||||||
|
|
||||||
|
We should use `null` instead, as it enabled strict type declaration in form of `?Product` since PHP 7.1:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function getProduct(): ?Product
|
||||||
|
{
|
||||||
|
if (...) {
|
||||||
|
return $product;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:heavy_check_mark:
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 2. No mixed Property
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
type_perfect:
|
||||||
|
no_mixed_property: true
|
||||||
|
```
|
||||||
|
|
||||||
|
This rule focuses on PHPStan blind spot while fetching a property. If we have a property with unknown type, PHPStan is not be able to analyse it. It silently ignores it.
|
||||||
|
|
||||||
|
```php
|
||||||
|
private $someType;
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->someType->vale;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It doesn't see there is a typo in `vale` property name. It should be `value`
|
||||||
|
|
||||||
|
:no_good:
|
||||||
|
|
||||||
|
↓
|
||||||
|
|
||||||
|
|
||||||
|
```php
|
||||||
|
private SomeType $someType;
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->someType->value;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This rule makes sure all property fetches know their type they're called on.
|
||||||
|
|
||||||
|
:heavy_check_mark:
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 3. No mixed Caller
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
type_perfect:
|
||||||
|
no_mixed_caller: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Same as above, only for method calls:
|
||||||
|
|
||||||
|
```php
|
||||||
|
private $someType;
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->someType->someMetho(1, 2);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It doesn't see there is a typo in `someMetho` name, and that the 2nd parameter must be `string`.
|
||||||
|
|
||||||
|
:no_good:
|
||||||
|
|
||||||
|
↓
|
||||||
|
|
||||||
|
|
||||||
|
```php
|
||||||
|
private SomeType $someType;
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->someType->someMethod(1, 'active');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This group makes sure methods call know their type they're called on.
|
||||||
|
|
||||||
|
:heavy_check_mark:
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 4. Narrow Param Types
|
||||||
|
|
||||||
|
The more narrow param type we have, the reliable the code is. `string` beats `mixed`, `int` beats `scalar` and `ExactObject` beats `stdClass`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
type_perfect:
|
||||||
|
narrow_param: true
|
||||||
|
```
|
||||||
|
|
||||||
|
In case of `private`, but also `public` method calls, our project often knows exact types that are passed in it:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// in one file
|
||||||
|
$product->addPrice(100.52);
|
||||||
|
|
||||||
|
// another file
|
||||||
|
$product->addPrice(52.05);
|
||||||
|
```
|
||||||
|
|
||||||
|
But out of from fear and "just to be safe", we keep the `addPrice()` param type empty, `mixed` or in a docblock.
|
||||||
|
|
||||||
|
:no_good:
|
||||||
|
|
||||||
|
↓
|
||||||
|
|
||||||
|
If, in 100 % cases the `float` type is passed, PHPStan knows it can be added and improve further analysis:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
|
||||||
|
-/**
|
||||||
|
- * @param float $price
|
||||||
|
- */
|
||||||
|
-public function addPrice($price)
|
||||||
|
+public function addPrice(float $price)
|
||||||
|
{
|
||||||
|
$this->price = $price;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's where this group comes in. It checks all the passed types, and tells us know how to narrow the param type declaration.
|
||||||
|
|
||||||
|
:heavy_check_mark:
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 5. Narrow Return Types
|
||||||
|
|
||||||
|
Last but not least, the more narrow return type, the more reliable the code.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
type_perfect:
|
||||||
|
narrow_return: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Where does it help? Let's say we have 2 types of talks, that do have different behavior:
|
||||||
|
|
||||||
|
```php
|
||||||
|
final class ConferenceTalk extends Talk
|
||||||
|
{
|
||||||
|
public function bookHotel()
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MeetupTalk extends Talk
|
||||||
|
{
|
||||||
|
public function bookTrain()
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then we have a factory (repository, or services) that returns generic `Talk` type:
|
||||||
|
|
||||||
|
```php
|
||||||
|
final class TalkFactory
|
||||||
|
{
|
||||||
|
public function createConferenceTalk(): Talk
|
||||||
|
{
|
||||||
|
return new ConferenceTalk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createMeetupTalk(): Talk
|
||||||
|
{
|
||||||
|
return new MeetupTalk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case we've just lost strict type and have to verify the type on runtime:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$talk instanceof ConferenceTalk
|
||||||
|
```
|
||||||
|
|
||||||
|
:no_good:
|
||||||
|
|
||||||
|
↓
|
||||||
|
|
||||||
|
That's where this group comes in. In case we return the exact type, we should use exact type in return type declaration to keep the code as reliable as possible:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
final class TalkFactory
|
||||||
|
{
|
||||||
|
- public function createConferenceTalk(): Talk
|
||||||
|
+ public function createConferenceTalk(): ConferenceTalk
|
||||||
|
{
|
||||||
|
return new ConferenceTalk();
|
||||||
|
}
|
||||||
|
|
||||||
|
- public function createMeetupTalk(): Talk
|
||||||
|
+ public function createMeetupTalk(): MeetupTalk
|
||||||
|
{
|
||||||
|
return new MeetupTalk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:heavy_check_mark:
|
||||||
|
|
||||||
|
Add sets one by one, fix what you find helpful and ignore the rest.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Happy coding!
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,9 @@ use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
|
||||||
|
|
||||||
return new Configuration()
|
return new Configuration()
|
||||||
// PhpParser classes are provided by phpstan/phpstan, no need to require nikic/php-parser directly
|
// PhpParser classes are provided by phpstan/phpstan, no need to require nikic/php-parser directly
|
||||||
->ignoreErrorsOnPackage('nikic/php-parser', [ErrorType::SHADOW_DEPENDENCY]);
|
->ignoreErrorsOnPackage('nikic/php-parser', [ErrorType::SHADOW_DEPENDENCY])
|
||||||
|
// type-perfect test fixtures are static analysis inputs, not real code, and reference 3rd-party classes on purpose
|
||||||
|
->ignoreErrorsOnPath(
|
||||||
|
__DIR__ . '/packages/type-perfect/tests',
|
||||||
|
[ErrorType::UNKNOWN_CLASS, ErrorType::SHADOW_DEPENDENCY]
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,30 @@
|
||||||
"keywords": ["static analysis", "phpstan-extension"],
|
"keywords": ["static analysis", "phpstan-extension"],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.4",
|
"php": "^8.4",
|
||||||
"phpstan/phpstan": "^2.2"
|
"phpstan/phpstan": "^2.2",
|
||||||
|
"webmozart/assert": "^1.11 || ^2.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/extension-installer": "^1.4",
|
"phpstan/extension-installer": "^1.4",
|
||||||
"phpunit/phpunit": "^12.5",
|
"phpunit/phpunit": "^13.2",
|
||||||
"symplify/easy-coding-standard": "^13.1",
|
"symplify/easy-coding-standard": "^13.2",
|
||||||
"rector/rector": "^2.4",
|
"rector/rector": "^2.5",
|
||||||
"tracy/tracy": "^2.12",
|
"tracy/tracy": "^2.12",
|
||||||
"tomasvotruba/unused-public": "^2.2",
|
"tomasvotruba/unused-public": "^2.2",
|
||||||
"rector/jack": "^1.0",
|
"rector/jack": "^1.0",
|
||||||
"shipmonk/composer-dependency-analyser": "^1.8"
|
"shipmonk/composer-dependency-analyser": "^1.8",
|
||||||
|
"symfony/dom-crawler": "^8.1"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"TomasVotruba\\TypeCoverage\\": "src"
|
"TomasVotruba\\TypeCoverage\\": "src",
|
||||||
|
"Rector\\TypePerfect\\": "packages/type-perfect/src"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"TomasVotruba\\TypeCoverage\\Tests\\": "tests"
|
"TomasVotruba\\TypeCoverage\\Tests\\": "tests",
|
||||||
|
"Rector\\TypePerfect\\Tests\\": "packages/type-perfect/tests"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -43,7 +47,8 @@
|
||||||
"extra": {
|
"extra": {
|
||||||
"phpstan": {
|
"phpstan": {
|
||||||
"includes": [
|
"includes": [
|
||||||
"config/extension.neon"
|
"config/extension.neon",
|
||||||
|
"packages/type-perfect/config/extension.neon"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/LICENSE
vendored
Normal file
25
tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
The MIT License
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Copyright (c) 2020 Tomas Votruba (https://tomasvotruba.com)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
94
tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/config/extension.neon
vendored
Normal file
94
tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/config/extension.neon
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
parametersSchema:
|
||||||
|
# see https://doc.nette.org/en/schema for configuration
|
||||||
|
type_perfect: structure([
|
||||||
|
narrow_param: bool()
|
||||||
|
narrow_return: bool()
|
||||||
|
no_mixed: bool()
|
||||||
|
null_over_false: bool()
|
||||||
|
|
||||||
|
# replace for no_mixed
|
||||||
|
no_mixed_property: bool()
|
||||||
|
no_mixed_caller: bool()
|
||||||
|
|
||||||
|
no_param_type_removal: bool()
|
||||||
|
no_array_access_on_object: bool()
|
||||||
|
no_isset_on_object: bool()
|
||||||
|
no_empty_on_object: bool()
|
||||||
|
])
|
||||||
|
|
||||||
|
# defaults
|
||||||
|
parameters:
|
||||||
|
type_perfect:
|
||||||
|
narrow_param: false
|
||||||
|
narrow_return: false
|
||||||
|
no_mixed: false
|
||||||
|
null_over_false: false
|
||||||
|
# priority override of no_mixed
|
||||||
|
no_mixed_property: false
|
||||||
|
no_mixed_caller: false
|
||||||
|
|
||||||
|
no_param_type_removal: false
|
||||||
|
no_array_access_on_object: false
|
||||||
|
no_isset_on_object: false
|
||||||
|
no_empty_on_object: false
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# turn on by the group name
|
||||||
|
|
||||||
|
# no_param_type_removal
|
||||||
|
- Rector\TypePerfect\Rules\NoParamTypeRemovalRule
|
||||||
|
|
||||||
|
# no_array_access_on_object
|
||||||
|
- Rector\TypePerfect\Rules\NoArrayAccessOnObjectRule
|
||||||
|
|
||||||
|
# no_isset_on_object
|
||||||
|
- Rector\TypePerfect\Rules\NoIssetOnObjectRule
|
||||||
|
|
||||||
|
# no_empty_on_object
|
||||||
|
- Rector\TypePerfect\Rules\NoEmptyOnObjectRule
|
||||||
|
|
||||||
|
# narrow_param
|
||||||
|
- Rector\TypePerfect\Rules\NarrowPublicClassMethodParamTypeRule
|
||||||
|
- Rector\TypePerfect\Rules\NarrowPrivateClassMethodParamTypeRule
|
||||||
|
|
||||||
|
# narrow_return
|
||||||
|
- Rector\TypePerfect\Rules\NarrowReturnObjectTypeRule
|
||||||
|
|
||||||
|
# no_mixed
|
||||||
|
- Rector\TypePerfect\Rules\NoMixedPropertyFetcherRule
|
||||||
|
- Rector\TypePerfect\Rules\NoMixedMethodCallerRule
|
||||||
|
|
||||||
|
# null_over_false
|
||||||
|
- Rector\TypePerfect\Rules\ReturnNullOverFalseRule
|
||||||
|
|
||||||
|
services:
|
||||||
|
-
|
||||||
|
factory: Rector\TypePerfect\Configuration
|
||||||
|
arguments:
|
||||||
|
- %type_perfect%
|
||||||
|
|
||||||
|
- Rector\TypePerfect\NodeFinder\ClassMethodNodeFinder
|
||||||
|
- Rector\TypePerfect\NodeFinder\MethodCallNodeFinder
|
||||||
|
- Rector\TypePerfect\NodeFinder\ReturnNodeFinder
|
||||||
|
- Rector\TypePerfect\PhpDoc\ApiDocStmtAnalyzer
|
||||||
|
- Rector\TypePerfect\Printer\NodeComparator
|
||||||
|
- Rector\TypePerfect\Reflection\ReflectionParser
|
||||||
|
- Rector\TypePerfect\Reflection\MethodNodeAnalyser
|
||||||
|
- Rector\TypePerfect\Matcher\Collector\PublicClassMethodMatcher
|
||||||
|
- Rector\TypePerfect\Matcher\ClassMethodCallReferenceResolver
|
||||||
|
- Rector\TypePerfect\Printer\CollectorMetadataPrinter
|
||||||
|
- Rector\TypePerfect\Guard\EmptyIssetGuard
|
||||||
|
|
||||||
|
# for NarrowPublicClassMethodParamTypeByCallerTypeRule
|
||||||
|
-
|
||||||
|
class: Rector\TypePerfect\Collector\ClassMethod\PublicClassMethodParamTypesCollector
|
||||||
|
tags: [phpstan.collector]
|
||||||
|
|
||||||
|
-
|
||||||
|
class: Rector\TypePerfect\Collector\MethodCall\MethodCallArgTypesCollector
|
||||||
|
tags: [phpstan.collector]
|
||||||
|
|
||||||
|
-
|
||||||
|
class: Rector\TypePerfect\Collector\MethodCallableNode\MethodCallableCollector
|
||||||
|
tags:
|
||||||
|
- phpstan.collector
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Collector\ClassMethod;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Collectors\Collector;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
use Rector\TypePerfect\Matcher\Collector\PublicClassMethodMatcher;
|
||||||
|
use Rector\TypePerfect\PhpDoc\ApiDocStmtAnalyzer;
|
||||||
|
use Rector\TypePerfect\Printer\CollectorMetadataPrinter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements Collector<ClassMethod, array{class-string, string, string, int}>
|
||||||
|
*/
|
||||||
|
final readonly class PublicClassMethodParamTypesCollector implements Collector
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ApiDocStmtAnalyzer $apiDocStmtAnalyzer,
|
||||||
|
private PublicClassMethodMatcher $publicClassMethodMatcher,
|
||||||
|
private CollectorMetadataPrinter $collectorMetadataPrinter,
|
||||||
|
private Configuration $configuration
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return ClassMethod::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ClassMethod $node
|
||||||
|
* @return array{class-string, string, string, int}|null
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): ?array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNarrowParamEnabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node->params === []) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->publicClassMethodMatcher->shouldSkipClassMethod($node)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only if the class has no parents/implementers, to avoid class method required by contracts
|
||||||
|
$classReflection = $scope->getClassReflection();
|
||||||
|
if (! $classReflection instanceof ClassReflection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->apiDocStmtAnalyzer->hasApiDoc($node, $classReflection)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->publicClassMethodMatcher->shouldSkipClassReflection($classReflection)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodName = $node->name->toString();
|
||||||
|
|
||||||
|
// is this method required by parent contract? skip it
|
||||||
|
if ($this->publicClassMethodMatcher->isUsedByParentClassOrInterface($classReflection, $methodName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$printedParamTypesString = $this->collectorMetadataPrinter->printParamTypesToString(
|
||||||
|
$node,
|
||||||
|
$classReflection,
|
||||||
|
$scope
|
||||||
|
);
|
||||||
|
return [$classReflection->getName(), $methodName, $printedParamTypesString, $node->getLine()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Collector\MethodCall;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PhpParser\Node\Identifier;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Collectors\Collector;
|
||||||
|
use PHPStan\Reflection\ExtendedMethodReflection;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
use Rector\TypePerfect\Matcher\ClassMethodCallReferenceResolver;
|
||||||
|
use Rector\TypePerfect\Printer\CollectorMetadataPrinter;
|
||||||
|
use Rector\TypePerfect\ValueObject\MethodCallReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements Collector<MethodCall, array{string, string}>
|
||||||
|
*/
|
||||||
|
final readonly class MethodCallArgTypesCollector implements Collector
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ClassMethodCallReferenceResolver $classMethodCallReferenceResolver,
|
||||||
|
private CollectorMetadataPrinter $collectorMetadataPrinter,
|
||||||
|
private Configuration $configuration
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return MethodCall::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param MethodCall $node
|
||||||
|
* @return array{string, string}|null
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): ?array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNarrowParamEnabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node->isFirstClassCallable() || $node->getArgs() === [] || ! $node->name instanceof Identifier) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodCalledOnType = $scope->getType($node->var);
|
||||||
|
$methodReflection = $scope->getMethodReflection($methodCalledOnType, $node->name->name);
|
||||||
|
if (! $methodReflection instanceof ExtendedMethodReflection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMethodCallReference = $this->classMethodCallReferenceResolver->resolve($node, $scope, true);
|
||||||
|
if (! $classMethodCallReference instanceof MethodCallReference) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMethodReference = $this->createClassMethodReference($classMethodCallReference);
|
||||||
|
|
||||||
|
$stringArgTypesString = $this->collectorMetadataPrinter->printArgTypesAsString(
|
||||||
|
$node,
|
||||||
|
$methodReflection,
|
||||||
|
$scope
|
||||||
|
);
|
||||||
|
return [$classMethodReference, $stringArgTypesString];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createClassMethodReference(MethodCallReference $classMethodCallReference): string
|
||||||
|
{
|
||||||
|
$className = $classMethodCallReference->getClass();
|
||||||
|
$methodName = $classMethodCallReference->getMethod();
|
||||||
|
|
||||||
|
return $className . '::' . $methodName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Collector\MethodCallableNode;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Collectors\Collector;
|
||||||
|
use PHPStan\Node\MethodCallableNode;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
use Rector\TypePerfect\Matcher\ClassMethodCallReferenceResolver;
|
||||||
|
use Rector\TypePerfect\ValueObject\MethodCallReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements Collector<MethodCallableNode, array<string>|null>
|
||||||
|
*
|
||||||
|
* PHPStan has special node for first class callables of MethodCall
|
||||||
|
*
|
||||||
|
* @see https://github.com/phpstan/phpstan-src/blob/511c1e435fb43b8eb0ac310e6aa3230147963790/src/Analyser/NodeScopeResolver.php#L1936
|
||||||
|
*/
|
||||||
|
final readonly class MethodCallableCollector implements Collector
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ClassMethodCallReferenceResolver $classMethodCallReferenceResolver,
|
||||||
|
private Configuration $configuration
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return MethodCallableNode::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param MethodCallableNode $node
|
||||||
|
* @return array{string}|null
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): ?array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNarrowParamEnabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMethodCallReference = $this->classMethodCallReferenceResolver->resolve($node, $scope, true);
|
||||||
|
if (! $classMethodCallReference instanceof MethodCallReference) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMethodReference = $this->createClassMethodReference($classMethodCallReference);
|
||||||
|
|
||||||
|
// special case that should skip everything
|
||||||
|
return [$classMethodReference];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createClassMethodReference(MethodCallReference $classMethodCallReference): string
|
||||||
|
{
|
||||||
|
$className = $classMethodCallReference->getClass();
|
||||||
|
$methodName = $classMethodCallReference->getMethod();
|
||||||
|
|
||||||
|
return $className . '::' . $methodName;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Configuration.php
vendored
Normal file
80
tools/.phpstan/vendor/tomasvotruba/type-coverage/packages/type-perfect/src/Configuration.php
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect;
|
||||||
|
|
||||||
|
final class Configuration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $parameters
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private array $parameters
|
||||||
|
) {
|
||||||
|
// enabed by default in tests
|
||||||
|
if (defined('PHPUNIT_COMPOSER_INSTALL')) {
|
||||||
|
$this->parameters['narrow_param'] = true;
|
||||||
|
$this->parameters['narrow_return'] = true;
|
||||||
|
$this->parameters['no_mixed'] = true;
|
||||||
|
$this->parameters['null_over_false'] = true;
|
||||||
|
$this->parameters['no_param_type_removal'] = true;
|
||||||
|
$this->parameters['no_array_access_on_object'] = true;
|
||||||
|
$this->parameters['no_isset_on_object'] = true;
|
||||||
|
$this->parameters['no_empty_on_object'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNarrowParamEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->parameters['narrow_param'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNarrowReturnEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->parameters['narrow_return'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNoMixedPropertyEnabled(): bool
|
||||||
|
{
|
||||||
|
if ($this->parameters['no_mixed_property']) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parameters['no_mixed'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNoMixedCallerEnabled(): bool
|
||||||
|
{
|
||||||
|
if ($this->parameters['no_mixed_caller']) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parameters['no_mixed'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNoFalsyReturnEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->parameters['null_over_false'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNoParamTypeRemovalEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->parameters['no_param_type_removal'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNoArrayAccessOnObjectEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->parameters['no_array_access_on_object'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNoIssetOnObjectEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->parameters['no_isset_on_object'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNoEmptyOnObjectEnabled(): bool
|
||||||
|
{
|
||||||
|
return $this->parameters['no_empty_on_object'] ?? false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Enum\Types;
|
||||||
|
|
||||||
|
final class ResolvedTypes
|
||||||
|
{
|
||||||
|
public const string UNKNOWN_TYPES = 'unknown_types';
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Guard;
|
||||||
|
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||||
|
use PhpParser\Node\Expr\Variable;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Type\TypeCombinator;
|
||||||
|
|
||||||
|
final class EmptyIssetGuard
|
||||||
|
{
|
||||||
|
public function isLegal(Expr $expr, Scope $scope): bool
|
||||||
|
{
|
||||||
|
if ($expr instanceof ArrayDimFetch) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $expr instanceof Variable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($expr->name instanceof Expr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $scope->hasVariableType($expr->name)->yes()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$varType = $scope->getNativeType($expr);
|
||||||
|
$varType = TypeCombinator::removeNull($varType);
|
||||||
|
return $varType->getObjectClassNames() === [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Matcher;
|
||||||
|
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Node\MethodCallableNode;
|
||||||
|
use PHPStan\Type\ThisType;
|
||||||
|
use PHPStan\Type\TypeCombinator;
|
||||||
|
use Rector\TypePerfect\ValueObject\MethodCallReference;
|
||||||
|
|
||||||
|
final class ClassMethodCallReferenceResolver
|
||||||
|
{
|
||||||
|
public function resolve(
|
||||||
|
MethodCall|MethodCallableNode $methodCallOrMethodCallable,
|
||||||
|
Scope $scope,
|
||||||
|
bool $allowThisType
|
||||||
|
): ?MethodCallReference {
|
||||||
|
if ($methodCallOrMethodCallable instanceof MethodCallableNode) {
|
||||||
|
$methodName = $methodCallOrMethodCallable->getName();
|
||||||
|
$variable = $methodCallOrMethodCallable->getVar();
|
||||||
|
} else {
|
||||||
|
$methodName = $methodCallOrMethodCallable->name;
|
||||||
|
$variable = $methodCallOrMethodCallable->var;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($methodName instanceof Expr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$callerType = $scope->getType($variable);
|
||||||
|
|
||||||
|
// remove optional nullable type
|
||||||
|
if (TypeCombinator::containsNull($callerType)) {
|
||||||
|
$callerType = TypeCombinator::removeNull($callerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $allowThisType && $callerType instanceof ThisType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($callerType->getObjectClassNames()) !== 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// move to the class where method is defined, e.g. parent class defines the method, so it should be checked there
|
||||||
|
$className = $callerType->getObjectClassNames()[0];
|
||||||
|
$methodNameString = $methodName->toString();
|
||||||
|
|
||||||
|
return new MethodCallReference($className, $methodNameString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Matcher\Collector;
|
||||||
|
|
||||||
|
use PhpParser\Comment\Doc;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
|
||||||
|
final class PublicClassMethodMatcher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private const array SKIPPED_TYPES = [
|
||||||
|
'PHPUnit\Framework\TestCase',
|
||||||
|
'Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function shouldSkipClassReflection(ClassReflection $classReflection): bool
|
||||||
|
{
|
||||||
|
// skip interface as required, traits as unable to detect for sure
|
||||||
|
if (! $classReflection->isClass()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_any(self::SKIPPED_TYPES, fn (string $skippedType): bool => $classReflection->is($skippedType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isUsedByParentClassOrInterface(ClassReflection $classReflection, string $methodName): bool
|
||||||
|
{
|
||||||
|
// is this method required by parent contract? skip it
|
||||||
|
foreach ($classReflection->getInterfaces() as $parentInterfaceReflection) {
|
||||||
|
if ($parentInterfaceReflection->hasMethod($methodName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_any($classReflection->getParents(), fn (ClassReflection $parentClassReflection): bool => $parentClassReflection->hasMethod($methodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldSkipClassMethod(ClassMethod $classMethod): bool
|
||||||
|
{
|
||||||
|
if ($classMethod->isMagic()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($classMethod->isStatic()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip attributes
|
||||||
|
if ($classMethod->attrGroups !== []) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $classMethod->isPublic()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$doc = $classMethod->getDocComment();
|
||||||
|
|
||||||
|
// skip symfony action
|
||||||
|
return $doc instanceof Doc && str_contains($doc->getText(), '@Route');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\NodeFinder;
|
||||||
|
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\Stmt\Class_;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use Rector\TypePerfect\Reflection\ReflectionParser;
|
||||||
|
|
||||||
|
final readonly class ClassMethodNodeFinder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ReflectionParser $reflectionParser,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByMethodCall(MethodCall $methodCall, Scope $scope): ?ClassMethod
|
||||||
|
{
|
||||||
|
$classReflection = $scope->getClassReflection();
|
||||||
|
if (! $classReflection instanceof ClassReflection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classLike = $this->reflectionParser->parseClassReflection($classReflection);
|
||||||
|
if (! $classLike instanceof Class_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $methodCall->name instanceof Identifier) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodCallName = $methodCall->name->toString();
|
||||||
|
|
||||||
|
$classMethod = $classLike->getMethod($methodCallName);
|
||||||
|
if (! $classMethod instanceof ClassMethod) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $classMethod->isPrivate()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\NodeFinder;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PhpParser\Node\Stmt\Class_;
|
||||||
|
use PhpParser\NodeFinder;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use Rector\TypePerfect\Printer\NodeComparator;
|
||||||
|
use Rector\TypePerfect\Reflection\ReflectionParser;
|
||||||
|
use Webmozart\Assert\Assert;
|
||||||
|
|
||||||
|
final readonly class MethodCallNodeFinder
|
||||||
|
{
|
||||||
|
private NodeFinder $nodeFinder;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private ReflectionParser $reflectionParser,
|
||||||
|
private NodeComparator $nodeComparator,
|
||||||
|
) {
|
||||||
|
$this->nodeFinder = new NodeFinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return MethodCall[]
|
||||||
|
*/
|
||||||
|
public function findUsages(MethodCall $methodCall, Scope $scope): array
|
||||||
|
{
|
||||||
|
$classReflection = $scope->getClassReflection();
|
||||||
|
if (! $classReflection instanceof ClassReflection) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$classLike = $this->reflectionParser->parseClassReflection($classReflection);
|
||||||
|
if (! $classLike instanceof Class_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodCalls = $this->nodeFinder->find($classLike, function (Node $node) use ($methodCall): bool {
|
||||||
|
if (! $node instanceof MethodCall) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->nodeComparator->areNodesEqual($node->var, $methodCall->var)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->nodeComparator->areNodesEqual($node->name, $methodCall->name);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert::allIsInstanceOf($methodCalls, MethodCall::class);
|
||||||
|
|
||||||
|
return $methodCalls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\NodeFinder;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\FunctionLike;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PhpParser\Node\Stmt\Return_;
|
||||||
|
use PhpParser\NodeTraverser;
|
||||||
|
use Rector\TypePerfect\NodeVisitor\CallableNodeVisitor;
|
||||||
|
|
||||||
|
final readonly class ReturnNodeFinder
|
||||||
|
{
|
||||||
|
public function findOnlyReturnsExpr(ClassMethod $classMethod): Expr|null
|
||||||
|
{
|
||||||
|
$returns = $this->findReturnsWithValues($classMethod);
|
||||||
|
if (count($returns) !== 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$onlyReturn = $returns[0];
|
||||||
|
return $onlyReturn->expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Return_[]
|
||||||
|
*/
|
||||||
|
public function findReturnsWithValues(ClassMethod $classMethod): array
|
||||||
|
{
|
||||||
|
$returns = [];
|
||||||
|
|
||||||
|
$this->traverseNodesWithCallable((array) $classMethod->stmts, static function (
|
||||||
|
Node $node
|
||||||
|
) use (&$returns) {
|
||||||
|
// skip different scope
|
||||||
|
if ($node instanceof FunctionLike) {
|
||||||
|
return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $node instanceof Return_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $node->expr instanceof Expr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$returns[] = $node;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $returns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(Node $node): (int|Node|null) $callable
|
||||||
|
* @param Node[] $nodes
|
||||||
|
*/
|
||||||
|
private function traverseNodesWithCallable(array $nodes, callable $callable): void
|
||||||
|
{
|
||||||
|
$nodeTraverser = new NodeTraverser();
|
||||||
|
|
||||||
|
$callableNodeVisitor = new CallableNodeVisitor($callable);
|
||||||
|
$nodeTraverser->addVisitor($callableNodeVisitor);
|
||||||
|
$nodeTraverser->traverse($nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\NodeVisitor;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
|
use PhpParser\Node\Stmt\Expression;
|
||||||
|
use PhpParser\NodeVisitorAbstract;
|
||||||
|
|
||||||
|
final class CallableNodeVisitor extends NodeVisitorAbstract
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var callable(Node): (int|Node|null)
|
||||||
|
*/
|
||||||
|
private $callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(Node $node): (int|Node|null) $callable
|
||||||
|
*/
|
||||||
|
public function __construct(callable $callable)
|
||||||
|
{
|
||||||
|
$this->callable = $callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enterNode(Node $node): int|Node|null
|
||||||
|
{
|
||||||
|
$originalNode = $node;
|
||||||
|
$callable = $this->callable;
|
||||||
|
|
||||||
|
/** @var int|Node|null $newNode */
|
||||||
|
$newNode = $callable($node);
|
||||||
|
|
||||||
|
if ($originalNode instanceof Stmt && $newNode instanceof Expr) {
|
||||||
|
return new Expression($newNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\PhpDoc;
|
||||||
|
|
||||||
|
use PhpParser\Comment\Doc;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
|
||||||
|
final class ApiDocStmtAnalyzer
|
||||||
|
{
|
||||||
|
public function hasApiDoc(ClassMethod $classMethod, ClassReflection $classReflection): bool
|
||||||
|
{
|
||||||
|
if ($this->hasClassReflectionApiDoc($classReflection)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$docComment = $classMethod->getDocComment();
|
||||||
|
if (! $docComment instanceof Doc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_contains($docComment->getText(), '@api');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasClassReflectionApiDoc(ClassReflection $classReflection): bool
|
||||||
|
{
|
||||||
|
if (! $classReflection->getResolvedPhpDoc() instanceof ResolvedPhpDocBlock) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedPhpDocBlock = $classReflection->getResolvedPhpDoc();
|
||||||
|
return str_contains($resolvedPhpDocBlock->getPhpDocString(), '@api');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Printer;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\IntersectionType as NodeIntersectionType;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
use PhpParser\Node\Name\FullyQualified;
|
||||||
|
use PhpParser\Node\NullableType;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PhpParser\Node\UnionType;
|
||||||
|
use PhpParser\PrettyPrinter\Standard;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use PHPStan\Reflection\ExtendedMethodReflection;
|
||||||
|
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||||
|
use PHPStan\Type\ClosureType;
|
||||||
|
use PHPStan\Type\Generic\GenericObjectType;
|
||||||
|
use PHPStan\Type\IntegerRangeType;
|
||||||
|
use PHPStan\Type\IntersectionType;
|
||||||
|
use PHPStan\Type\MixedType;
|
||||||
|
use PHPStan\Type\ObjectType;
|
||||||
|
use PHPStan\Type\ThisType;
|
||||||
|
use PHPStan\Type\Type;
|
||||||
|
use PHPStan\Type\TypeCombinator;
|
||||||
|
use PHPStan\Type\UnionType as PHPStanUnionType;
|
||||||
|
use PHPStan\Type\VerbosityLevel;
|
||||||
|
use Rector\TypePerfect\Enum\Types\ResolvedTypes;
|
||||||
|
|
||||||
|
final readonly class CollectorMetadataPrinter
|
||||||
|
{
|
||||||
|
private Standard $standard;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
) {
|
||||||
|
$this->standard = new Standard();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function printArgTypesAsString(
|
||||||
|
MethodCall $methodCall,
|
||||||
|
ExtendedMethodReflection $extendedMethodReflection,
|
||||||
|
Scope $scope
|
||||||
|
): string {
|
||||||
|
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
|
||||||
|
$scope,
|
||||||
|
$methodCall->getArgs(),
|
||||||
|
$extendedMethodReflection->getVariants(),
|
||||||
|
$extendedMethodReflection->getNamedArgumentsVariants()
|
||||||
|
);
|
||||||
|
$parameters = $parametersAcceptor->getParameters();
|
||||||
|
|
||||||
|
$stringArgTypes = [];
|
||||||
|
foreach ($methodCall->getArgs() as $i => $arg) {
|
||||||
|
$argType = $scope->getType($arg->value);
|
||||||
|
|
||||||
|
// we have no idea, nothing we can do
|
||||||
|
if ($argType instanceof MixedType) {
|
||||||
|
return ResolvedTypes::UNKNOWN_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($argType instanceof IntersectionType) {
|
||||||
|
return ResolvedTypes::UNKNOWN_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($argType instanceof GenericObjectType) {
|
||||||
|
return ResolvedTypes::UNKNOWN_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($argType instanceof PHPStanUnionType) {
|
||||||
|
return ResolvedTypes::UNKNOWN_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($argType instanceof ThisType) {
|
||||||
|
$argType = new ObjectType($argType->getClassName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists($i, $parameters)) {
|
||||||
|
$defaultValueType = $parameters[$i]->getDefaultValue();
|
||||||
|
if ($defaultValueType instanceof Type) {
|
||||||
|
$argType = TypeCombinator::union($argType, $defaultValueType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$printedArgType = $this->printTypeToString($argType);
|
||||||
|
$printedArgType = $this->normalizeDateTime($printedArgType);
|
||||||
|
|
||||||
|
$stringArgTypes[] = $printedArgType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('|', $stringArgTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function printParamTypesToString(
|
||||||
|
ClassMethod $classMethod,
|
||||||
|
ClassReflection $classReflection,
|
||||||
|
Scope $scope
|
||||||
|
): string {
|
||||||
|
$className = $classReflection->getName();
|
||||||
|
|
||||||
|
$parametersReflection = [];
|
||||||
|
if ($classReflection->hasMethod($classMethod->name->name)) {
|
||||||
|
$methodReflection = $classReflection->getMethod($classMethod->name->name, $scope);
|
||||||
|
$variants = $methodReflection->getVariants();
|
||||||
|
if (count($variants) === 1) {
|
||||||
|
$parametersReflection = $variants[0]->getParameters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$printedParamTypes = [];
|
||||||
|
foreach ($classMethod->params as $i => $param) {
|
||||||
|
if ($param->type === null) {
|
||||||
|
$printedParamTypes[] = '';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$phpdocType = null;
|
||||||
|
if (array_key_exists($i, $parametersReflection)) {
|
||||||
|
$paramphpdocType = $parametersReflection[$i]->getPhpDocType();
|
||||||
|
if (! $paramphpdocType instanceof MixedType) {
|
||||||
|
$phpdocType = $paramphpdocType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($phpdocType instanceof Type) {
|
||||||
|
$printedParamType = $this->printTypeToString($phpdocType);
|
||||||
|
} else {
|
||||||
|
$paramType = $this->transformSelfToClassName($param->type, $className);
|
||||||
|
|
||||||
|
if ($paramType instanceof NullableType) {
|
||||||
|
// unite to phpstan type
|
||||||
|
$paramType = new UnionType([$paramType->type, new Identifier('null')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($paramType instanceof UnionType || $paramType instanceof NodeIntersectionType) {
|
||||||
|
$paramType = $this->resolveSortedTypes($paramType, $className);
|
||||||
|
}
|
||||||
|
|
||||||
|
$printedParamType = $this->standard->prettyPrint([$paramType]);
|
||||||
|
$printedParamType = str_replace('\Closure', 'callable', $printedParamType);
|
||||||
|
$printedParamType = ltrim($printedParamType, '\\');
|
||||||
|
$printedParamType = str_replace('|\\', '|', $printedParamType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// to avoid DateTime vs DateTimeImmutable vs DateTimeInterface conflicts
|
||||||
|
$printedParamType = $this->normalizeDateTime($printedParamType);
|
||||||
|
|
||||||
|
$printedParamTypes[] = $printedParamType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('|', $printedParamTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function transformSelfToClassName(Node $node, ?string $className): Node
|
||||||
|
{
|
||||||
|
if (! $node instanceof Name || $className === null) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node->toString() !== 'self') {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FullyQualified($className);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveSortedTypes(
|
||||||
|
UnionType|NodeIntersectionType $paramType,
|
||||||
|
?string $className
|
||||||
|
): UnionType|NodeIntersectionType {
|
||||||
|
$typeNames = [];
|
||||||
|
|
||||||
|
foreach ($paramType->types as $type) {
|
||||||
|
if ($type instanceof NodeIntersectionType) {
|
||||||
|
foreach ($type->types as $intersectionType) {
|
||||||
|
/** @var Identifier|Name $intersectionType */
|
||||||
|
$intersectionType = $this->transformSelfToClassName($intersectionType, $className);
|
||||||
|
$typeNames[] = (string) $intersectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Identifier|Name $type */
|
||||||
|
$type = $this->transformSelfToClassName($type, $className);
|
||||||
|
$typeNames[] = (string) $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
sort($typeNames);
|
||||||
|
|
||||||
|
$types = [];
|
||||||
|
foreach ($typeNames as $typeName) {
|
||||||
|
$types[] = new Identifier($typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($paramType instanceof NodeIntersectionType) {
|
||||||
|
return new NodeIntersectionType($types);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UnionType($types);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printTypeToString(Type $type): string
|
||||||
|
{
|
||||||
|
if ($type->isClassString()->yes()) {
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type->isArray()->yes()) {
|
||||||
|
return 'array';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type->isBoolean()->yes()) {
|
||||||
|
return 'bool';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof IntegerRangeType) {
|
||||||
|
return 'int';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof ClosureType) {
|
||||||
|
return 'callable';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($type->getEnumCases()) === 1) {
|
||||||
|
return $type->getEnumCases()[0]
|
||||||
|
->getClassName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $type->describe(VerbosityLevel::typeOnly());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeDateTime(string $printedType): string
|
||||||
|
{
|
||||||
|
if ($printedType === 'DateTimeImmutable') {
|
||||||
|
return 'DateTimeInterface';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($printedType === 'DateTime') {
|
||||||
|
return 'DateTimeInterface';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $printedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Printer;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PHPStan\Node\Printer\Printer;
|
||||||
|
|
||||||
|
final readonly class NodeComparator
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Printer $printer
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function areNodesEqual(Node $firstNode, Node $secondNode): bool
|
||||||
|
{
|
||||||
|
// remove comments from nodes
|
||||||
|
$firstNode->setAttribute('comments', null);
|
||||||
|
$secondNode->setAttribute('comments', null);
|
||||||
|
|
||||||
|
return $this->printer->prettyPrint([$firstNode]) === $this->printer->prettyPrint([$secondNode]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Reflection;
|
||||||
|
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use PHPStan\Reflection\MethodReflection;
|
||||||
|
|
||||||
|
final class MethodNodeAnalyser
|
||||||
|
{
|
||||||
|
public function hasParentVendorLock(Scope $scope, string $methodName): bool
|
||||||
|
{
|
||||||
|
return $this->matchFirstParentClassMethod($scope, $methodName) instanceof MethodReflection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function matchFirstParentClassMethod(Scope $scope, string $methodName): ?MethodReflection
|
||||||
|
{
|
||||||
|
$classReflection = $scope->getClassReflection();
|
||||||
|
if (! $classReflection instanceof ClassReflection) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the classes have highter priority, e.g. priority in class covariance
|
||||||
|
foreach ($classReflection->getParents() as $parentClassReflection) {
|
||||||
|
if ($parentClassReflection->hasNativeMethod($methodName)) {
|
||||||
|
return $parentClassReflection->getNativeMethod($methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($classReflection->getAncestors() as $ancestorClassReflection) {
|
||||||
|
if ($classReflection === $ancestorClassReflection) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $ancestorClassReflection->hasNativeMethod($methodName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ancestorClassReflection->getNativeMethod($methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Reflection;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Stmt\ClassLike;
|
||||||
|
use PhpParser\NodeFinder;
|
||||||
|
use PhpParser\NodeTraverser;
|
||||||
|
use PhpParser\NodeVisitor\NameResolver;
|
||||||
|
use PhpParser\Parser;
|
||||||
|
use PhpParser\ParserFactory;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
final class ReflectionParser
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<string, ClassLike>
|
||||||
|
*/
|
||||||
|
private array $classesByFilename = [];
|
||||||
|
|
||||||
|
private readonly Parser $parser;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$parserFactory = new ParserFactory();
|
||||||
|
$this->parser = $parserFactory->createForNewestSupportedVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseClassReflection(ClassReflection $classReflection): ?ClassLike
|
||||||
|
{
|
||||||
|
$fileName = $classReflection->getFileName();
|
||||||
|
if ($fileName === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parseFilenameToClass($fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseFilenameToClass(string $fileName): ClassLike|null
|
||||||
|
{
|
||||||
|
if (isset($this->classesByFilename[$fileName])) {
|
||||||
|
return $this->classesByFilename[$fileName];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var string $fileContents */
|
||||||
|
$fileContents = file_get_contents($fileName);
|
||||||
|
|
||||||
|
$stmts = $this->parser->parse($fileContents);
|
||||||
|
if (! is_array($stmts)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// complete namespacedName variables
|
||||||
|
$nodeTraverser = new NodeTraverser();
|
||||||
|
$nodeTraverser->addVisitor(new NameResolver());
|
||||||
|
$nodeTraverser->traverse($stmts);
|
||||||
|
} catch (Throwable) {
|
||||||
|
// not reachable
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classLike = $this->findFirstClassLike($stmts);
|
||||||
|
if (! $classLike instanceof ClassLike) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->classesByFilename[$fileName] = $classLike;
|
||||||
|
|
||||||
|
return $classLike;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Node[] $nodes
|
||||||
|
*/
|
||||||
|
private function findFirstClassLike(array $nodes): ?ClassLike
|
||||||
|
{
|
||||||
|
$nodeFinder = new NodeFinder();
|
||||||
|
return $nodeFinder->findFirstInstanceOf($nodes, ClassLike::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Arg;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PhpParser\Node\Expr\Variable;
|
||||||
|
use PhpParser\Node\Name\FullyQualified;
|
||||||
|
use PhpParser\Node\Param;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use PHPStan\Type\Generic\GenericObjectType;
|
||||||
|
use PHPStan\Type\Generic\TemplateType;
|
||||||
|
use PHPStan\Type\IntersectionType;
|
||||||
|
use PHPStan\Type\MixedType;
|
||||||
|
use PHPStan\Type\ObjectType;
|
||||||
|
use PHPStan\Type\UnionType;
|
||||||
|
use PHPStan\Type\VerbosityLevel;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
use Rector\TypePerfect\NodeFinder\ClassMethodNodeFinder;
|
||||||
|
use Rector\TypePerfect\NodeFinder\MethodCallNodeFinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see \Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\NarrowPrivateClassMethodParamTypeRuleTest
|
||||||
|
* @implements Rule<MethodCall>
|
||||||
|
*/
|
||||||
|
final readonly class NarrowPrivateClassMethodParamTypeRule implements Rule
|
||||||
|
{
|
||||||
|
public const string ERROR_MESSAGE = 'Parameter %d should use "%s" type as the only type passed to this method';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private Configuration $configuration,
|
||||||
|
private MethodCallNodeFinder $methodCallNodeFinder,
|
||||||
|
private ClassMethodNodeFinder $classMethodNodeFinder
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string<Node>
|
||||||
|
*/
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return MethodCall::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param MethodCall $node
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNarrowParamEnabled() || $node->isFirstClassCallable()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = $node->getArgs();
|
||||||
|
if ($args === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $node->var instanceof Variable) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_string($node->var->name)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node->var->name !== 'this') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->validateArgVsParamTypes($args, $node, $scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Arg[] $args
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
private function validateArgVsParamTypes(array $args, MethodCall $methodCall, Scope $scope): array
|
||||||
|
{
|
||||||
|
$methodCallUses = $this->methodCallNodeFinder->findUsages($methodCall, $scope);
|
||||||
|
if (count($methodCallUses) > 1) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMethod = $this->classMethodNodeFinder->findByMethodCall($methodCall, $scope);
|
||||||
|
if (! $classMethod instanceof ClassMethod) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Param[] $params */
|
||||||
|
$params = $classMethod->getParams();
|
||||||
|
|
||||||
|
$errorMessages = [];
|
||||||
|
|
||||||
|
foreach ($args as $position => $arg) {
|
||||||
|
$param = $params[$position] ?? [];
|
||||||
|
if (! $param instanceof Param) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($param->variadic) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$paramRuleError = $this->validateParam($param, $position, $arg->value, $scope);
|
||||||
|
if (! $paramRuleError instanceof RuleError) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo test double failed type
|
||||||
|
$errorMessages[] = $paramRuleError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errorMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateParam(Param $param, int $position, Expr $expr, Scope $scope): ?RuleError
|
||||||
|
{
|
||||||
|
$type = $param->type;
|
||||||
|
|
||||||
|
// @todo some static type mapper from php-parser to PHPStan?
|
||||||
|
if (! $type instanceof FullyQualified) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$argType = $scope->getType($expr);
|
||||||
|
if ($argType instanceof MixedType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($argType instanceof TemplateType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not solveable yet, work with PHP 8 code only
|
||||||
|
if ($argType instanceof UnionType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($argType instanceof IntersectionType || $argType instanceof GenericObjectType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$objectType = new ObjectType($type->toString());
|
||||||
|
|
||||||
|
if ($objectType->equals($argType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classReflection = $objectType->getClassReflection();
|
||||||
|
if ($classReflection instanceof ClassReflection && $classReflection->isAbstract()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle weird type substration cases
|
||||||
|
$paramTypeAsString = $objectType->describe(VerbosityLevel::typeOnly());
|
||||||
|
$argTypeAsString = $argType->describe(VerbosityLevel::typeOnly());
|
||||||
|
|
||||||
|
if ($paramTypeAsString === $argTypeAsString) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$errorMessage = sprintf(self::ERROR_MESSAGE, $position + 1, $argTypeAsString);
|
||||||
|
|
||||||
|
return RuleErrorBuilder::message($errorMessage)
|
||||||
|
->identifier('typePerfect.narrowPrivateClassMethodParamType')
|
||||||
|
->line($param->getLine())
|
||||||
|
->build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Node\CollectedDataNode;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use Rector\TypePerfect\Collector\ClassMethod\PublicClassMethodParamTypesCollector;
|
||||||
|
use Rector\TypePerfect\Collector\MethodCall\MethodCallArgTypesCollector;
|
||||||
|
use Rector\TypePerfect\Collector\MethodCallableNode\MethodCallableCollector;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
use Rector\TypePerfect\Enum\Types\ResolvedTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see \Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\NarrowPublicClassMethodParamTypeRuleTest
|
||||||
|
*
|
||||||
|
* @implements Rule<CollectedDataNode>
|
||||||
|
*/
|
||||||
|
final readonly class NarrowPublicClassMethodParamTypeRule implements Rule
|
||||||
|
{
|
||||||
|
public const string ERROR_MESSAGE = 'Parameters should have "%s" types as the only types passed to this method';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private Configuration $configuration
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string<Node>
|
||||||
|
*/
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return CollectedDataNode::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CollectedDataNode $node
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNarrowParamEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$publicClassMethodCollector = $node->get(PublicClassMethodParamTypesCollector::class);
|
||||||
|
$classMethodReferenceToArgTypes = $this->resolveClassMethodReferenceToArgTypes($node);
|
||||||
|
|
||||||
|
$ruleErrors = [];
|
||||||
|
|
||||||
|
foreach ($publicClassMethodCollector as $filePath => $declarations) {
|
||||||
|
foreach ($declarations as [$className, $methodName, $paramTypesString, $line]) {
|
||||||
|
$uniqueCollectedArgTypesString = $this->resolveUniqueArgTypesString(
|
||||||
|
$classMethodReferenceToArgTypes,
|
||||||
|
$className,
|
||||||
|
$methodName
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($uniqueCollectedArgTypesString === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($paramTypesString === $uniqueCollectedArgTypesString) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ruleErrors[] = RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $uniqueCollectedArgTypesString))
|
||||||
|
->identifier('typePerfect.narrowPublicClassMethodParamType')
|
||||||
|
->file($filePath)
|
||||||
|
->line($line)
|
||||||
|
->build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ruleErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string[]>
|
||||||
|
*/
|
||||||
|
private function resolveClassMethodReferenceToArgTypes(CollectedDataNode $collectedDataNode): array
|
||||||
|
{
|
||||||
|
$methodCallArgTypesByFilePath = $collectedDataNode->get(MethodCallArgTypesCollector::class);
|
||||||
|
|
||||||
|
// these should be skipped completely, as we have no idea what is being passed there
|
||||||
|
$methodCallablesByFilePath = $collectedDataNode->get(MethodCallableCollector::class);
|
||||||
|
|
||||||
|
$methodFirstClassCallables = $this->flattenCollectedData($methodCallablesByFilePath);
|
||||||
|
|
||||||
|
// group call references and types
|
||||||
|
$classMethodReferenceToTypes = [];
|
||||||
|
|
||||||
|
foreach ($methodCallArgTypesByFilePath as $methodCallArgTypes) {
|
||||||
|
foreach ($methodCallArgTypes as [$classMethodReference, $argTypesString]) {
|
||||||
|
// skip it
|
||||||
|
if (in_array($classMethodReference, $methodFirstClassCallables, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMethodReferenceToTypes[$classMethodReference][] = $argTypesString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve unique values
|
||||||
|
foreach ($classMethodReferenceToTypes as $key => $value) {
|
||||||
|
$classMethodReferenceToTypes[$key] = array_unique($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classMethodReferenceToTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string[]> $classMethodReferenceToArgTypes
|
||||||
|
*/
|
||||||
|
private function resolveUniqueArgTypesString(
|
||||||
|
array $classMethodReferenceToArgTypes,
|
||||||
|
string $className,
|
||||||
|
string $methodName
|
||||||
|
): ?string {
|
||||||
|
$currentClassMethodReference = $className . '::' . $methodName;
|
||||||
|
|
||||||
|
$collectedArgTypes = $classMethodReferenceToArgTypes[$currentClassMethodReference] ?? null;
|
||||||
|
if ($collectedArgTypes === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need exactly one type
|
||||||
|
if (count($collectedArgTypes) !== 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// one of the arg types could not be resolved, we're not sure
|
||||||
|
if (in_array(ResolvedTypes::UNKNOWN_TYPES, $collectedArgTypes, true)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $collectedArgTypes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, list<(array<string>|null)>> $methodCallablesByFilePath
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private function flattenCollectedData(array $methodCallablesByFilePath): array
|
||||||
|
{
|
||||||
|
$methodFirstClassCallables = [];
|
||||||
|
foreach ($methodCallablesByFilePath as $methodCallables) {
|
||||||
|
foreach ($methodCallables as $methodCallable) {
|
||||||
|
// the collector returns null for nodes it cannot resolve
|
||||||
|
if (! isset($methodCallable[0])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodFirstClassCallables[] = $methodCallable[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $methodFirstClassCallables;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Name\FullyQualified;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use PHPStan\Type\ObjectType;
|
||||||
|
use PHPStan\Type\Type;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
use Rector\TypePerfect\NodeFinder\ReturnNodeFinder;
|
||||||
|
use Rector\TypePerfect\Reflection\MethodNodeAnalyser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see \Rector\TypePerfect\Tests\Rules\NarrowReturnObjectTypeRule\NarrowReturnObjectTypeRuleTest
|
||||||
|
*
|
||||||
|
* @implements Rule<ClassMethod>
|
||||||
|
*/
|
||||||
|
final readonly class NarrowReturnObjectTypeRule implements Rule
|
||||||
|
{
|
||||||
|
public const string ERROR_MESSAGE = 'Provide more specific return type "%s" over abstract one';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private ReturnNodeFinder $returnNodeFinder,
|
||||||
|
private MethodNodeAnalyser $methodNodeAnalyser,
|
||||||
|
private Configuration $configuration
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string<Node>
|
||||||
|
*/
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return ClassMethod::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ClassMethod $node
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNarrowReturnEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $node->returnType instanceof FullyQualified) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->shouldSkipScope($scope)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$returnObjectType = new ObjectType($node->returnType->toString());
|
||||||
|
if ($this->shouldSkipReturnObjectType($returnObjectType)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodName = $node->name->toString();
|
||||||
|
if ($this->methodNodeAnalyser->hasParentVendorLock($scope, $methodName)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$returnExpr = $this->returnNodeFinder->findOnlyReturnsExpr($node);
|
||||||
|
if (! $returnExpr instanceof Expr) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$returnExprType = $scope->getType($returnExpr);
|
||||||
|
if ($this->shouldSkipReturnExprType($returnExprType)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($returnObjectType->equals($returnExprType)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// is subtype?
|
||||||
|
if (! $returnObjectType->isSuperTypeOf($returnExprType)->yes()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($returnExprType->getObjectClassNames()) !== 1) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$errorMessage = sprintf(self::ERROR_MESSAGE, $returnExprType->getObjectClassNames()[0]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
RuleErrorBuilder::message($errorMessage)
|
||||||
|
->identifier('typePerfect.narrowReturnObjectType')
|
||||||
|
->build(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldSkipReturnObjectType(ObjectType $objectType): bool
|
||||||
|
{
|
||||||
|
$classReflection = $objectType->getClassReflection();
|
||||||
|
if (! $classReflection instanceof ClassReflection) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cannot be more precise if final class
|
||||||
|
return $classReflection->isFinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldSkipScope(Scope $scope): bool
|
||||||
|
{
|
||||||
|
$classReflection = $scope->getClassReflection();
|
||||||
|
if (! $classReflection instanceof ClassReflection) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ! $classReflection->isClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldSkipReturnExprType(Type $type): bool
|
||||||
|
{
|
||||||
|
if (count($type->getObjectClassNames()) !== 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($type->getObjectClassReflections()) !== 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $type->getObjectClassReflections()[0]
|
||||||
|
->isAnonymous();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use PHPStan\Type\ObjectType;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see \Rector\TypePerfect\Tests\Rules\NoArrayAccessOnObjectRule\NoArrayAccessOnObjectRuleTest
|
||||||
|
* @implements Rule<ArrayDimFetch>
|
||||||
|
*/
|
||||||
|
final readonly class NoArrayAccessOnObjectRule implements Rule
|
||||||
|
{
|
||||||
|
public const string ERROR_MESSAGE = 'Use explicit methods over array access on object';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private const array ALLOWED_CLASSES = [
|
||||||
|
'SplFixedArray',
|
||||||
|
'SimpleXMLElement',
|
||||||
|
'Iterator',
|
||||||
|
'WeakMap',
|
||||||
|
'Aws\ResultInterface',
|
||||||
|
'Symfony\Component\DomCrawler\AbstractUriElement',
|
||||||
|
'Symfony\Component\Form\FormInterface',
|
||||||
|
'Symfony\Component\OptionsResolver\Options',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private Configuration $configuration,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string<Node>
|
||||||
|
*/
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return ArrayDimFetch::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ArrayDimFetch $node
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNoArrayAccessOnObjectEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$varType = $scope->getType($node->var);
|
||||||
|
if (! $varType instanceof ObjectType) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isAllowedObjectType($varType)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
RuleErrorBuilder::message(self::ERROR_MESSAGE)
|
||||||
|
->identifier('typePerfect.noArrayAccessOnObject')
|
||||||
|
->build(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isAllowedObjectType(ObjectType $objectType): bool
|
||||||
|
{
|
||||||
|
return array_any(self::ALLOWED_CLASSES, fn (string $allowedClass): bool => $objectType->isInstanceOf($allowedClass)->yes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\Empty_;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
use Rector\TypePerfect\Guard\EmptyIssetGuard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements Rule<Empty_>
|
||||||
|
*/
|
||||||
|
final readonly class NoEmptyOnObjectRule implements Rule
|
||||||
|
{
|
||||||
|
public const string ERROR_MESSAGE = 'Use instanceof instead of empty() on object';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private EmptyIssetGuard $emptyIssetGuard,
|
||||||
|
private Configuration $configuration,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return Empty_::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Empty_ $node
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNoEmptyOnObjectEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->emptyIssetGuard->isLegal($node->expr, $scope)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
RuleErrorBuilder::message(self::ERROR_MESSAGE)
|
||||||
|
->identifier('typePerfect.noEmptyOnObject')
|
||||||
|
->build(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\Isset_;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
use Rector\TypePerfect\Guard\EmptyIssetGuard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see \Rector\TypePerfect\Tests\Rules\NoIssetOnObjectRule\NoIssetOnObjectRuleTest
|
||||||
|
* @implements Rule<Isset_>
|
||||||
|
*/
|
||||||
|
final readonly class NoIssetOnObjectRule implements Rule
|
||||||
|
{
|
||||||
|
public const string ERROR_MESSAGE = 'Use instanceof instead of isset() on object';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private EmptyIssetGuard $emptyIssetGuard,
|
||||||
|
private Configuration $configuration,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return Isset_::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Isset_ $node
|
||||||
|
*
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNoIssetOnObjectEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($node->vars as $var) {
|
||||||
|
if ($this->emptyIssetGuard->isLegal($var, $scope)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
RuleErrorBuilder::message(self::ERROR_MESSAGE)
|
||||||
|
->identifier('typePerfect.noIssetOnObject')
|
||||||
|
->build(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Node\Printer\Printer;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use PHPStan\Type\ErrorType;
|
||||||
|
use PHPStan\Type\MixedType;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see \Rector\TypePerfect\Tests\Rules\NoMixedMethodCallerRule\NoMixedMethodCallerRuleTest
|
||||||
|
* @implements Rule<MethodCall>
|
||||||
|
*/
|
||||||
|
final readonly class NoMixedMethodCallerRule implements Rule
|
||||||
|
{
|
||||||
|
public const string ERROR_MESSAGE = 'Mixed variable in a `%s->...()` can skip important errors. Make sure the type is known';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private Printer $printer,
|
||||||
|
private Configuration $configuration,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string<Node>
|
||||||
|
*/
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return MethodCall::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param MethodCall $node
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNoMixedCallerEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$callerType = $scope->getType($node->var);
|
||||||
|
|
||||||
|
if (! $callerType instanceof MixedType) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($callerType instanceof ErrorType) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// if error, skip as well for false positive
|
||||||
|
if ($this->isPreviousTypeErrorType($node, $scope)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$printedMethodCall = $this->printer->prettyPrintExpr($node->var);
|
||||||
|
|
||||||
|
return [
|
||||||
|
RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $printedMethodCall))
|
||||||
|
->identifier('typePerfect.noMixedMethodCaller')
|
||||||
|
->build(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isPreviousTypeErrorType(MethodCall $methodCall, Scope $scope): bool
|
||||||
|
{
|
||||||
|
$currentMethodCall = $methodCall;
|
||||||
|
while ($currentMethodCall->var instanceof MethodCall) {
|
||||||
|
$previousType = $scope->getType($currentMethodCall->var);
|
||||||
|
if ($previousType instanceof ErrorType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentMethodCall = $currentMethodCall->var;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\PropertyFetch;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Node\Printer\Printer;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use PHPStan\Type\MixedType;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see \Rector\TypePerfect\Tests\Rules\NoMixedPropertyFetcherRule\NoMixedPropertyFetcherRuleTest
|
||||||
|
* @implements Rule<PropertyFetch>
|
||||||
|
*/
|
||||||
|
final readonly class NoMixedPropertyFetcherRule implements Rule
|
||||||
|
{
|
||||||
|
public const string ERROR_MESSAGE = 'Mixed property fetch in a "%s->..." can skip important errors. Make sure the type is known';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private Printer $printer,
|
||||||
|
private Configuration $configuration,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string<Node>
|
||||||
|
*/
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return PropertyFetch::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PropertyFetch $node
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNoMixedPropertyEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$callerType = $scope->getType($node->var);
|
||||||
|
if (! $callerType instanceof MixedType) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$printedVar = $this->printer->prettyPrintExpr($node->var);
|
||||||
|
|
||||||
|
return [
|
||||||
|
RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $printedVar))
|
||||||
|
->identifier('typePerfect.noMixedPropertyFetcher')
|
||||||
|
->build(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Reflection\Php\PhpMethodReflection;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use PHPStan\Type\MixedType;
|
||||||
|
use PHPStan\Type\Type;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
use Rector\TypePerfect\Reflection\MethodNodeAnalyser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see \Rector\TypePerfect\Tests\Rules\ForbiddenParamTypeRemovalRule\ForbiddenParamTypeRemovalRuleTest
|
||||||
|
* @implements Rule<ClassMethod>
|
||||||
|
*/
|
||||||
|
final readonly class NoParamTypeRemovalRule implements Rule
|
||||||
|
{
|
||||||
|
public const string ERROR_MESSAGE = 'Removing parent param type is forbidden';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private MethodNodeAnalyser $methodNodeAnalyser,
|
||||||
|
private Configuration $configuration,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string<Node>
|
||||||
|
*/
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return ClassMethod::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ClassMethod $node
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNoParamTypeRemovalEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node->params === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMethodName = (string) $node->name;
|
||||||
|
if ($classMethodName === '__construct') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$parentClassMethodReflection = $this->methodNodeAnalyser->matchFirstParentClassMethod($scope, $classMethodName);
|
||||||
|
if (! $parentClassMethodReflection instanceof PhpMethodReflection) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($node->params as $paramPosition => $param) {
|
||||||
|
if ($param->type !== null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parentParamType = $this->resolveParentParamType($parentClassMethodReflection, $paramPosition);
|
||||||
|
if ($parentParamType instanceof MixedType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// removed param type!
|
||||||
|
return [
|
||||||
|
RuleErrorBuilder::message(self::ERROR_MESSAGE)
|
||||||
|
->identifier('typePerfect.noParamTypeRemoval')
|
||||||
|
->build(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveParentParamType(PhpMethodReflection $phpMethodReflection, int $paramPosition): Type
|
||||||
|
{
|
||||||
|
foreach ($phpMethodReflection->getVariants() as $extendedParametersAcceptor) {
|
||||||
|
foreach ($extendedParametersAcceptor->getParameters() as $parentParamPosition => $parameterReflectionWithPhpDoc) {
|
||||||
|
if ($paramPosition !== $parentParamPosition) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parameterReflectionWithPhpDoc->getNativeType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MixedType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Rules;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Stmt\ClassMethod;
|
||||||
|
use PhpParser\Node\Stmt\Return_;
|
||||||
|
use PhpParser\NodeFinder;
|
||||||
|
use PHPStan\Analyser\Scope;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Rules\RuleError;
|
||||||
|
use PHPStan\Rules\RuleErrorBuilder;
|
||||||
|
use PHPStan\Type\Constant\ConstantBooleanType;
|
||||||
|
use Rector\TypePerfect\Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements Rule<ClassMethod>
|
||||||
|
*/
|
||||||
|
final readonly class ReturnNullOverFalseRule implements Rule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
public const string ERROR_MESSAGE = 'Returning false in non return bool class method. Use null with type|null instead or add bool return type';
|
||||||
|
|
||||||
|
private NodeFinder $nodeFinder;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private Configuration $configuration,
|
||||||
|
) {
|
||||||
|
$this->nodeFinder = new NodeFinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string<Node>
|
||||||
|
*/
|
||||||
|
public function getNodeType(): string
|
||||||
|
{
|
||||||
|
return ClassMethod::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ClassMethod $node
|
||||||
|
* @return RuleError[]
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node, Scope $scope): array
|
||||||
|
{
|
||||||
|
if (! $this->configuration->isNoFalsyReturnEnabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node->stmts === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node->returnType instanceof Node) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Return_[] $returns */
|
||||||
|
$returns = $this->nodeFinder->findInstanceOf($node->stmts, Return_::class);
|
||||||
|
|
||||||
|
$hasFalseType = false;
|
||||||
|
$hasTrueType = false;
|
||||||
|
|
||||||
|
foreach ($returns as $return) {
|
||||||
|
if (! $return->expr instanceof Expr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exprType = $scope->getType($return->expr);
|
||||||
|
if (! $exprType instanceof ConstantBooleanType) {
|
||||||
|
if ($exprType->isBoolean()->yes()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exprType->getValue()) {
|
||||||
|
$hasTrueType = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasFalseType = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $hasTrueType && $hasFalseType) {
|
||||||
|
return [
|
||||||
|
RuleErrorBuilder::message(self::ERROR_MESSAGE)
|
||||||
|
->identifier('typePerfect.nullOverFalse')
|
||||||
|
->build(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\ValueObject;
|
||||||
|
|
||||||
|
final readonly class MethodCallReference
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $class,
|
||||||
|
private string $method
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClass(): string
|
||||||
|
{
|
||||||
|
return $this->class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMethod(): string
|
||||||
|
{
|
||||||
|
return $this->method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source\AnotherClassWithRun;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\PreferredClassRule\Fixture\SomeStaticCall;
|
||||||
|
|
||||||
|
class DifferentClassSameMethodCallName
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param SomeStaticCall|MethodCall $node
|
||||||
|
*/
|
||||||
|
public function process(AnotherClassWithRun $anotherClassWithRun)
|
||||||
|
{
|
||||||
|
$anotherClassWithRun->run($anotherClassWithRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SomeStaticCall|MethodCall $node
|
||||||
|
*/
|
||||||
|
private function run(Node $node)
|
||||||
|
{
|
||||||
|
if ($node->name instanceof MethodCall) {
|
||||||
|
$this->run($node->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
class DoubleShot
|
||||||
|
{
|
||||||
|
public function run(Node\Arg $arg, Node\Param $param)
|
||||||
|
{
|
||||||
|
$this->isCheck($arg, $param);
|
||||||
|
}
|
||||||
|
private function isCheck(\PhpParser\Node $arg, \PhpParser\Node $param)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
|
||||||
|
class Fixture
|
||||||
|
{
|
||||||
|
public function run(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof MethodCall) {
|
||||||
|
$this->isCheck($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isCheck(Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SkipAbstractBase;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source\ConceptBase;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source\ConceptImpl1;
|
||||||
|
|
||||||
|
class MyService {
|
||||||
|
public function run(ConceptImpl1 $arg)
|
||||||
|
{
|
||||||
|
$this->isCheck($arg);
|
||||||
|
}
|
||||||
|
private function isCheck(ConceptBase $arg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
|
||||||
|
class SkipAlreadyCorrectType
|
||||||
|
{
|
||||||
|
public function run(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof MethodCall) {
|
||||||
|
$this->isCheck($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isCheck(MethodCall $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node\Expr\Assign;
|
||||||
|
use PhpParser\Node\Stmt\Return_;
|
||||||
|
|
||||||
|
final class SkipCorrectUnionType
|
||||||
|
{
|
||||||
|
public function run(\PhpParser\Node $node)
|
||||||
|
{
|
||||||
|
if (! $node instanceof Assign && ! $node instanceof Return_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->isIncludeOnceOrRequireOnce($node)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Assign|Return_ $node
|
||||||
|
*/
|
||||||
|
private function isIncludeOnceOrRequireOnce(\PhpParser\Node $node)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PHPStan\Node\FileNode;
|
||||||
|
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
|
||||||
|
|
||||||
|
final class SkipDuplicatedCallOfSameMethodWithComment2
|
||||||
|
{
|
||||||
|
public function firstMethod()
|
||||||
|
{
|
||||||
|
$this->printFile(new FileWithoutNamespace([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printFile(\PhpParser\Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function secondMethod()
|
||||||
|
{
|
||||||
|
// some comment
|
||||||
|
$this->printFile(new FileNode([]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T of MethodCall
|
||||||
|
*/
|
||||||
|
class SkipGenericType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param T $node
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function run(Node $node)
|
||||||
|
{
|
||||||
|
$this->getsAGeneric($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param T $node
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function getsAGeneric(Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
|
||||||
|
class SkipMayOverrideArg
|
||||||
|
{
|
||||||
|
public function run(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof MethodCall) {
|
||||||
|
|
||||||
|
// on this part, $node may be overriden
|
||||||
|
$node = $this->mayOverrideNode();
|
||||||
|
|
||||||
|
$this->isCheck($node);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isCheck(Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
class SkipMixed
|
||||||
|
{
|
||||||
|
public function run($arg)
|
||||||
|
{
|
||||||
|
$this->isCheck($arg);
|
||||||
|
}
|
||||||
|
private function isCheck(\PhpParser\Node $arg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PhpParser\Node\Expr\PropertyFetch;
|
||||||
|
|
||||||
|
class SkipMultipleUsed
|
||||||
|
{
|
||||||
|
public function run(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof MethodCall) {
|
||||||
|
$this->isCheck($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node instanceof PropertyFetch) {
|
||||||
|
$this->isCheck($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isCheck(Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
final class SkipNoArgs
|
||||||
|
{
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipNotFromThis
|
||||||
|
{
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$obj = new \DateTime('now');
|
||||||
|
$obj->format('Y-m-d');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
|
||||||
|
class SkipNotPrivate
|
||||||
|
{
|
||||||
|
public function run(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof MethodCall) {
|
||||||
|
$this->isCheckNotPrivate($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCheckNotPrivate(Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PHPStan\Type\MixedType;
|
||||||
|
|
||||||
|
class SkipOptedOut
|
||||||
|
{
|
||||||
|
public function run(\PHPStan\Type\Type $type)
|
||||||
|
{
|
||||||
|
if ($type instanceof MixedType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->isCheck($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isCheck(\PHPStan\Type\Type $type)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
final class SkipParentNotIf
|
||||||
|
{
|
||||||
|
public function run(Node $node)
|
||||||
|
{
|
||||||
|
$this->execute($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\PreferredClassRule\Fixture\SomeStaticCall;
|
||||||
|
|
||||||
|
class SkipRecursive
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param SomeStaticCall|MethodCall $node
|
||||||
|
*/
|
||||||
|
public function processNode(Node $node): void
|
||||||
|
{
|
||||||
|
$this->run($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SomeStaticCall|MethodCall $node
|
||||||
|
*/
|
||||||
|
private function run(Node $node): void
|
||||||
|
{
|
||||||
|
if ($node->name instanceof MethodCall) {
|
||||||
|
$this->run($node->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source\MyIterator;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source\MyPreise;
|
||||||
|
|
||||||
|
final class SkipSameGeneric
|
||||||
|
{
|
||||||
|
protected function getPriceAndCalculateTableVM(): void
|
||||||
|
{
|
||||||
|
|
||||||
|
$prices = $this->getPricesForArticle();
|
||||||
|
$this->getAmounts($prices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param MyIterator<MyPreise> $prices
|
||||||
|
*/
|
||||||
|
private function getAmounts(MyIterator $prices): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return MyIterator<MyPreise>
|
||||||
|
*/
|
||||||
|
private function getPricesForArticle(): MyIterator
|
||||||
|
{
|
||||||
|
/** @var MyIterator<MyPreise> */
|
||||||
|
return new MyIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
class SkipVariadics
|
||||||
|
{
|
||||||
|
public function run(Node $node, Node $node2, Node $node3)
|
||||||
|
{
|
||||||
|
$arr = [$node, $node2, $node3];
|
||||||
|
$this->takesVariadic(...$arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function takesVariadic(Node ...$node)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
use Override;
|
||||||
|
use PhpParser\Node\Arg;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PhpParser\Node\Param;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Testing\RuleTestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Rector\TypePerfect\Rules\NarrowPrivateClassMethodParamTypeRule;
|
||||||
|
|
||||||
|
final class NarrowPrivateClassMethodParamTypeRuleTest extends RuleTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param list<array{0: string, 1: int, 2?: string|null}> $expectedErrorsWithLines
|
||||||
|
*/
|
||||||
|
#[DataProvider('provideData')]
|
||||||
|
public function testRule(string $filePath, array $expectedErrorsWithLines): void
|
||||||
|
{
|
||||||
|
$this->analyse([$filePath], $expectedErrorsWithLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Iterator<array<array<int, mixed>, mixed>>
|
||||||
|
*/
|
||||||
|
public static function provideData(): Iterator
|
||||||
|
{
|
||||||
|
yield [__DIR__ . '/Fixture/SkipDuplicatedCallOfSameMethodWithComment.php', []];
|
||||||
|
|
||||||
|
yield [__DIR__ . '/Fixture/SkipCorrectUnionType.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipRecursive.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipMixed.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipOptedOut.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipNotFromThis.php', []];
|
||||||
|
|
||||||
|
yield [__DIR__ . '/Fixture/SkipParentNotIf.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipNoArgs.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipAlreadyCorrectType.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipMayOverrideArg.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipMultipleUsed.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipNotPrivate.php', []];
|
||||||
|
|
||||||
|
$errorMessage = sprintf(NarrowPrivateClassMethodParamTypeRule::ERROR_MESSAGE, 1, MethodCall::class);
|
||||||
|
yield [__DIR__ . '/Fixture/Fixture.php', [[$errorMessage, 19]]];
|
||||||
|
yield [__DIR__ . '/Fixture/DifferentClassSameMethodCallName.php', [[$errorMessage, 25]]];
|
||||||
|
|
||||||
|
$argErrorMessage = sprintf(NarrowPrivateClassMethodParamTypeRule::ERROR_MESSAGE, 1, Arg::class);
|
||||||
|
$paramErrorMessage = sprintf(NarrowPrivateClassMethodParamTypeRule::ERROR_MESSAGE, 2, Param::class);
|
||||||
|
yield [__DIR__ . '/Fixture/DoubleShot.php', [[$argErrorMessage, 15], [$paramErrorMessage, 15]]];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipGenericType.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipAbstractBase.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipVariadics.php', []];
|
||||||
|
yield [__DIR__ . '/Fixture/SkipSameGeneric.php', []];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
#[Override]
|
||||||
|
public static function getAdditionalConfigFiles(): array
|
||||||
|
{
|
||||||
|
return [__DIR__ . '/../../../config/extension.neon'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRule(): Rule
|
||||||
|
{
|
||||||
|
return self::getContainer()->getByType(NarrowPrivateClassMethodParamTypeRule::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
final class AnotherClassWithRun
|
||||||
|
{
|
||||||
|
public function run(Node $node)
|
||||||
|
{
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source;
|
||||||
|
|
||||||
|
|
||||||
|
abstract class ConceptBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source;
|
||||||
|
|
||||||
|
class ConceptImpl1 extends ConceptBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
final class MyIterator
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPrivateClassMethodParamTypeRule\Source;
|
||||||
|
|
||||||
|
class MyPreise
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class ExplicitlyNullableParams
|
||||||
|
{
|
||||||
|
public function test(
|
||||||
|
?bool $bool = true,
|
||||||
|
): bool
|
||||||
|
{
|
||||||
|
return $bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function run(): void
|
||||||
|
{
|
||||||
|
$skipDateTimeMix = new ExplicitlyNullableParams();
|
||||||
|
$skipDateTimeMix->test(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\FirstClassCallables;
|
||||||
|
|
||||||
|
final class CallVariadics
|
||||||
|
{
|
||||||
|
public function callMe(SomeCalledMethod $someCalledMethod)
|
||||||
|
{
|
||||||
|
$closure = $someCalledMethod->callMe(...);
|
||||||
|
|
||||||
|
$someCalledMethod->callMe(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\FirstClassCallables;
|
||||||
|
|
||||||
|
final class SomeCalledMethod
|
||||||
|
{
|
||||||
|
public function callMe($number)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\Generics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
class GenericA
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/** @param GenericA<string> $g */
|
||||||
|
public function doFoo(GenericA $g):void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param GenericA<string> $g */
|
||||||
|
function doBar($g):void {
|
||||||
|
$this->doFoo($g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\Generics;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\SkipSameGeneric\MyGroup;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\SkipSameGeneric\MyIterator;
|
||||||
|
|
||||||
|
final class SkipSameGeneric
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param MyIterator<MyGroup> $_werbemasse
|
||||||
|
*/
|
||||||
|
public function setWerbemasse($_werbemasse): void
|
||||||
|
{
|
||||||
|
$this->_werbemasse = $_werbemasse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
class HandleDefaultValue {
|
||||||
|
public function takesUnionWithDifferentDefaultValue(
|
||||||
|
int|string $value = "hallo"
|
||||||
|
): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function takesUnionWithSameTypeDefaultValue(
|
||||||
|
int|string $value = 123
|
||||||
|
): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function run(): void
|
||||||
|
{
|
||||||
|
$o = new HandleDefaultValue();
|
||||||
|
$o->takesUnionWithDifferentDefaultValue(1);
|
||||||
|
|
||||||
|
$o = new HandleDefaultValue();
|
||||||
|
$o->takesUnionWithSameTypeDefaultValue(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class PublicDoubleShot
|
||||||
|
{
|
||||||
|
public function callMeTwice($number)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
final class SkipApiMarked
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
public function callNode(Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipCallable
|
||||||
|
{
|
||||||
|
public function map(callable $mapper): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->map(function () {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipClassStringPassed
|
||||||
|
{
|
||||||
|
public function resolve(string $className)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipClosure
|
||||||
|
{
|
||||||
|
public function map(\Closure $mapper): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->map(function () {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipDateTimeMix
|
||||||
|
{
|
||||||
|
public function markDate(
|
||||||
|
\DateTimeInterface $date
|
||||||
|
): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function run(): void
|
||||||
|
{
|
||||||
|
$skipDateTimeMix = new SkipDateTimeMix();
|
||||||
|
$skipDateTimeMix->markDate(
|
||||||
|
new \DateTimeImmutable('15:30')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipDefault
|
||||||
|
{
|
||||||
|
public function dontSetParamValue(
|
||||||
|
string $Id = ''
|
||||||
|
): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function run(): void
|
||||||
|
{
|
||||||
|
$skipDefault = new SkipDefault();
|
||||||
|
$skipDefault->dontSetParamValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\PickReasonEnum;
|
||||||
|
|
||||||
|
class SkipEnum
|
||||||
|
{
|
||||||
|
public function pick(PickReasonEnum $pickReasonEnum): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->pick(PickReasonEnum::BarcodeIssue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PhpParser\Node\Expr\StaticCall;
|
||||||
|
|
||||||
|
final class SkipEqualUnionType
|
||||||
|
{
|
||||||
|
public function run(MethodCall|StaticCall $obj)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runTernary(StaticCall|MethodCall $obj)
|
||||||
|
{
|
||||||
|
return $obj instanceof StaticCall
|
||||||
|
? $obj->class
|
||||||
|
: $obj->var;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runTernaryFlipped(StaticCall|MethodCall $obj)
|
||||||
|
{
|
||||||
|
return $obj instanceof MethodCall
|
||||||
|
? $obj->var
|
||||||
|
: $obj->class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Node[]|Node $node
|
||||||
|
*/
|
||||||
|
public function runArrayTyped(array | Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<Node>|Node $node
|
||||||
|
*/
|
||||||
|
public function runArrayTyped2(array | Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\PassMeAsType;
|
||||||
|
|
||||||
|
final class SkipExpectedClassType
|
||||||
|
{
|
||||||
|
public function callMeWithClassType(PassMeAsType $passMeAsType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipMixedAndString
|
||||||
|
{
|
||||||
|
public function resolve($value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipNonPublicClassMethod
|
||||||
|
{
|
||||||
|
public function personInTree()
|
||||||
|
{
|
||||||
|
$this->callMe(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function callMe($number)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
final class SkipNullableCompare
|
||||||
|
{
|
||||||
|
public function callNode(?Node $node)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipProperlyFilledParamType
|
||||||
|
{
|
||||||
|
public function callMeTwice(int $number)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
final class SkipResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param resource $resource
|
||||||
|
*/
|
||||||
|
public function map($resource, string $name): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$resource = @fsockopen('100', 100);
|
||||||
|
$this->map($resource, 'name');
|
||||||
|
|
||||||
|
|
||||||
|
$resource = $this->getFileResource();
|
||||||
|
$this->map($resource, 'surname');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource
|
||||||
|
*/
|
||||||
|
private function getFileResource()
|
||||||
|
{
|
||||||
|
return fopen('file.txt', 'r');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
class SkipSelf {
|
||||||
|
public function takesSelf(self $code): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function run(SkipSelf $self): void
|
||||||
|
{
|
||||||
|
$self->takesSelf(new SkipSelf());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedThisType\CallByThis;
|
||||||
|
|
||||||
|
final class SkipThisPassedExactType
|
||||||
|
{
|
||||||
|
public function run(CallByThis $obj)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
final class SkipUsedInternallyForSecondType
|
||||||
|
{
|
||||||
|
public function run(stdClass|DateTime $obj)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$this->run(new DateTime('now'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\SomeInterface;
|
||||||
|
|
||||||
|
final class ThisPassedFromInterface
|
||||||
|
{
|
||||||
|
public function run(SomeInterface $obj)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
use Override;
|
||||||
|
use PHPStan\Collectors\Collector;
|
||||||
|
use PHPStan\Rules\Rule;
|
||||||
|
use PHPStan\Testing\RuleTestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Rector\TypePerfect\Rules\NarrowPublicClassMethodParamTypeRule;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedThisType\CallByThisFromInterface;
|
||||||
|
|
||||||
|
final class NarrowPublicClassMethodParamTypeRuleTest extends RuleTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string[] $filePaths
|
||||||
|
* @param list<array{0: string, 1: int, 2?: string|null}> $expectedErrorsWithLines
|
||||||
|
*/
|
||||||
|
#[DataProvider('provideData')]
|
||||||
|
public function testRule(array $filePaths, array $expectedErrorsWithLines): void
|
||||||
|
{
|
||||||
|
$this->analyse($filePaths, $expectedErrorsWithLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Iterator<array<array<int, array<int, mixed>>, mixed>>
|
||||||
|
*/
|
||||||
|
public static function provideData(): Iterator
|
||||||
|
{
|
||||||
|
yield [[__DIR__ . '/Fixture/Generics/SkipPassedGenerics.php'], []];
|
||||||
|
|
||||||
|
yield [[__DIR__ . '/Fixture/SkipDefault.php'], []];
|
||||||
|
yield [[__DIR__ . '/Fixture/SkipResource.php'], []];
|
||||||
|
yield [[__DIR__ . '/Fixture/SkipDateTimeMix.php'], []];
|
||||||
|
yield [[__DIR__ . '/Fixture/SkipNonPublicClassMethod.php'], []];
|
||||||
|
|
||||||
|
// skip first class callables as anything can be passed there
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/FirstClassCallables/CallVariadics.php',
|
||||||
|
__DIR__ . '/Fixture/FirstClassCallables/SomeCalledMethod.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip expected scalar type
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipProperlyFilledParamType.php',
|
||||||
|
__DIR__ . '/Source/ExpectedType/FirstTypedCaller.php',
|
||||||
|
__DIR__ . '/Source/ExpectedType/SecondTypedCaller.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip expected object type
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipExpectedClassType.php',
|
||||||
|
__DIR__ . '/Source/ExpectedClassType/FirstClassTypedCaller.php',
|
||||||
|
__DIR__ . '/Source/ExpectedClassType/SecondClassTypedCaller.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip class-string
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipClassStringPassed.php',
|
||||||
|
__DIR__ . '/Source/ExpectedClassString/FirstTypedCaller.php',
|
||||||
|
__DIR__ . '/Source/ExpectedClassString/SecondTypedCaller.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip everything in case of values is mixed
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipMixedAndString.php',
|
||||||
|
__DIR__ . '/Source/MixedAndString/FirstCaller.php',
|
||||||
|
__DIR__ . '/Source/MixedAndString/SecondCaller.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip int + string values
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipMixedAndString.php',
|
||||||
|
__DIR__ . '/Source/MixedAndString/FirstCaller.php',
|
||||||
|
__DIR__ . '/Source/MixedAndString/ThirdCaller.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip nullable compare
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipNullableCompare.php',
|
||||||
|
__DIR__ . '/Source/NullableParam/FirstNullable.php',
|
||||||
|
__DIR__ . '/Source/NullableParam/SecondNullable.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip api
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipApiMarked.php',
|
||||||
|
__DIR__ . '/Source/ExpectedNodeApi/CallWithProperty.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip equal union type
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipEqualUnionType.php',
|
||||||
|
__DIR__ . '/Source/ExpectedUnion/CallUnionType.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip equal union type flipped
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipEqualUnionType.php',
|
||||||
|
__DIR__ . '/Source/ExpectedUnion/CallUnionTypeFlipped.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip equal union type ternary if else
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipEqualUnionType.php',
|
||||||
|
__DIR__ . '/Source/ExpectedUnion/CallUnionTypeTernaryIfElse.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip equal union type ternary if else flipped
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipEqualUnionType.php',
|
||||||
|
__DIR__ . '/Source/ExpectedUnion/CallUnionTypeTernaryIfElseFlipped.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip equal union type array typed
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipEqualUnionType.php',
|
||||||
|
__DIR__ . '/Source/ExpectedUnion/CallUnionArrayType.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip this passed exact type
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipThisPassedExactType.php',
|
||||||
|
__DIR__ . '/Source/ExpectedThisType/CallByThis.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
// skip used internally for second type
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/SkipUsedInternallyForSecondType.php',
|
||||||
|
__DIR__ . '/Source/ExpectedType/OnlyFirstTypeCalledOutside.php',
|
||||||
|
], []];
|
||||||
|
|
||||||
|
$argErrorMessage = sprintf(NarrowPublicClassMethodParamTypeRule::ERROR_MESSAGE, 'int');
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/PublicDoubleShot.php',
|
||||||
|
__DIR__ . '/Source/FirstCaller.php',
|
||||||
|
__DIR__ . '/Source/SecondCaller.php',
|
||||||
|
], [[$argErrorMessage, 9]]];
|
||||||
|
|
||||||
|
// this passed from interface
|
||||||
|
$argErrorMessage = sprintf(
|
||||||
|
NarrowPublicClassMethodParamTypeRule::ERROR_MESSAGE,
|
||||||
|
CallByThisFromInterface::class
|
||||||
|
);
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/ThisPassedFromInterface.php',
|
||||||
|
__DIR__ . '/Source/ExpectedThisType/CallByThisFromInterface.php',
|
||||||
|
], [[$argErrorMessage, 11]]];
|
||||||
|
|
||||||
|
yield [[__DIR__ . '/Fixture/SkipSelf.php'], []];
|
||||||
|
|
||||||
|
yield [[__DIR__ . '/Fixture/SkipClosure.php'], []];
|
||||||
|
|
||||||
|
yield [[__DIR__ . '/Fixture/SkipCallable.php'], []];
|
||||||
|
|
||||||
|
yield [[__DIR__ . '/Fixture/SkipEnum.php'], []];
|
||||||
|
|
||||||
|
$argErrorMessage = sprintf(NarrowPublicClassMethodParamTypeRule::ERROR_MESSAGE, 'int');
|
||||||
|
|
||||||
|
yield [[__DIR__ . '/Fixture/HandleDefaultValue.php'], [[$argErrorMessage, 15]]];
|
||||||
|
|
||||||
|
$argErrorMessage = sprintf(NarrowPublicClassMethodParamTypeRule::ERROR_MESSAGE, 'bool');
|
||||||
|
yield [[__DIR__ . '/Fixture/ExplicitlyNullableParams.php'], [[$argErrorMessage, 9]]];
|
||||||
|
|
||||||
|
// skip generics
|
||||||
|
yield [[
|
||||||
|
__DIR__ . '/Fixture/Generics/SkipSameGeneric.php',
|
||||||
|
__DIR__ . '/Source/SkipSameGeneric/MyGroup.php',
|
||||||
|
__DIR__ . '/Source/SkipSameGeneric/MyIterator.php',
|
||||||
|
__DIR__ . '/Source/SkipSameGeneric/MyService.php',
|
||||||
|
], []];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
#[Override]
|
||||||
|
public static function getAdditionalConfigFiles(): array
|
||||||
|
{
|
||||||
|
return [__DIR__ . '/../../../config/extension.neon'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRule(): Rule
|
||||||
|
{
|
||||||
|
return self::getContainer()->getByType(NarrowPublicClassMethodParamTypeRule::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warning, just spent hour looking for why the test does not run :D This should be implicit part of the parent
|
||||||
|
* class.
|
||||||
|
*
|
||||||
|
* @return Collector[]
|
||||||
|
*/
|
||||||
|
#[Override]
|
||||||
|
protected function getCollectors(): array
|
||||||
|
{
|
||||||
|
return self::getContainer()->getServicesByTag('phpstan.collector');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedClassString;
|
||||||
|
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipClassStringPassed;
|
||||||
|
|
||||||
|
final class FirstTypedCaller
|
||||||
|
{
|
||||||
|
public function run(ClassReflection $classReflection)
|
||||||
|
{
|
||||||
|
$skipClassStringPassed = new SkipClassStringPassed();
|
||||||
|
$skipClassStringPassed->resolve($classReflection->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedClassString;
|
||||||
|
|
||||||
|
use PHPStan\Reflection\ClassReflection;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipClassStringPassed;
|
||||||
|
|
||||||
|
final class SecondTypedCaller
|
||||||
|
{
|
||||||
|
public function run(ClassReflection $classReflection)
|
||||||
|
{
|
||||||
|
$skipClassStringPassed = new SkipClassStringPassed();
|
||||||
|
$skipClassStringPassed->resolve($classReflection->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedClassType;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipExpectedClassType;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\PassMeAsType;
|
||||||
|
|
||||||
|
final class FirstClassTypedCaller
|
||||||
|
{
|
||||||
|
public function goForIt(SkipExpectedClassType $skipExpectedClassType)
|
||||||
|
{
|
||||||
|
$knownType = new PassMeAsType();
|
||||||
|
$skipExpectedClassType->callMeWithClassType($knownType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedClassType;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipExpectedClassType;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\PassMeAsType;
|
||||||
|
|
||||||
|
final class SecondClassTypedCaller
|
||||||
|
{
|
||||||
|
public function goForIt(SkipExpectedClassType $skipExpectedClassType)
|
||||||
|
{
|
||||||
|
$knownType = new PassMeAsType();
|
||||||
|
$skipExpectedClassType->callMeWithClassType($knownType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedNodeApi;
|
||||||
|
|
||||||
|
use PhpParser\Node\Stmt\Property;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipApiMarked;
|
||||||
|
|
||||||
|
final class CallWithProperty
|
||||||
|
{
|
||||||
|
public function run(SkipApiMarked $skipApiMarked, Property $property): void
|
||||||
|
{
|
||||||
|
$skipApiMarked->callNode($property);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedThisType;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipThisPassedExactType;
|
||||||
|
|
||||||
|
final class CallByThis
|
||||||
|
{
|
||||||
|
public function run(SkipThisPassedExactType $skipThisPassedExactType): void
|
||||||
|
{
|
||||||
|
$skipThisPassedExactType->run($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedThisType;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\ThisPassedFromInterface;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\SomeInterface;
|
||||||
|
|
||||||
|
final class CallByThisFromInterface implements SomeInterface
|
||||||
|
{
|
||||||
|
public function run(ThisPassedFromInterface $thisPassedFromInterface): void
|
||||||
|
{
|
||||||
|
$thisPassedFromInterface->run($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedType;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipProperlyFilledParamType;
|
||||||
|
|
||||||
|
final class FirstTypedCaller
|
||||||
|
{
|
||||||
|
public function goForIt(SkipProperlyFilledParamType $skipProperlyFilledParamType)
|
||||||
|
{
|
||||||
|
$skipProperlyFilledParamType->callMeTwice(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedType;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipUsedInternallyForSecondType;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
final class OnlyFirstTypeCalledOutside
|
||||||
|
{
|
||||||
|
public function run(SkipUsedInternallyForSecondType $skipUsedInternallyForSecondType)
|
||||||
|
{
|
||||||
|
$skipUsedInternallyForSecondType->run(new stdClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedType;
|
||||||
|
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipProperlyFilledParamType;
|
||||||
|
|
||||||
|
final class SecondTypedCaller
|
||||||
|
{
|
||||||
|
public function goForIt(SkipProperlyFilledParamType $skipProperlyFilledParamType)
|
||||||
|
{
|
||||||
|
$skipProperlyFilledParamType->callMeTwice(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedUnion;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipEqualUnionType;
|
||||||
|
|
||||||
|
final class CallUnionTypeArrayType
|
||||||
|
{
|
||||||
|
public function run(SkipEqualUnionType $skipEqualUnionType, Node $node): void
|
||||||
|
{
|
||||||
|
/** @var Node[]|Node $node */
|
||||||
|
$node = rand(0, 1)
|
||||||
|
? $node
|
||||||
|
: [$node];
|
||||||
|
|
||||||
|
$skipEqualUnionType->runArrayTyped($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run2(SkipEqualUnionType $skipEqualUnionType, Node $node): void
|
||||||
|
{
|
||||||
|
/** @var Node[]|Node $node */
|
||||||
|
$node = rand(0, 1)
|
||||||
|
? $node
|
||||||
|
: [$node];
|
||||||
|
|
||||||
|
$skipEqualUnionType->runArrayTyped2($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Source\ExpectedUnion;
|
||||||
|
|
||||||
|
use PhpParser\Node\Expr\MethodCall;
|
||||||
|
use PhpParser\Node\Expr\StaticCall;
|
||||||
|
use Rector\TypePerfect\Tests\Rules\NarrowPublicClassMethodParamTypeRule\Fixture\SkipEqualUnionType;
|
||||||
|
|
||||||
|
final class CallUnionType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param MethodCall[]|StaticCall[] $data
|
||||||
|
*/
|
||||||
|
public function run(SkipEqualUnionType $skipEqualUnionType, array $data): void
|
||||||
|
{
|
||||||
|
foreach ($data as $value) {
|
||||||
|
$skipEqualUnionType->run($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue