From 2f3508bc8d8d5a0650d2ad0b722a694d5e175882 Mon Sep 17 00:00:00 2001 From: Jake Hotson Date: Fri, 28 Aug 2026 07:08:11 +0100 Subject: [PATCH] [TASK] Make PHPStan extension compatible with PHPStan 2.2.8 This mirrors https://github.com/MyIntervals/emogrifier/pull/1646 and https://github.com/MyIntervals/emogrifier/pull/1647, though this time done as a single commit. As noted there, the extension API is unstable, and if it keeps being changed, it may be preferable to clutter the code with `phpstan-ignore` comments instead. We will see... --- Build/phpstan/phpstan.neon | 5 +++ src/PhpStan/IgnoreBooleanAlways.php | 58 +++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Build/phpstan/phpstan.neon b/Build/phpstan/phpstan.neon index 0556cc92..83e14fb4 100644 --- a/Build/phpstan/phpstan.neon +++ b/Build/phpstan/phpstan.neon @@ -29,6 +29,11 @@ parameters: message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) .* will always evaluate to#' path: '%currentWorkingDirectory%/tests/' + # This is a temporary but sadly necessary setting to allow upgrade to PHPStan 2.2.8. + # It maintains compatibility with PHPStan 2.2.7. + # When it has been bedded in, it can (and should) be removed. + reportUnmatchedIgnoredErrors: false + services: - class: \Sabberworm\CSS\PhpStan\IgnoreBooleanAlways diff --git a/src/PhpStan/IgnoreBooleanAlways.php b/src/PhpStan/IgnoreBooleanAlways.php index dbcd499f..fb3a8503 100644 --- a/src/PhpStan/IgnoreBooleanAlways.php +++ b/src/PhpStan/IgnoreBooleanAlways.php @@ -10,6 +10,7 @@ use PHPStan\Analyser\Error; use PHPStan\Analyser\IgnoreErrorExtension; use PHPStan\Analyser\Scope; +use PHPStan\Node\FunctionCallExpressionNode; /** * Ignore PHPStan warnings where the DocBlocks indicate that a conditional expression would always be true (or false), @@ -22,25 +23,50 @@ final class IgnoreBooleanAlways implements IgnoreErrorExtension { public function shouldIgnore(Error $error, Node $node, Scope $scope): bool { - $shouldIgnore = false; - switch ($error->getIdentifier()) { case 'function.alreadyNarrowedType': - // For an `assert()` that the DocBlocks say cannot fail. - if ($node instanceof FuncCall) { - $nameNode = $node->name; - if ($nameNode instanceof Name && $nameNode->name === 'assert') { - $shouldIgnore = true; - } - } - break; + return self::shouldIgnoreFunctionAlreadyNarrowedType($node); case 'instanceof.alwaysTrue': - // For `instanceof` within an `assert()` that the DocBlocks say cannot fail. - $functionCallStack = $scope->getFunctionCallStack(); - if (isset($functionCallStack[0]) && $functionCallStack[0]->getName() === 'assert') { - $shouldIgnore = true; - } - break; + return self::shouldIgnoreInstanceofAlwaysTrue($scope); + default: + return false; + } + } + + /** + * For an `assert()` that the DocBlocks say cannot fail. + */ + private static function shouldIgnoreFunctionAlreadyNarrowedType(Node $node): bool + { + $shouldIgnore = false; + + // This is an unstable API that does not adhere to semver. + // @phpstan-ignore phpstanApi.classConstant, phpstanApi.class + if (\class_exists(FunctionCallExpressionNode::class) && $node instanceof FunctionCallExpressionNode) { + // Unwrap for PHPStan >= 2.2.8 + // @phpstan-ignore phpstanApi.method + $node = $node->getOriginalNode(); + } + if ($node instanceof FuncCall) { + $nameNode = $node->name; + if ($nameNode instanceof Name && $nameNode->name === 'assert') { + $shouldIgnore = true; + } + } + + return $shouldIgnore; + } + + /** + * For `instanceof` within an `assert()` that the DocBlocks say cannot fail. + */ + private static function shouldIgnoreInstanceofAlwaysTrue(Scope $scope): bool + { + $shouldIgnore = false; + + $functionCallStack = $scope->getFunctionCallStack(); + if (isset($functionCallStack[0]) && $functionCallStack[0]->getName() === 'assert') { + $shouldIgnore = true; } return $shouldIgnore;