From 7defb25c9e2866a0eb126ef5be6b8c6e5e2937ba Mon Sep 17 00:00:00 2001 From: Gregor Harlan Date: Sat, 19 Jul 2025 00:03:38 +0200 Subject: [PATCH] Introduce `rawMessage` key in `ignoreErrors` --- conf/parametersSchema.neon | 19 +++++ src/Analyser/Ignore/IgnoredError.php | 9 ++ src/Analyser/Ignore/IgnoredErrorHelper.php | 10 ++- .../Ignore/IgnoredErrorHelperResult.php | 8 +- tests/PHPStan/Analyser/AnalyserTest.php | 84 +++++++++++++++++++ 5 files changed, 123 insertions(+), 7 deletions(-) diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 3f60d6383d..57722b81ce 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -143,6 +143,25 @@ parametersSchema: ?identifiers: listOf(string()) ?reportUnmatched: bool() ]) + structure([ + rawMessage: string() + ?identifier: string() + ?path: string() + ?reportUnmatched: bool() + ]), + structure([ + rawMessage: string() + count: int() + path: string() + ?identifier: string() + ?reportUnmatched: bool() + ]), + structure([ + rawMessage: string() + paths: listOf(string()) + ?identifier: string() + ?reportUnmatched: bool() + ]), ) ) internalErrorsCountLimit: int() diff --git a/src/Analyser/Ignore/IgnoredError.php b/src/Analyser/Ignore/IgnoredError.php index 8f44d3cb28..6aae1fba4c 100644 --- a/src/Analyser/Ignore/IgnoredError.php +++ b/src/Analyser/Ignore/IgnoredError.php @@ -30,6 +30,8 @@ public static function stringifyPattern($ignoredError): string $message = ''; if (isset($ignoredError['message'])) { $message = $ignoredError['message']; + } elseif (isset($ignoredError['rawMessage'])) { + $message = '"' . $ignoredError['rawMessage'] . '"'; } if (isset($ignoredError['identifier'])) { if ($message === '') { @@ -71,6 +73,7 @@ public static function shouldIgnore( FileHelper $fileHelper, Error $error, ?string $ignoredErrorPattern, + ?string $ignoredErrorMessage, ?string $identifier, ?string $path, ): bool @@ -91,6 +94,12 @@ public static function shouldIgnore( } } + if ($ignoredErrorMessage !== null) { + if ($error->getMessage() !== $ignoredErrorMessage) { + return false; + } + } + if ($path !== null) { $fileExcluder = new FileExcluder($fileHelper, [$path]); $isExcluded = $fileExcluder->isExcludedFromAnalysing($error->getFilePath()); diff --git a/src/Analyser/Ignore/IgnoredErrorHelper.php b/src/Analyser/Ignore/IgnoredErrorHelper.php index 29fb13d818..19e9428f26 100644 --- a/src/Analyser/Ignore/IgnoredErrorHelper.php +++ b/src/Analyser/Ignore/IgnoredErrorHelper.php @@ -40,7 +40,7 @@ public function initialize(): IgnoredErrorHelperResult $expandedIgnoreErrors = []; foreach ($this->ignoreErrors as $ignoreError) { if (is_array($ignoreError)) { - if (!isset($ignoreError['message']) && !isset($ignoreError['messages']) && !isset($ignoreError['identifier']) && !isset($ignoreError['identifiers'])) { + if (!isset($ignoreError['message']) && !isset($ignoreError['messages']) && !isset($ignoreError['rawMessage']) && !isset($ignoreError['identifier']) && !isset($ignoreError['identifiers'])) { $errors[] = sprintf( 'Ignored error %s is missing a message or an identifier.', Json::encode($ignoreError), @@ -71,7 +71,7 @@ public function initialize(): IgnoredErrorHelperResult $uniquedExpandedIgnoreErrors = []; foreach ($expandedIgnoreErrors as $ignoreError) { - if (!isset($ignoreError['message']) && !isset($ignoreError['identifier'])) { + if (!isset($ignoreError['message']) && !isset($ignoreError['rawMessage']) && !isset($ignoreError['identifier'])) { $uniquedExpandedIgnoreErrors[] = $ignoreError; continue; } @@ -84,6 +84,9 @@ public function initialize(): IgnoredErrorHelperResult if (isset($ignoreError['message'])) { $key = sprintf("%s\n%s", $key, $ignoreError['message']); } + if (isset($ignoreError['rawMessage'])) { + $key = sprintf("%s\n%s", $key, $ignoreError['rawMessage']); + } if (isset($ignoreError['identifier'])) { $key = sprintf("%s\n%s", $key, $ignoreError['identifier']); } @@ -98,6 +101,7 @@ public function initialize(): IgnoredErrorHelperResult $uniquedExpandedIgnoreErrors[$key] = [ 'message' => $ignoreError['message'] ?? null, + 'rawMessage' => $ignoreError['rawMessage'] ?? null, 'path' => $ignoreError['path'], 'identifier' => $ignoreError['identifier'] ?? null, 'count' => ($uniquedExpandedIgnoreErrors[$key]['count'] ?? 1) + ($ignoreError['count'] ?? 1), @@ -114,7 +118,7 @@ public function initialize(): IgnoredErrorHelperResult ]; try { if (is_array($ignoreError)) { - if (!isset($ignoreError['message']) && !isset($ignoreError['identifier'])) { + if (!isset($ignoreError['message']) && !isset($ignoreError['rawMessage']) && !isset($ignoreError['identifier'])) { $errors[] = sprintf( 'Ignored error %s is missing a message or an identifier.', Json::encode($ignoreError), diff --git a/src/Analyser/Ignore/IgnoredErrorHelperResult.php b/src/Analyser/Ignore/IgnoredErrorHelperResult.php index 3358c9e63a..59b2d224ce 100644 --- a/src/Analyser/Ignore/IgnoredErrorHelperResult.php +++ b/src/Analyser/Ignore/IgnoredErrorHelperResult.php @@ -58,13 +58,13 @@ public function process( $processIgnoreError = function (Error $error, int $i, $ignore) use (&$unmatchedIgnoredErrors, &$stringErrors): bool { $shouldBeIgnored = false; if (is_string($ignore)) { - $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore, null, null); + $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore, null, null, null); if ($shouldBeIgnored) { unset($unmatchedIgnoredErrors[$i]); } } else { if (isset($ignore['path'])) { - $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'] ?? null, $ignore['identifier'] ?? null, $ignore['path']); + $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'] ?? null, $ignore['rawMessage'] ?? null, $ignore['identifier'] ?? null, $ignore['path']); if ($shouldBeIgnored) { if (isset($ignore['count'])) { $realCount = $unmatchedIgnoredErrors[$i]['realCount'] ?? 0; @@ -85,7 +85,7 @@ public function process( } } elseif (isset($ignore['paths'])) { foreach ($ignore['paths'] as $j => $ignorePath) { - $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'] ?? null, $ignore['identifier'] ?? null, $ignorePath); + $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'] ?? null, $ignore['rawMessage'] ?? null, $ignore['identifier'] ?? null, $ignorePath); if (!$shouldBeIgnored) { continue; } @@ -102,7 +102,7 @@ public function process( break; } } else { - $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'] ?? null, $ignore['identifier'] ?? null, null); + $shouldBeIgnored = IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore['message'] ?? null, $ignore['rawMessage'] ?? null, $ignore['identifier'] ?? null, null); if ($shouldBeIgnored) { unset($unmatchedIgnoredErrors[$i]); } diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 297d612c8d..dbccb1a383 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -78,6 +78,12 @@ public function testFileWithAnIgnoredErrorMessage(): void $this->assertEmpty($result); } + public function testFileWithAnIgnoredErrorRawMessage(): void + { + $result = $this->runAnalyser([['rawMessage' => 'Fail.']], true, __DIR__ . '/data/bootstrap-error.php', false); + $this->assertEmpty($result); + } + public function testFileWithAnIgnoredErrorMessageAndWrongIdentifier(): void { $result = $this->runAnalyser([['message' => '#Fail\.#', 'identifier' => 'wrong.identifier']], true, __DIR__ . '/data/bootstrap-error.php', false); @@ -88,6 +94,16 @@ public function testFileWithAnIgnoredErrorMessageAndWrongIdentifier(): void $this->assertSame('Ignored error pattern #Fail\.# (wrong.identifier) was not matched in reported errors.', $result[1]); } + public function testFileWithAnIgnoredErrorRawMessageAndWrongIdentifier(): void + { + $result = $this->runAnalyser([['rawMessage' => 'Fail.', 'identifier' => 'wrong.identifier']], true, __DIR__ . '/data/bootstrap-error.php', false); + $this->assertCount(2, $result); + assert($result[0] instanceof Error); + $this->assertSame('Fail.', $result[0]->getMessage()); + assert(is_string($result[1])); + $this->assertSame('Ignored error pattern "Fail." (wrong.identifier) was not matched in reported errors.', $result[1]); + } + public function testFileWithAnIgnoredWrongIdentifier(): void { $result = $this->runAnalyser([['identifier' => 'wrong.identifier']], true, __DIR__ . '/data/bootstrap-error.php', false); @@ -104,6 +120,12 @@ public function testFileWithAnIgnoredErrorMessageAndCorrectIdentifier(): void $this->assertEmpty($result); } + public function testFileWithAnIgnoredErrorRawMessageAndCorrectIdentifier(): void + { + $result = $this->runAnalyser([['rawMessage' => 'Fail.', 'identifier' => 'tests.alwaysFail']], true, __DIR__ . '/data/bootstrap-error.php', false); + $this->assertEmpty($result); + } + public function testFileWithAnIgnoredErrorIdentifier(): void { $result = $this->runAnalyser([['identifier' => 'tests.alwaysFail']], true, __DIR__ . '/data/bootstrap-error.php', false); @@ -215,6 +237,31 @@ public static function dataIgnoreErrorByPathAndCount(): iterable ], ], ]; + + yield [ + [ + [ + 'rawMessage' => 'Fail.', + 'count' => 3, + 'path' => __DIR__ . '/data/two-fails.php', + ], + ], + ]; + + yield [ + [ + [ + 'rawMessage' => 'Fail.', + 'count' => 2, + 'path' => __DIR__ . '/data/two-fails.php', + ], + [ + 'rawMessage' => 'Fail.', + 'count' => 1, + 'path' => __DIR__ . '/data/two-fails.php', + ], + ], + ]; } /** @@ -351,6 +398,18 @@ public function testIgnoreErrorByPaths(): void $this->assertNoErrors($result); } + public function testIgnoreErrorRawByPaths(): void + { + $ignoreErrors = [ + [ + 'rawMessage' => 'Fail.', + 'paths' => [__DIR__ . '/data/bootstrap-error.php'], + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap-error.php', false); + $this->assertNoErrors($result); + } + public function testIgnoreErrorMultiByPaths(): void { $ignoreErrors = [ @@ -606,6 +665,18 @@ public function testIgnoreErrorExplicitReportUnmatchedDisable(): void $this->assertNoErrors($result); } + public function testIgnoreErrorExplicitReportUnmatchedDisableRaw(): void + { + $ignoreErrors = [ + [ + 'rawMessage' => 'Fail.', + 'reportUnmatched' => false, + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap.php', false); + $this->assertNoErrors($result); + } + public function testIgnoreErrorExplicitReportUnmatchedDisableMulti(): void { $ignoreErrors = [ @@ -631,6 +702,19 @@ public function testIgnoreErrorExplicitReportUnmatchedEnable(): void $this->assertSame('Ignored error pattern #Fail# was not matched in reported errors.', $result[0]); } + public function testIgnoreErrorExplicitReportUnmatchedEnableRaw(): void + { + $ignoreErrors = [ + [ + 'rawMessage' => 'Fail.', + 'reportUnmatched' => true, + ], + ]; + $result = $this->runAnalyser($ignoreErrors, false, __DIR__ . '/data/bootstrap.php', false); + $this->assertCount(1, $result); + $this->assertSame('Ignored error pattern "Fail." was not matched in reported errors.', $result[0]); + } + public function testIgnoreErrorExplicitReportUnmatchedEnableMulti(): void { $ignoreErrors = [