Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions composer-packages/phpstan/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 8 additions & 11 deletions composer-packages/phpstan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<p align="center">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-GPL--2.0--or--later-blue.svg" alt="License: GPL-2.0-or-later"></a>
<img src="https://img.shields.io/badge/PHP-8.2%2B-777bb4.svg" alt="PHP 8.2+">
<img src="https://img.shields.io/badge/PHPStan-2.x-blue.svg" alt="PHPStan 2.x">
<img src="https://img.shields.io/badge/PHPStan-2.3%2B-blue.svg" alt="PHPStan 2.3+">
</p>

<p align="center">
Expand All @@ -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.
Expand All @@ -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.
```

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion composer-packages/phpstan/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"require": {
"php": ">=8.2",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan": "^2.3",
"szepeviktor/phpstan-wordpress": "^2.0"
},
"require-dev": {
Expand Down
8 changes: 6 additions & 2 deletions composer-packages/phpstan/phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 35 additions & 3 deletions composer-packages/phpstan/tests/ConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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-');
Expand All @@ -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', "<?php\n"),
'Could not write vendor/autoload.php into the temp consumer project.'
);

foreach (self::LINKED_DEPS as $dep) {
$linked = @symlink($this->realPath(self::PACKAGE_ROOT . '/vendor/' . $dep), $project . '/vendor/' . $dep);
if (! $linked) {
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions composer-packages/phpstan/tests/fixtures/nullable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
// Reads a property off a possibly-null WP_Post. Only level 8 reports it, so this
// proves the baseline's level. Do not "fix" it.

declare( strict_types=1 );

function rtcamp_wp_phpstan_fixture_nullable( int $post_id ): string {
return get_post( $post_id )->post_title;
}
9 changes: 9 additions & 0 deletions composer-packages/phpstan/tests/fixtures/typed-constant.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
// Declares a typed class constant, which needs PHP 8.3. The baseline's 8.2 floor
// must reject it, even when PHPStan itself runs on 8.3 or later. Do not "fix" it.

declare( strict_types=1 );

final class Rtcamp_Wp_Phpstan_Fixture_Typed_Constant {
public const string NAME = 'fixture';
}
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
}
},
"require-dev": {
"phpstan/phpstan": "^2.0",
"phpstan/phpstan": "^2.3",
"phpunit/phpunit": "^12.0",
"squizlabs/php_codesniffer": "^3.10",
"symplify/monorepo-builder": "^12.7"
Expand Down
21 changes: 16 additions & 5 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion monorepo-builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
$mbConfig->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',
],
Expand Down
Loading