From c92cb4fddf2024dcb5748bb56feb86017cbb410e Mon Sep 17 00:00:00 2001 From: TangRufus Date: Mon, 13 Oct 2025 00:49:25 +0100 Subject: [PATCH] Add PHPStan --- .github/workflows/lint.yml | 30 ++++ composer.json | 15 +- composer.lock | 269 +++++++++++++++++++++++++++++- phpstan.neon | 5 + src/Console/ConstraintCommand.php | 2 +- src/Console/ModeOption.php | 2 +- src/Console/SourceOption.php | 2 +- src/MinorOnlyMatrix.php | 1 - src/Releases/OfflineReleases.php | 5 + src/Releases/PhpNetReleases.php | 13 +- src/Versions.php | 5 +- 11 files changed, 334 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 phpstan.neon diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..80f073a --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,30 @@ +name: Lint + +on: + workflow_dispatch: + pull_request: + branches: + - main + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +permissions: {} + +jobs: + phpstan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + - uses: ramsey/composer-install@v3 + + - run: vendor/bin/phpstan analyse --error-format=github diff --git a/composer.json b/composer.json index ef989d8..bbf0aad 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,12 @@ }, "require-dev": { "mockery/mockery": "^1.6.12", - "pestphp/pest": "^4.1.2" + "pestphp/pest": "^4.1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0" }, "autoload": { "psr-4": { @@ -51,7 +56,8 @@ ], "config": { "allow-plugins": { - "pestphp/pest-plugin": true + "pestphp/pest-plugin": true, + "phpstan/extension-installer": true }, "sort-packages": true }, @@ -67,10 +73,9 @@ "pest:e2e": "XDEBUG_MODE=off pest --group=e2e", "pest:feature": "pest --group=feature", "pest:unit": "pest --group=unit", - "test": [ - "@composer normalize --dry-run", + "lint": [ "pint --test", - "XDEBUG_MODE=off pest" + "phpstan analyse" ] } } diff --git a/composer.lock b/composer.lock index 92f42fb..de85a63 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "aa7895196f2844e1aca7cedc29b15e21", + "content-hash": "9e6325014f190eb6558df42f0b73cbdd", "packages": [ { "name": "composer/semver", @@ -2792,6 +2792,54 @@ }, "time": "2024-11-09T15:12:26+00:00" }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, { "name": "phpstan/phpdoc-parser", "version": "2.3.0", @@ -2839,6 +2887,225 @@ }, "time": "2025-08-30T15:50:23+00:00" }, + { + "name": "phpstan/phpstan", + "version": "2.1.31", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", + "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-10-10T14:14:11+00:00" + }, + { + "name": "phpstan/phpstan-deprecation-rules", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "468e02c9176891cc901143da118f09dc9505fc2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f", + "reference": "468e02c9176891cc901143da118f09dc9505fc2f", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.15" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "support": { + "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3" + }, + "time": "2025-05-14T10:56:57+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.29" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7" + }, + "time": "2025-09-26T11:19:08+00:00" + }, + { + "name": "phpstan/phpstan-symfony", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-symfony.git", + "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/8820c22d785c235f69bb48da3d41e688bc8a1796", + "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.13" + }, + "conflict": { + "symfony/framework-bundle": "<3.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "psr/container": "1.1.2", + "symfony/config": "^5.4 || ^6.1", + "symfony/console": "^5.4 || ^6.1", + "symfony/dependency-injection": "^5.4 || ^6.1", + "symfony/form": "^5.4 || ^6.1", + "symfony/framework-bundle": "^5.4 || ^6.1", + "symfony/http-foundation": "^5.4 || ^6.1", + "symfony/messenger": "^5.4", + "symfony/polyfill-php80": "^1.24", + "symfony/serializer": "^5.4", + "symfony/service-contracts": "^2.2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lukáš Unger", + "email": "looky.msc@gmail.com", + "homepage": "https://lookyman.net" + } + ], + "description": "Symfony Framework extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-symfony/issues", + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.8" + }, + "time": "2025-09-07T06:55:50+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "12.4.0", diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..7cffe7a --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - bin + - src diff --git a/src/Console/ConstraintCommand.php b/src/Console/ConstraintCommand.php index cf0247a..d7892de 100644 --- a/src/Console/ConstraintCommand.php +++ b/src/Console/ConstraintCommand.php @@ -50,7 +50,7 @@ public function __invoke( return Command::FAILURE; } - if (empty($versions)) { + if ($versions === []) { $this->printError( $io, sprintf('No PHP versions could satisfy the constraint "%s".', $constraint) diff --git a/src/Console/ModeOption.php b/src/Console/ModeOption.php index 8e84c31..5c492c8 100644 --- a/src/Console/ModeOption.php +++ b/src/Console/ModeOption.php @@ -15,7 +15,7 @@ public function __construct() Mode::description(), Mode::NAME, null, - Mode::cases(), + array_column(Mode::cases(), 'value'), ); } } diff --git a/src/Console/SourceOption.php b/src/Console/SourceOption.php index 99f1c9e..0024d8f 100644 --- a/src/Console/SourceOption.php +++ b/src/Console/SourceOption.php @@ -15,7 +15,7 @@ public function __construct() Source::description(), Source::NAME, null, - Source::cases(), + array_column(Source::cases(), 'value'), ); } } diff --git a/src/MinorOnlyMatrix.php b/src/MinorOnlyMatrix.php index d5dd3f8..14f1ae9 100644 --- a/src/MinorOnlyMatrix.php +++ b/src/MinorOnlyMatrix.php @@ -20,7 +20,6 @@ public function satisfiedBy(string $constraint): array $versions[] = "{$major}.{$minor}"; } - $versions = array_filter($versions); $versions = array_filter($versions, static fn (string $version) => ! str_starts_with($version, '.')); $versions = array_filter($versions, static fn (string $version) => ! str_ends_with($version, '.')); $versions = array_unique($versions); diff --git a/src/Releases/OfflineReleases.php b/src/Releases/OfflineReleases.php index 671447e..c6b4683 100644 --- a/src/Releases/OfflineReleases.php +++ b/src/Releases/OfflineReleases.php @@ -4,6 +4,7 @@ namespace TypistTech\PhpMatrix\Releases; +use RuntimeException; use TypistTech\PhpMatrix\ReleasesInterface; class OfflineReleases implements ReleasesInterface @@ -16,7 +17,11 @@ class OfflineReleases implements ReleasesInterface public function all(): array { $content = file_get_contents(self::ALL_VERSIONS_FILE); + if ($content === false) { + throw new RuntimeException('Could not read all versions file'); + } + /** @var string[] */ return json_decode($content, true, 512, JSON_THROW_ON_ERROR); } } diff --git a/src/Releases/PhpNetReleases.php b/src/Releases/PhpNetReleases.php index 1c61afb..f0f00a5 100644 --- a/src/Releases/PhpNetReleases.php +++ b/src/Releases/PhpNetReleases.php @@ -28,7 +28,7 @@ public function all(): array { $promises = []; foreach (self::MAJORS as $major) { - $promises[$major] = $this->http->getAsync(self::ENDPOINT, [ + $promises[$major] = $this->http->requestAsync('GET', self::ENDPOINT, [ 'query' => [ 'json' => true, 'max' => 1000, @@ -37,8 +37,12 @@ public function all(): array ]); } - // Wait for the requests to complete; throws a ConnectException - // if any of the requests fail + /** + * Wait for the requests to complete; throws a ConnectException + * if any of the requests fail + * + * @var ResponseInterface[] $responses + */ $responses = Utils::unwrap($promises); $contents = array_map( @@ -48,14 +52,15 @@ public function all(): array $releases = []; foreach ($contents as $content) { + /** @var mixed[] $data */ $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR); $releases[] = array_keys($data); } $releases = array_merge(...$releases); - $releases = array_filter($releases); $releases = array_filter($releases, 'is_string'); + $releases = array_filter($releases, static fn (string $release) => $release !== ''); $releases = array_unique($releases); return array_values($releases); diff --git a/src/Versions.php b/src/Versions.php index 66a8c88..ccb64e1 100644 --- a/src/Versions.php +++ b/src/Versions.php @@ -9,9 +9,12 @@ readonly class Versions { + /** + * @return string[] + */ public static function sort(string ...$versions): array { - if (empty($versions)) { + if ($versions === []) { throw new UnexpectedValueException('Argument #1 ($versions) must not be empty'); }