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;