diff --git a/composer-packages/phpstan/CHANGELOG.md b/composer-packages/phpstan/CHANGELOG.md index 63a6019..7e0fad2 100644 --- a/composer-packages/phpstan/CHANGELOG.md +++ b/composer-packages/phpstan/CHANGELOG.md @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Added + +- `phpVersion` range `80200`–`80599`, so code is analysed against every PHP version from the 8.2 floor up to 8.5. + +### Changed + +- **Breaking:** the baseline runs at level 8 (was 5). Projects now get missing-type errors for untyped parameters, returns and properties and for arrays with no value type (level 6), partially wrong union types (level 7), and nullable access (level 8). To migrate, record existing errors with `phpstan analyse --generate-baseline`, or set `level` lower in your own neon. +- `phpstan/phpstan` minimum raised to `^2.3`. 2.3 rewrote the analysis engine and bundles the optional Turbo extension, which loads automatically on PHP 8.3+: per its release notes, analysing WordPress core went from 60 s on 2.1.33 to under 8 s. 2.3 also reports some errors older releases missed, mostly `function.resultUnused`. + ## [1.0.1] - 2026-10-01 ### Added diff --git a/composer-packages/phpstan/README.md b/composer-packages/phpstan/README.md index d19f6fe..a03a068 100644 --- a/composer-packages/phpstan/README.md +++ b/composer-packages/phpstan/README.md @@ -3,7 +3,7 @@
@@ -16,7 +16,7 @@ > The [`rtCamp/wp-phpstan`](https://github.com/rtCamp/wp-phpstan) repository is a read-only mirror, split from > there on each release. Please open issues and pull requests in wp-tooling. -This package ships a single `phpstan.neon.dist` of rtCamp defaults (level 5, WordPress-aware +This package ships a single `phpstan.neon.dist` of rtCamp defaults (level 8, PHP 8.2–8.5, WordPress-aware rules) and pulls in [`szepeviktor/phpstan-wordpress`](https://github.com/szepeviktor/phpstan-wordpress), which loads the WordPress function stubs and dynamic return-type extensions PHPStan needs to understand core. @@ -40,14 +40,11 @@ includes: - vendor/rtcamp/wp-phpstan/phpstan.neon.dist parameters: + # Every file and directory holding your own PHP. Dependencies resolve + # through Composer's autoloader, so vendor/ needs no entry. paths: - - src - - includes - excludePaths: - - vendor - - node_modules - - build - - tests + - my-plugin.php + - inc/ # Project-specific overrides and suppressions go here. ``` @@ -72,8 +69,8 @@ vendor/bin/phpstan analyse ## Overriding -Anything you set under `parameters` *after* the includes wins, so a project can -raise the level, add `paths`, or add `ignoreErrors` without forking this baseline. +Anything you set under `parameters` in your own neon overrides this baseline, so a +project can change the level, add `paths`, or add `ignoreErrors` without forking it. Per-project suppressions belong in your own neon — keep this shared file clean. ## License diff --git a/composer-packages/phpstan/composer.json b/composer-packages/phpstan/composer.json index b6851ca..d2dc381 100644 --- a/composer-packages/phpstan/composer.json +++ b/composer-packages/phpstan/composer.json @@ -22,7 +22,7 @@ }, "require": { "php": ">=8.2", - "phpstan/phpstan": "^2.0", + "phpstan/phpstan": "^2.3", "szepeviktor/phpstan-wordpress": "^2.0" }, "require-dev": { diff --git a/composer-packages/phpstan/phpstan.neon.dist b/composer-packages/phpstan/phpstan.neon.dist index 332db52..24e8ae9 100644 --- a/composer-packages/phpstan/phpstan.neon.dist +++ b/composer-packages/phpstan/phpstan.neon.dist @@ -7,14 +7,18 @@ includes: - ../../szepeviktor/phpstan-wordpress/extension.neon parameters: - level: 5 + level: 8 + # Analyse against the whole supported PHP range: the 8.2 floor up to 8.5. + phpVersion: + min: 80200 + max: 80599 # WordPress doc-blocks are unreliable; don't treat their types as certain. treatPhpDocTypesAsCertain: false reportUnmatchedIgnoredErrors: true # Wrong-case function/method calls and provably wrong @var tags. checkFunctionNameCase: true reportWrongPhpDocTypeInVarTag: true - # Low-noise correctness checks — stricter than bare level 5, not a level bump. + # Low-noise correctness checks on top of what the level enables. inferPrivatePropertyTypeFromConstructor: true checkInternalClassCaseSensitivity: true checkExplicitMixedMissingReturn: true diff --git a/composer-packages/phpstan/tests/ConfigTest.php b/composer-packages/phpstan/tests/ConfigTest.php index c29d27b..73c3d5a 100644 --- a/composer-packages/phpstan/tests/ConfigTest.php +++ b/composer-packages/phpstan/tests/ConfigTest.php @@ -61,6 +61,22 @@ public function test_baseline_catches_a_type_error(): void self::assertStringContainsString('should return int but returns string', $output); } + public function test_baseline_runs_at_level_8(): void + { + [$exitCode, $output] = $this->analyse(__DIR__ . '/fixtures/nullable.php'); + + self::assertNotSame(0, $exitCode, "Expected the nullable fixture to fail analysis. PHPStan output:\n" . $output); + self::assertStringContainsString('Cannot access property $post_title on WP_Post|null', $output); + } + + public function test_baseline_analyses_for_the_php_8_2_floor(): void + { + [$exitCode, $output] = $this->analyse(__DIR__ . '/fixtures/typed-constant.php'); + + self::assertNotSame(0, $exitCode, "Expected the typed-constant fixture to fail analysis. PHPStan output:\n" . $output); + self::assertStringContainsString('Class constants with native types are supported only on PHP 8.3 and later', $output); + } + private function makeConsumerProject(): string { $project = tempnam(sys_get_temp_dir(), 'wp-phpstan-consumer-'); @@ -79,6 +95,20 @@ private function makeConsumerProject(): string 'Could not copy phpstan.neon.dist into the temp consumer project.' ); + // Without a phpVersion, PHPStan infers one from require.php in the + // composer.json of its working directory, but only when a + // vendor/autoload.php sits beside it; otherwise it reads the package's + // own composer.json (>=8.2). Both files below are needed so the baseline + // is the only source of the PHP range. + self::assertNotFalse( + file_put_contents($project . '/composer.json', "{}\n"), + 'Could not write composer.json into the temp consumer project.' + ); + self::assertNotFalse( + file_put_contents($project . '/vendor/autoload.php', "realPath(self::PACKAGE_ROOT . '/vendor/' . $dep), $project . '/vendor/' . $dep); if (! $linked) { @@ -128,10 +158,12 @@ private function analyse(string $targetFile): array ) ); - // WordPress stubs are large; raise the limit above PHP's 128M default so - // analysis completes instead of crashing the worker. + // Run from the project root, as a consumer does. WordPress stubs are + // large; raise the limit above PHP's 128M default so analysis completes + // instead of crashing the worker. $command = sprintf( - '%s %s analyse --no-progress --no-ansi --error-format=raw --memory-limit=1G --configuration=%s 2>&1', + 'cd %s && %s %s analyse --no-progress --no-ansi --error-format=raw --memory-limit=1G --configuration=%s 2>&1', + escapeshellarg($this->project), escapeshellarg(PHP_BINARY), escapeshellarg($this->phpstanBinary()), escapeshellarg($neon) diff --git a/composer-packages/phpstan/tests/fixtures/nullable.php b/composer-packages/phpstan/tests/fixtures/nullable.php new file mode 100644 index 0000000..ace4d8d --- /dev/null +++ b/composer-packages/phpstan/tests/fixtures/nullable.php @@ -0,0 +1,9 @@ +post_title; +} diff --git a/composer-packages/phpstan/tests/fixtures/typed-constant.php b/composer-packages/phpstan/tests/fixtures/typed-constant.php new file mode 100644 index 0000000..10d2533 --- /dev/null +++ b/composer-packages/phpstan/tests/fixtures/typed-constant.php @@ -0,0 +1,9 @@ +dataToAppend([ 'require-dev' => [ 'phpunit/phpunit' => '^12.0', - 'phpstan/phpstan' => '^2.0', + 'phpstan/phpstan' => '^2.3', 'szepeviktor/phpstan-wordpress' => '^2.0', 'squizlabs/php_codesniffer' => '^3.10', ],