diff --git a/src/Analyser.php b/src/Analyser.php index e0c3501..f383931 100644 --- a/src/Analyser.php +++ b/src/Analyser.php @@ -130,8 +130,7 @@ public function run(): AnalysisResult if (!$this->isInClassmap($usedSymbol)) { if ( !$this->isConstOrFunction($usedSymbol) - && !$ignoreList->shouldIgnoreUnknownClass($usedSymbol) - && !$ignoreList->shouldIgnoreError(ErrorType::UNKNOWN_CLASS, $filePath, null) + && !$ignoreList->shouldIgnoreUnknownClass($usedSymbol, $filePath) ) { foreach ($lineNumbers as $lineNumber) { $classmapErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber); diff --git a/src/Config/Ignore/IgnoreList.php b/src/Config/Ignore/IgnoreList.php index 33668df..9bbc5ad 100644 --- a/src/Config/Ignore/IgnoreList.php +++ b/src/Config/Ignore/IgnoreList.php @@ -132,13 +132,28 @@ public function getUnusedIgnores(): array return $unused; } - public function shouldIgnoreUnknownClass(string $class): bool + public function shouldIgnoreUnknownClass(string $class, string $filePath): bool + { + $ignoredGlobally = $this->shouldIgnoreErrorGlobally(ErrorType::UNKNOWN_CLASS); + $ignoredByPath = $this->shouldIgnoreErrorOnPath(ErrorType::UNKNOWN_CLASS, $filePath); + $ignoredByRegex = $this->shouldIgnoreUnknownClassByRegex($class); + $ignoredByBlacklist = $this->shouldIgnoreUnknownClassByBlacklist($class); + + return $ignoredGlobally || $ignoredByPath || $ignoredByRegex || $ignoredByBlacklist; + } + + private function shouldIgnoreUnknownClassByBlacklist(string $class): bool { if (isset($this->ignoredUnknownClasses[$class])) { $this->ignoredUnknownClasses[$class] = true; return true; } + return false; + } + + private function shouldIgnoreUnknownClassByRegex(string $class): bool + { foreach ($this->ignoredUnknownClassesRegexes as $regex => $ignoreUsed) { $matches = preg_match($regex, $class); @@ -156,7 +171,7 @@ public function shouldIgnoreUnknownClass(string $class): bool } /** - * @param ErrorType::* $errorType + * @param ErrorType::SHADOW_DEPENDENCY|ErrorType::UNUSED_DEPENDENCY|ErrorType::DEV_DEPENDENCY_IN_PROD|ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV $errorType */ public function shouldIgnoreError(string $errorType, ?string $realPath, ?string $packageName): bool { diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index b2441dc..b0ad794 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use ShipMonk\ComposerDependencyAnalyser\Config\Configuration; use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType; +use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedClassIgnore; use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore; use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidConfigException; use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidPathException; @@ -17,13 +18,18 @@ class ConfigurationTest extends TestCase public function testShouldIgnore(): void { $configuration = new Configuration(); - $configuration->ignoreErrors([ErrorType::UNUSED_DEPENDENCY]); + $configuration->ignoreUnknownClasses(['Unknown\Clazz']); + $configuration->ignoreErrors([ErrorType::UNUSED_DEPENDENCY, ErrorType::UNKNOWN_CLASS]); $configuration->ignoreErrorsOnPath(__DIR__ . '/app/../', [ErrorType::SHADOW_DEPENDENCY]); $configuration->ignoreErrorsOnPackage('my/package', [ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV]); $configuration->ignoreErrorsOnPackageAndPath('vendor/package', __DIR__ . '/../tests/app', [ErrorType::DEV_DEPENDENCY_IN_PROD]); $ignoreList = $configuration->getIgnoreList(); + self::assertTrue($ignoreList->shouldIgnoreUnknownClass('Unknown\Clazz', __DIR__)); + self::assertTrue($ignoreList->shouldIgnoreUnknownClass('Unknown\Clazz', __DIR__ . '/app')); + self::assertTrue($ignoreList->shouldIgnoreUnknownClass('Any\Clazz', __DIR__)); + self::assertTrue($ignoreList->shouldIgnoreError(ErrorType::UNUSED_DEPENDENCY, null, null)); self::assertTrue($ignoreList->shouldIgnoreError(ErrorType::UNUSED_DEPENDENCY, null, 'some/package')); self::assertTrue($ignoreList->shouldIgnoreError(ErrorType::UNUSED_DEPENDENCY, __DIR__, null)); @@ -87,6 +93,46 @@ public function testOverlappingUnusedIgnores(): void ], $ignoreList3->getUnusedIgnores()); } + public function testOverlappingUnusedIgnoresOfUnknownClass(): void + { + $configuration = new Configuration(); + $configuration->ignoreErrors([ErrorType::UNKNOWN_CLASS]); + $configuration->ignoreErrorsOnPath(__DIR__, [ErrorType::UNKNOWN_CLASS]); + $configuration->ignoreUnknownClasses(['Unknown\Clazz']); + $configuration->ignoreUnknownClassesRegex('~^Unknown~'); + + $ignoreList1 = $configuration->getIgnoreList(); + $ignoreList2 = $configuration->getIgnoreList(); + $ignoreList3 = $configuration->getIgnoreList(); + + $parentDir = realpath(__DIR__ . '/..'); + self::assertNotFalse($parentDir); + + foreach ([$ignoreList1, $ignoreList2, $ignoreList3] as $ignoreList) { + self::assertEquals([ + new UnusedErrorIgnore(ErrorType::UNKNOWN_CLASS, null, null), + new UnusedErrorIgnore(ErrorType::UNKNOWN_CLASS, __DIR__, null), + new UnusedClassIgnore('Unknown\Clazz', false), + new UnusedClassIgnore('~^Unknown~', true), + ], $ignoreList->getUnusedIgnores()); + } + + self::assertTrue($ignoreList1->shouldIgnoreUnknownClass('Unknown\Clazz', __DIR__)); + self::assertEquals([], $ignoreList1->getUnusedIgnores()); + + self::assertTrue($ignoreList2->shouldIgnoreUnknownClass('Unknown\Clazz', $parentDir)); + self::assertEquals([ + new UnusedErrorIgnore(ErrorType::UNKNOWN_CLASS, __DIR__, null), + ], $ignoreList2->getUnusedIgnores()); + + self::assertTrue($ignoreList3->shouldIgnoreUnknownClass('Another\Clazz', $parentDir)); + self::assertEquals([ + new UnusedErrorIgnore(ErrorType::UNKNOWN_CLASS, __DIR__, null), + new UnusedClassIgnore('Unknown\Clazz', false), + new UnusedClassIgnore('~^Unknown~', true), + ], $ignoreList3->getUnusedIgnores()); + } + /** * @param callable(Configuration): void $configure * @dataProvider provideInvalidConfigs