Compare commits
2 Commits
0107b5050a
...
c8c60658d5
| Author | SHA1 | Date |
|---|---|---|
|
|
c8c60658d5 | |
|
|
724ed522d7 |
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.2.6",
|
||||
"phpstan/phpstan": "^2.2.7",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"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"
|
||||
},
|
||||
"config": {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8e49cfe62670da56315a4c47e1b01b5d",
|
||||
"content-hash": "3579343022b2a28c7195fe30b1752bfb",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
|
|
@ -130,11 +130,11 @@
|
|||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.2.6",
|
||||
"version": "2.2.7",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a6e9b5a9420f6109c091e87d82683bd1a80b87ed",
|
||||
"reference": "a6e9b5a9420f6109c091e87d82683bd1a80b87ed",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921",
|
||||
"reference": "692db47b9dddb0487934e5236e77d48594aef921",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -190,7 +190,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-26T21:22:49+00:00"
|
||||
"time": "2026-07-29T17:39:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
|
|
@ -246,29 +246,31 @@
|
|||
},
|
||||
{
|
||||
"name": "tomasvotruba/type-coverage",
|
||||
"version": "2.2.2",
|
||||
"version": "2.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/TomasVotruba/type-coverage.git",
|
||||
"reference": "25f298265c823e8fb0505169135855e1e6a91021"
|
||||
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/25f298265c823e8fb0505169135855e1e6a91021",
|
||||
"reference": "25f298265c823e8fb0505169135855e1e6a91021",
|
||||
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.4",
|
||||
"phpstan/phpstan": "^2.2"
|
||||
"phpstan/phpstan": "^2.2",
|
||||
"webmozart/assert": "^1.11 || ^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpunit/phpunit": "^12.5",
|
||||
"phpunit/phpunit": "^13.2",
|
||||
"rector/jack": "^1.0",
|
||||
"rector/rector": "^2.4",
|
||||
"rector/rector": "^2.5",
|
||||
"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",
|
||||
"tracy/tracy": "^2.12"
|
||||
},
|
||||
|
|
@ -276,12 +278,14 @@
|
|||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"config/extension.neon"
|
||||
"config/extension.neon",
|
||||
"packages/type-perfect/config/extension.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Rector\\TypePerfect\\": "packages/type-perfect/src",
|
||||
"TomasVotruba\\TypeCoverage\\": "src"
|
||||
}
|
||||
},
|
||||
|
|
@ -296,7 +300,7 @@
|
|||
],
|
||||
"support": {
|
||||
"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": [
|
||||
{
|
||||
|
|
@ -308,7 +312,73 @@
|
|||
"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": [],
|
||||
|
|
|
|||
|
|
@ -129,12 +129,12 @@
|
|||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.2.6",
|
||||
"version_normalized": "2.2.6.0",
|
||||
"version": "2.2.7",
|
||||
"version_normalized": "2.2.7.0",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a6e9b5a9420f6109c091e87d82683bd1a80b87ed",
|
||||
"reference": "a6e9b5a9420f6109c091e87d82683bd1a80b87ed",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921",
|
||||
"reference": "692db47b9dddb0487934e5236e77d48594aef921",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"conflict": {
|
||||
"phpstan/phpstan-shim": "*"
|
||||
},
|
||||
"time": "2026-07-26T21:22:49+00:00",
|
||||
"time": "2026-07-29T17:39:32+00:00",
|
||||
"bin": [
|
||||
"phpstan",
|
||||
"phpstan.phar"
|
||||
|
|
@ -251,45 +251,49 @@
|
|||
},
|
||||
{
|
||||
"name": "tomasvotruba/type-coverage",
|
||||
"version": "2.2.2",
|
||||
"version_normalized": "2.2.2.0",
|
||||
"version": "2.3.0",
|
||||
"version_normalized": "2.3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/TomasVotruba/type-coverage.git",
|
||||
"reference": "25f298265c823e8fb0505169135855e1e6a91021"
|
||||
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/25f298265c823e8fb0505169135855e1e6a91021",
|
||||
"reference": "25f298265c823e8fb0505169135855e1e6a91021",
|
||||
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.4",
|
||||
"phpstan/phpstan": "^2.2"
|
||||
"phpstan/phpstan": "^2.2",
|
||||
"webmozart/assert": "^1.11 || ^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpunit/phpunit": "^12.5",
|
||||
"phpunit/phpunit": "^13.2",
|
||||
"rector/jack": "^1.0",
|
||||
"rector/rector": "^2.4",
|
||||
"rector/rector": "^2.5",
|
||||
"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",
|
||||
"tracy/tracy": "^2.12"
|
||||
},
|
||||
"time": "2026-06-07T12:35:00+00:00",
|
||||
"time": "2026-07-29T20:32:37+00:00",
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"config/extension.neon"
|
||||
"config/extension.neon",
|
||||
"packages/type-perfect/config/extension.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Rector\\TypePerfect\\": "packages/type-perfect/src",
|
||||
"TomasVotruba\\TypeCoverage\\": "src"
|
||||
}
|
||||
},
|
||||
|
|
@ -304,7 +308,7 @@
|
|||
],
|
||||
"support": {
|
||||
"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": [
|
||||
{
|
||||
|
|
@ -317,6 +321,75 @@
|
|||
}
|
||||
],
|
||||
"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,
|
||||
|
|
@ -325,6 +398,7 @@
|
|||
"phpstan/extension-installer",
|
||||
"phpstan/phpstan",
|
||||
"phpstan/phpstan-strict-rules",
|
||||
"tomasvotruba/type-coverage"
|
||||
"tomasvotruba/type-coverage",
|
||||
"webmozart/assert"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
'name' => '__root__',
|
||||
'pretty_version' => '7.0.x-dev',
|
||||
'version' => '7.0.9999999.9999999-dev',
|
||||
'reference' => 'a6ad4baae34da90666233dd9d2832c1941ceb072',
|
||||
'reference' => '724ed522d74918b0d84a50cdee4d0d2f7a5ba67c',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
'__root__' => array(
|
||||
'pretty_version' => '7.0.x-dev',
|
||||
'version' => '7.0.9999999.9999999-dev',
|
||||
'reference' => 'a6ad4baae34da90666233dd9d2832c1941ceb072',
|
||||
'reference' => '724ed522d74918b0d84a50cdee4d0d2f7a5ba67c',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
|
@ -38,9 +38,9 @@
|
|||
'dev_requirement' => true,
|
||||
),
|
||||
'phpstan/phpstan' => array(
|
||||
'pretty_version' => '2.2.6',
|
||||
'version' => '2.2.6.0',
|
||||
'reference' => 'a6e9b5a9420f6109c091e87d82683bd1a80b87ed',
|
||||
'pretty_version' => '2.2.7',
|
||||
'version' => '2.2.7.0',
|
||||
'reference' => '692db47b9dddb0487934e5236e77d48594aef921',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpstan/phpstan',
|
||||
'aliases' => array(),
|
||||
|
|
@ -56,13 +56,22 @@
|
|||
'dev_requirement' => true,
|
||||
),
|
||||
'tomasvotruba/type-coverage' => array(
|
||||
'pretty_version' => '2.2.2',
|
||||
'version' => '2.2.2.0',
|
||||
'reference' => '25f298265c823e8fb0505169135855e1e6a91021',
|
||||
'pretty_version' => '2.3.0',
|
||||
'version' => '2.3.0.0',
|
||||
'reference' => '7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644',
|
||||
'type' => 'phpstan-extension',
|
||||
'install_path' => __DIR__ . '/../tomasvotruba/type-coverage',
|
||||
'aliases' => array(),
|
||||
'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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,16 +1,16 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmpmem0ACgkQUcZzBf/C
|
||||
5cBRFA/8Dd+m2oNrmAr/YiYNYS/mEb/mPN7+4iPAYDmyFqcDlvXV7YWzxWxfiHm/
|
||||
IkxRoteTpUJgD9sOwgEbsxKrdbMbKjeT4n+Kl9UjGkrlSYXkFW0f/Wyiwwhnn1iQ
|
||||
pBZu/WVL0zU/qgeMtZq+UscX8z+Ze6mJBVXnf958oX/5YkiBp7kCSLy5DsBDrrFd
|
||||
54lv9oAHrUst3t/jS0eO809msJnwoAz6EHfc5m6N7vHRt1kHbgJCdXlnmtLvvt25
|
||||
BVwjV3bgVcfXWWEBeYVZz/0U4O8Bo3MbCuIf+AUAv8s7BTt5OasLb++5WRskQ8kj
|
||||
lO4h3DHnmEfqQ2+Vl0XoFfmVSLQOZYcZm5oLlCq8Ul3gKLw54reNWfuN6v519NY1
|
||||
dWbcd2FEkWjPNOA/068NPhW73N++fkmUh1U+KqMZ0qqZV6ms9ZG4opryryAxupUO
|
||||
pY3lMyk/NfdHwejqbDNl0BpGNcmzVVIO40yCgWv0YNnWzThpF8pivi6kOC3wFIt+
|
||||
Fl3pHu5C096/p1VH24+qt9a0/R5HkSm2KS7PJIkQGWWWNCYXERbIRJzKzFFb/xjJ
|
||||
KJ8Zdr97+/F/M912+MUZRd+niPC190eMr1XTDsnPsGwp9wBX7XAec0+1wJ3BbMD6
|
||||
kVeEXvv8fSNmTpV6K2upbj66DUicJPXbaTGjC3UDC4WpPYm8g+8=
|
||||
=CXwz
|
||||
iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmpqOpYACgkQUcZzBf/C
|
||||
5cCEABAAiNhRb1h8uQWaC3yY2aDdrISyH/ONSWanM56iFhsOP7n34Yyjwr/d3ywv
|
||||
pFvy8CERGuIQj/3/Y4c8S84bpRZtZWOJ0+ti/dGsPG9nG4IMBpyOjaoHfqTuIqnI
|
||||
qpD+2xMNG6iVhoYrpPcTi85A1RqJyh+T+7OjMml63EjhApY7A/zM+yVvktTmRBGe
|
||||
d15vdOErXegl0i/2dSFgqoWmFtTNqy9twQutupjQeehrcTX7bjnqVvHL6E65dlLt
|
||||
wPFjAU9CnQCkT4mmyt7tHuHuUL/uuBnJDRipB2KCO7K5rG2SbX2r6YJTt81AcvQ7
|
||||
n0o6AMWf9YaTuzp73i2H1QMMsP/845CCHih8lJsg6a1VcdmEsdf+yvj4XJrjZgR1
|
||||
4dKoA58zjX1SJ0k+PHl04eshWlJhXWXioUNyD2JXrlZP0l6GuXZnO7X3QYPtFcL0
|
||||
C5mADqt5X1SIAiQYOyW/KjRmbt4EYjQYCBo+AQQR2yjm3FUjrfEI0rjF96/MIksp
|
||||
R73S9JKS+Fo/igbI86iJoNse4+ijwyGgT3702fGqUyAPVLDZptEUMXuAnJ276RO1
|
||||
URSHUXWTbPDCjoMOeOO8IcaKhCVnEElCssPsZCLZdoYWM6wM/4bdN/iOIHbIJNWL
|
||||
nuw97z7kkY2rvPCHNmdSvNQrH4Xe++9TtcDParXOoelFKwnkkU0=
|
||||
=YVL9
|
||||
-----END PGP SIGNATURE-----
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
2d4b432
|
||||
86312b7
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -138,3 +138,406 @@ vendor/bin/phpstan
|
|||
<br>
|
||||
|
||||
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()
|
||||
// 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"],
|
||||
"require": {
|
||||
"php": "^8.4",
|
||||
"phpstan/phpstan": "^2.2"
|
||||
"phpstan/phpstan": "^2.2",
|
||||
"webmozart/assert": "^1.11 || ^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpunit/phpunit": "^12.5",
|
||||
"symplify/easy-coding-standard": "^13.1",
|
||||
"rector/rector": "^2.4",
|
||||
"phpunit/phpunit": "^13.2",
|
||||
"symplify/easy-coding-standard": "^13.2",
|
||||
"rector/rector": "^2.5",
|
||||
"tracy/tracy": "^2.12",
|
||||
"tomasvotruba/unused-public": "^2.2",
|
||||
"rector/jack": "^1.0",
|
||||
"shipmonk/composer-dependency-analyser": "^1.8"
|
||||
"shipmonk/composer-dependency-analyser": "^1.8",
|
||||
"symfony/dom-crawler": "^8.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"TomasVotruba\\TypeCoverage\\": "src"
|
||||
"TomasVotruba\\TypeCoverage\\": "src",
|
||||
"Rector\\TypePerfect\\": "packages/type-perfect/src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"TomasVotruba\\TypeCoverage\\Tests\\": "tests"
|
||||
"TomasVotruba\\TypeCoverage\\Tests\\": "tests",
|
||||
"Rector\\TypePerfect\\Tests\\": "packages/type-perfect/tests"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
|
@ -43,7 +47,8 @@
|
|||
"extra": {
|
||||
"phpstan": {
|
||||
"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)
|
||||
{
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue