diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index bc79fe7c401..04e84c9e3ab 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -135,6 +135,7 @@ parametersSchema: ?paths: listOf(string()) ?count: int() ?reportUnmatched: bool() + ?origin: string() ]), ) ) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index bb51fac85b5..6d90470a736 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -159,13 +159,13 @@ parameters: - rawMessage: 'Call to static method escape() of internal class Nette\DI\Helpers from outside its root namespace Nette.' identifier: staticMethod.internalClass - count: 4 + count: 6 path: src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php - rawMessage: 'Call to static method escape() of internal class Nette\DI\Helpers from outside its root namespace Nette.' identifier: staticMethod.internalClass - count: 5 + count: 12 path: src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php - @@ -758,6 +758,7 @@ parameters: identifier: closure.unusedUse count: 1 path: src/Testing/PHPStanTestCase.php + origin: src/Testing/PHPStanTestCaseTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.' @@ -896,6 +897,7 @@ parameters: identifier: phpstanApi.instanceofType count: 2 path: src/Type/BooleanType.php + origin: src/Type/JustNullableTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantBooleanType is error-prone and deprecated. Use Type::isTrue() or Type::isFalse() instead.' @@ -980,36 +982,42 @@ parameters: identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantBooleanType.php + origin: src/Type/Traits/ConstantScalarTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantBooleanType.php + origin: src/Type/Traits/ConstantScalarTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantBooleanType is error-prone and deprecated. Use Type::isTrue() or Type::isFalse() instead.' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Constant/ConstantBooleanType.php + origin: src/Type/Traits/ConstantScalarTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantFloatType.php + origin: src/Type/Traits/ConstantScalarTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\FloatType is error-prone and deprecated. Use Type::isFloat() instead.' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantFloatType.php + origin: src/Type/Traits/ConstantScalarTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantIntegerType.php + origin: src/Type/Traits/ConstantScalarTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\IntegerType is error-prone and deprecated. Use Type::isInteger() instead.' @@ -1028,6 +1036,7 @@ parameters: identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantStringType.php + origin: src/Type/Traits/ConstantScalarTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' @@ -1154,120 +1163,140 @@ parameters: identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateArrayType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateBenevolentUnionType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateBooleanType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateConstantArrayType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateConstantIntegerType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: 'Method PHPStan\Type\Generic\TemplateConstantIntegerType::toPhpDocNode() should return PHPStan\PhpDocParser\Ast\Type\ConstTypeNode but returns PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode.' identifier: return.type count: 1 path: src/Type/Generic/TemplateConstantIntegerType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateConstantStringType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateFloatType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateGenericObjectType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateIntegerType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateIntersectionType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateIterableType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateKeyOfType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/TemplateMixedType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateNullType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateObjectShapeType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateObjectType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateObjectWithoutClassType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/TemplateStrictMixedType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateStringType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\ArrayType is error-prone and deprecated. Use Type::isArray() or Type::getArrays() instead.' @@ -1358,6 +1387,7 @@ parameters: identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateUnionType.php + origin: src/Type/Generic/TemplateTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.' @@ -1382,6 +1412,7 @@ parameters: identifier: phpstanApi.instanceofType count: 2 path: src/Type/IntegerType.php + origin: src/Type/JustNullableTypeTrait.php - rawMessage: 'Doing instanceof PHPStan\Type\ArrayType is error-prone and deprecated. Use Type::isArray() or Type::getArrays() instead.' diff --git a/src/Analyser/Ignore/IgnoredErrorHelper.php b/src/Analyser/Ignore/IgnoredErrorHelper.php index d3394bcb0bb..c1f3b20f260 100644 --- a/src/Analyser/Ignore/IgnoredErrorHelper.php +++ b/src/Analyser/Ignore/IgnoredErrorHelper.php @@ -97,6 +97,9 @@ public function initialize(): IgnoredErrorHelperResult if (isset($ignoreError['identifier'])) { $key = sprintf("%s\n%s", $key, $ignoreError['identifier']); } + if (isset($ignoreError['origin'])) { + $key = sprintf("%s\n%s", $key, $ignoreError['origin']); + } if ($key === '') { throw new ShouldNotHappenException(); } @@ -115,6 +118,7 @@ public function initialize(): IgnoredErrorHelperResult 'message' => $ignoreError['message'] ?? null, 'rawMessage' => $ignoreError['rawMessage'] ?? null, 'path' => $ignoreError['path'], + 'origin' => $ignoreError['origin'] ?? null, 'identifier' => $ignoreError['identifier'] ?? null, 'count' => ($uniquedExpandedIgnoreErrors[$key]['count'] ?? 1) + ($ignoreError['count'] ?? 1), 'reportUnmatched' => $reportUnmatched, @@ -144,6 +148,11 @@ public function initialize(): IgnoredErrorHelperResult $ignoreError['path'] = $normalizedPath; $ignoreErrorsByFile[$normalizedPath][] = $ignoreErrorEntry; $ignoreError['realPath'] = $normalizedPath; + if (isset($ignoreError['origin']) && @is_file($ignoreError['origin'])) { + $normalizedOrigin = $this->fileHelper->normalizePath($ignoreError['origin']); + $ignoreError['origin'] = $normalizedOrigin; + $ignoreError['realOrigin'] = $normalizedOrigin; + } $expandedIgnoreErrors[$i] = $ignoreError; } else { $otherIgnoreErrors[] = $ignoreErrorEntry; diff --git a/src/Analyser/Ignore/IgnoredErrorHelperResult.php b/src/Analyser/Ignore/IgnoredErrorHelperResult.php index ea4c1295309..e29f693ce58 100644 --- a/src/Analyser/Ignore/IgnoredErrorHelperResult.php +++ b/src/Analyser/Ignore/IgnoredErrorHelperResult.php @@ -219,7 +219,11 @@ public function process( continue; } - if ($onlyFiles) { + if (isset($unmatchedIgnoredError['realOrigin'])) { + if (!array_key_exists($unmatchedIgnoredError['realOrigin'], $analysedFilesKeys)) { + continue; + } + } elseif ($onlyFiles) { continue; } diff --git a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php index b5ea4914f16..fa8f50c05c6 100644 --- a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php +++ b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php @@ -9,6 +9,8 @@ use PHPStan\Command\Output; use PHPStan\File\RelativePathHelper; use PHPStan\ShouldNotHappenException; +use function array_key_first; +use function array_unique; use function count; use function ksort; use function preg_quote; @@ -38,61 +40,80 @@ public function formatErrors( if (!$fileSpecificError->canBeIgnored()) { continue; } - $fileErrors[$this->relativePathHelper->getRelativePath($fileSpecificError->getFilePath())][] = $fileSpecificError; + $traitFilePath = $fileSpecificError->getTraitFilePath(); + $fileErrors[$this->relativePathHelper->getRelativePath($fileSpecificError->getFilePath())][] = [ + 'error' => $fileSpecificError, + 'origin' => $traitFilePath !== null ? $this->relativePathHelper->getRelativePath($traitFilePath) : null, + ]; } ksort($fileErrors, SORT_STRING); $messageKey = $this->useRawMessage ? 'rawMessage' : 'message'; $errorsToOutput = []; - foreach ($fileErrors as $file => $errors) { + foreach ($fileErrors as $file => $fileErrorEntries) { $fileErrorsByMessage = []; - foreach ($errors as $error) { + foreach ($fileErrorEntries as ['error' => $error, 'origin' => $origin]) { $errorMessage = $error->getMessage(); $identifier = $error->getIdentifier(); if (!isset($fileErrorsByMessage[$errorMessage])) { $fileErrorsByMessage[$errorMessage] = [ - 1, - $identifier !== null ? [$identifier => 1] : [], + 'count' => 1, + 'origins' => [$origin], + 'identifiers' => $identifier !== null ? [$identifier => ['count' => 1, 'origins' => [$origin]]] : [], ]; continue; } - $fileErrorsByMessage[$errorMessage][0]++; + $fileErrorsByMessage[$errorMessage]['count']++; + $fileErrorsByMessage[$errorMessage]['origins'][] = $origin; if ($identifier === null) { continue; } - if (!isset($fileErrorsByMessage[$errorMessage][1][$identifier])) { - $fileErrorsByMessage[$errorMessage][1][$identifier] = 1; + if (!isset($fileErrorsByMessage[$errorMessage]['identifiers'][$identifier])) { + $fileErrorsByMessage[$errorMessage]['identifiers'][$identifier] = ['count' => 1, 'origins' => [$origin]]; continue; } - $fileErrorsByMessage[$errorMessage][1][$identifier]++; + $fileErrorsByMessage[$errorMessage]['identifiers'][$identifier]['count']++; + $fileErrorsByMessage[$errorMessage]['identifiers'][$identifier]['origins'][] = $origin; } ksort($fileErrorsByMessage, SORT_STRING); - foreach ($fileErrorsByMessage as $message => [$totalCount, $identifiers]) { + foreach ($fileErrorsByMessage as $message => ['count' => $totalCount, 'origins' => $messageOrigins, 'identifiers' => $identifiers]) { if (!$this->useRawMessage) { $message = '#^' . preg_quote($message, '#') . '$#'; } ksort($identifiers, SORT_STRING); if (count($identifiers) > 0) { - foreach ($identifiers as $identifier => $identifierCount) { - $errorsToOutput[] = [ + foreach ($identifiers as $identifier => ['count' => $identifierCount, 'origins' => $identifierOrigins]) { + $uniqueOrigins = array_unique($identifierOrigins); + $uniformOrigin = count($uniqueOrigins) === 1 && $uniqueOrigins[array_key_first($uniqueOrigins)] !== null ? $uniqueOrigins[array_key_first($uniqueOrigins)] : null; + $entry = [ $messageKey => Helpers::escape($message), 'identifier' => $identifier, 'count' => $identifierCount, 'path' => Helpers::escape($file), ]; + if ($uniformOrigin !== null) { + $entry['origin'] = Helpers::escape($uniformOrigin); + } + $errorsToOutput[] = $entry; } } else { - $errorsToOutput[] = [ + $uniqueOrigins = array_unique($messageOrigins); + $uniformOrigin = count($uniqueOrigins) === 1 && $uniqueOrigins[array_key_first($uniqueOrigins)] !== null ? $uniqueOrigins[array_key_first($uniqueOrigins)] : null; + $entry = [ $messageKey => Helpers::escape($message), 'count' => $totalCount, 'path' => Helpers::escape($file), ]; + if ($uniformOrigin !== null) { + $entry['origin'] = Helpers::escape($uniformOrigin); + } + $errorsToOutput[] = $entry; } } } diff --git a/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php b/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php index ac4c1fc81fa..36301b60c03 100644 --- a/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php +++ b/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php @@ -6,6 +6,8 @@ use PHPStan\Command\AnalysisResult; use PHPStan\Command\Output; use PHPStan\File\RelativePathHelper; +use function array_key_first; +use function array_unique; use function count; use function ksort; use function preg_quote; @@ -39,7 +41,11 @@ public function formatErrors( if (!$fileSpecificError->canBeIgnored()) { continue; } - $fileErrors['/' . $this->relativePathHelper->getRelativePath($fileSpecificError->getFilePath())][] = $fileSpecificError; + $traitFilePath = $fileSpecificError->getTraitFilePath(); + $fileErrors['/' . $this->relativePathHelper->getRelativePath($fileSpecificError->getFilePath())][] = [ + 'error' => $fileSpecificError, + 'origin' => $traitFilePath !== null ? '/' . $this->relativePathHelper->getRelativePath($traitFilePath) : null, + ]; } ksort($fileErrors, SORT_STRING); @@ -47,35 +53,38 @@ public function formatErrors( $php .= "\n\n"; $php .= '$ignoreErrors = [];'; $php .= "\n"; - foreach ($fileErrors as $file => $errors) { + foreach ($fileErrors as $file => $fileErrorEntries) { $fileErrorsByMessage = []; - foreach ($errors as $error) { + foreach ($fileErrorEntries as ['error' => $error, 'origin' => $origin]) { $errorMessage = $error->getMessage(); $identifier = $error->getIdentifier(); if (!isset($fileErrorsByMessage[$errorMessage])) { $fileErrorsByMessage[$errorMessage] = [ - 1, - $identifier !== null ? [$identifier => 1] : [], + 'count' => 1, + 'origins' => [$origin], + 'identifiers' => $identifier !== null ? [$identifier => ['count' => 1, 'origins' => [$origin]]] : [], ]; continue; } - $fileErrorsByMessage[$errorMessage][0]++; + $fileErrorsByMessage[$errorMessage]['count']++; + $fileErrorsByMessage[$errorMessage]['origins'][] = $origin; if ($identifier === null) { continue; } - if (!isset($fileErrorsByMessage[$errorMessage][1][$identifier])) { - $fileErrorsByMessage[$errorMessage][1][$identifier] = 1; + if (!isset($fileErrorsByMessage[$errorMessage]['identifiers'][$identifier])) { + $fileErrorsByMessage[$errorMessage]['identifiers'][$identifier] = ['count' => 1, 'origins' => [$origin]]; continue; } - $fileErrorsByMessage[$errorMessage][1][$identifier]++; + $fileErrorsByMessage[$errorMessage]['identifiers'][$identifier]['count']++; + $fileErrorsByMessage[$errorMessage]['identifiers'][$identifier]['origins'][] = $origin; } ksort($fileErrorsByMessage, SORT_STRING); - foreach ($fileErrorsByMessage as $message => [$totalCount, $identifiers]) { + foreach ($fileErrorsByMessage as $message => ['count' => $totalCount, 'origins' => $messageOrigins, 'identifiers' => $identifiers]) { if ($this->useRawMessage) { $messageKey = 'rawMessage'; } else { @@ -85,24 +94,51 @@ public function formatErrors( ksort($identifiers, SORT_STRING); if (count($identifiers) > 0) { - foreach ($identifiers as $identifier => $identifierCount) { + foreach ($identifiers as $identifier => ['count' => $identifierCount, 'origins' => $identifierOrigins]) { + $uniqueOrigins = array_unique($identifierOrigins); + $uniformOrigin = count($uniqueOrigins) === 1 && $uniqueOrigins[array_key_first($uniqueOrigins)] !== null ? $uniqueOrigins[array_key_first($uniqueOrigins)] : null; + if ($uniformOrigin !== null) { + $php .= sprintf( + "\$ignoreErrors[] = [\n\t%s => %s,\n\t'identifier' => %s,\n\t'count' => %s,\n\t'path' => __DIR__ . %s,\n\t'origin' => __DIR__ . %s,\n];\n", + var_export($messageKey, true), + var_export(Helpers::escape($message), true), + var_export(Helpers::escape($identifier), true), + var_export($identifierCount, true), + var_export(Helpers::escape($file), true), + var_export(Helpers::escape($uniformOrigin), true), + ); + } else { + $php .= sprintf( + "\$ignoreErrors[] = [\n\t%s => %s,\n\t'identifier' => %s,\n\t'count' => %s,\n\t'path' => __DIR__ . %s,\n];\n", + var_export($messageKey, true), + var_export(Helpers::escape($message), true), + var_export(Helpers::escape($identifier), true), + var_export($identifierCount, true), + var_export(Helpers::escape($file), true), + ); + } + } + } else { + $uniqueOrigins = array_unique($messageOrigins); + $uniformOrigin = count($uniqueOrigins) === 1 && $uniqueOrigins[array_key_first($uniqueOrigins)] !== null ? $uniqueOrigins[array_key_first($uniqueOrigins)] : null; + if ($uniformOrigin !== null) { + $php .= sprintf( + "\$ignoreErrors[] = [\n\t%s => %s,\n\t'count' => %s,\n\t'path' => __DIR__ . %s,\n\t'origin' => __DIR__ . %s,\n];\n", + var_export($messageKey, true), + var_export(Helpers::escape($message), true), + var_export($totalCount, true), + var_export(Helpers::escape($file), true), + var_export(Helpers::escape($uniformOrigin), true), + ); + } else { $php .= sprintf( - "\$ignoreErrors[] = [\n\t%s => %s,\n\t'identifier' => %s,\n\t'count' => %s,\n\t'path' => __DIR__ . %s,\n];\n", + "\$ignoreErrors[] = [\n\t%s => %s,\n\t'count' => %s,\n\t'path' => __DIR__ . %s,\n];\n", var_export($messageKey, true), var_export(Helpers::escape($message), true), - var_export(Helpers::escape($identifier), true), - var_export($identifierCount, true), + var_export($totalCount, true), var_export(Helpers::escape($file), true), ); } - } else { - $php .= sprintf( - "\$ignoreErrors[] = [\n\t%s => %s,\n\t'count' => %s,\n\t'path' => __DIR__ . %s,\n];\n", - var_export($messageKey, true), - var_export(Helpers::escape($message), true), - var_export($totalCount, true), - var_export(Helpers::escape($file), true), - ); } } } diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 39da6a5be9e..c5e5bf0c4ad 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -29,8 +29,10 @@ use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\FileTypeMapper; use PHPUnit\Framework\Attributes\DataProvider; +use function array_filter; use function array_map; use function array_merge; +use function array_values; use function assert; use function count; use function is_string; @@ -609,6 +611,47 @@ public function testDoNotReportUnmatchedIgnoredErrorsFromPathWithCountIfPathWasN $this->assertNoErrors($result); } + public function testDoNotReportUnmatchedIgnoredErrorsFromPathWithoutOriginInPartialAnalysis(): void + { + $ignoreErrors = [ + [ + 'message' => '#Unknown error#', + 'path' => __DIR__ . '/data/traits-ignore/Foo.php', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, [ + __DIR__ . '/data/traits-ignore/Foo.php', + ], true); + $this->assertNoErrors($result); + } + + public function testReturnUnmatchedIgnoredErrorFromPathWithOriginWhenOriginIsAnalysed(): void + { + $ignoreErrors = [ + [ + 'message' => '#Unknown error#', + 'path' => __DIR__ . '/data/traits-ignore/Foo.php', + 'origin' => __DIR__ . '/data/traits-ignore/FooTrait.php', + ], + ]; + $result = $this->runAnalyser($ignoreErrors, true, [ + __DIR__ . '/data/traits-ignore/Foo.php', + __DIR__ . '/data/traits-ignore/FooTrait.php', + ], true); + // One result is the real fail() error (not suppressed by #Unknown error#), + // the other is the unmatched ignore rule reported because origin was analysed. + $this->assertCount(2, $result); + $unmatchedErrors = array_values(array_filter( + $result, + static fn ($r) => $r instanceof Error && $r->getIdentifier() === 'ignore.unmatched', + )); + $this->assertCount(1, $unmatchedErrors); + $this->assertSame( + 'Ignored error pattern #Unknown error# in path ' . __DIR__ . '/data/traits-ignore/Foo.php was not matched in reported errors.', + $unmatchedErrors[0]->getMessage(), + ); + } + public function testIgnoreNextLine(): void { $result = $this->runAnalyser([], false, [