diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..6a1bdf3 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,44 @@ +name: 'PHPStan Static Analysis' + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.2' + coverage: none + tools: composer:v2 + + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest --no-interaction + + - name: Run PHPStan + run: composer phpstan diff --git a/composer.json b/composer.json index 5e9ace7..a234679 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,9 @@ "lucatume/di52": "^3.0", "lucatume/wp-browser": "^3.0.14", "codeception/module-phpbrowser": "^1.0.0", - "codeception/module-asserts": "^1.0.0" + "codeception/module-asserts": "^1.0.0", + "phpstan/phpstan": "^1.12", + "szepeviktor/phpstan-wordpress": "^1.3" }, "autoload": { "psr-4": { @@ -45,5 +47,9 @@ "type": "git", "url": "https://github.com/wordpress/wordpress-develop.git" } - ] + ], + "scripts": { + "phpstan": "phpstan analyse --memory-limit=512M", + "phpstan:baseline": "phpstan analyse --generate-baseline=phpstan-baseline.neon --memory-limit=512M" + } } diff --git a/composer.lock b/composer.lock index 429a4ec..c0f19ea 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": "2784a3dc6410135037ac4c4588d24cd6", + "content-hash": "e030b86c9bdc981348f98418ad2c2e39", "packages": [ { "name": "stellarwp/container-contract", @@ -1894,6 +1894,57 @@ }, "time": "2017-03-05T17:38:23+00:00" }, + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.8.1", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "92e444847d94f7c30f88c60004648f507688acd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/92e444847d94f7c30f88c60004648f507688acd5", + "reference": "92e444847d94f7c30f88c60004648f507688acd5", + "shasum": "" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "5.6.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^5.4", + "php": "^7.4 || ^8.0", + "php-stubs/generator": "^0.8.3", + "phpdocumentor/reflection-docblock": "^5.4.1", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9.5", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-stubs/issues", + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.8.1" + }, + "time": "2025-05-02T12:33:34+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -2121,6 +2172,64 @@ }, "time": "2020-03-05T15:02:03+00:00" }, + { + "name": "phpstan/phpstan", + "version": "1.12.27", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3a6e423c076ab39dfedc307e2ac627ef579db162", + "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162", + "shasum": "" + }, + "require": { + "php": "^7.2|^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-05-21T20:51:45+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "5.3.2", @@ -4851,6 +4960,69 @@ ], "time": "2022-08-02T15:47:23+00:00" }, + { + "name": "szepeviktor/phpstan-wordpress", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/szepeviktor/phpstan-wordpress.git", + "reference": "7f8cfe992faa96b6a33bbd75c7bace98864161e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/7f8cfe992faa96b6a33bbd75c7bace98864161e7", + "reference": "7f8cfe992faa96b6a33bbd75c7bace98864161e7", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0", + "phpstan/phpstan": "^1.10.31", + "symfony/polyfill-php73": "^1.12.0" + }, + "require-dev": { + "composer/composer": "^2.1.14", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^8.0 || ^9.0", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "SzepeViktor\\PHPStan\\WordPress\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress extensions for PHPStan", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", + "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v1.3.5" + }, + "time": "2024-06-28T22:27:19+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.1", @@ -5247,15 +5419,15 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "ext-json": "*" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "7.2" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.6.0" } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..5264ea6 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,26 @@ +includes: + - vendor/szepeviktor/phpstan-wordpress/extension.neon + +parameters: + # Level 5 is a good balance between strictness and practicality + level: 5 + + # Paths to analyze + paths: + - src + + # Exclude paths + excludePaths: + - vendor/* + - _wordpress/* + - _plugins/* + + # Ignore specific errors if needed + ignoreErrors: + # Add specific error patterns to ignore here if needed + + # PHP version (must be an integer) + phpVersion: 70200 + + # Treat missing return types as mixed + treatPhpDocTypesAsCertain: false diff --git a/src/Config.php b/src/Config.php index 1fa76a3..73cd527 100644 --- a/src/Config.php +++ b/src/Config.php @@ -82,6 +82,7 @@ public static function setHookPrefix(string $prefix) /** * @since 1.0.0 * + * @return never * @throws ValidationExceptionInterface */ public static function throwValidationException() @@ -118,6 +119,7 @@ public static function setValidationExceptionClass(string $validationExceptionCl /** * @since 1.0.0 * + * @return never * @throws InvalidArgumentException */ public static function throwInvalidArgumentException() @@ -162,7 +164,7 @@ public static function initialize() return; } - if (empty(self::$container)) { + if (self::$container === null) { throw new RuntimeException('A service container must be set before initializing the library'); } diff --git a/src/Exceptions/Contracts/ValidationExceptionInterface.php b/src/Exceptions/Contracts/ValidationExceptionInterface.php index 8e07a6f..9b49854 100644 --- a/src/Exceptions/Contracts/ValidationExceptionInterface.php +++ b/src/Exceptions/Contracts/ValidationExceptionInterface.php @@ -4,7 +4,9 @@ namespace StellarWP\Validation\Exceptions\Contracts; -interface ValidationExceptionInterface +use Throwable; + +interface ValidationExceptionInterface extends Throwable { } diff --git a/src/Rules/Abstracts/ConditionalRule.php b/src/Rules/Abstracts/ConditionalRule.php index e17450c..f1b3d17 100644 --- a/src/Rules/Abstracts/ConditionalRule.php +++ b/src/Rules/Abstracts/ConditionalRule.php @@ -58,6 +58,7 @@ public static function fromString(string $options = null): ValidationRule $conditionSet->and($rule[0], '=', $rule[1]); } + // @phpstan-ignore-next-line return new static($conditionSet); } diff --git a/src/Rules/DateTime.php b/src/Rules/DateTime.php index c46d2bb..2a8a735 100644 --- a/src/Rules/DateTime.php +++ b/src/Rules/DateTime.php @@ -35,6 +35,7 @@ public static function id(): string */ public static function fromString(string $options = null): ValidationRule { + // @phpstan-ignore-next-line return new static($options); } diff --git a/src/ValidationRuleSet.php b/src/ValidationRuleSet.php index 49f9923..e06b158 100644 --- a/src/ValidationRuleSet.php +++ b/src/ValidationRuleSet.php @@ -153,7 +153,7 @@ public function getRule(string $rule) * * @since 1.0.0 * - * @return void + * @return self */ public function removeRuleWithId(string $id): self { @@ -258,14 +258,14 @@ private function sanitizeRule($rule) return $rule; } elseif (is_string($rule)) { return $this->getRuleFromString($rule); - } else { - Config::throwInvalidArgumentException( - sprintf( - 'Validation rule must be a string, instance of %s, or a closure', - ValidationRule::class - ) - ); } + + Config::throwInvalidArgumentException( + sprintf( + 'Validation rule must be a string, instance of %s, or a closure', + ValidationRule::class + ) + ); } /** @@ -277,6 +277,7 @@ private function sanitizeRule($rule) */ private function validateClosureRule(Closure $closure) { + $reflection = null; try { $reflection = new ReflectionFunction($closure); } catch (ReflectionException $e) {