diff --git a/README.md b/README.md index be131a5..b5c1c58 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,9 @@ return $config // but you may want to ignore those packages manually to be sure ->enableAnalysisOfUnusedDevDependencies() + // do not report ignores that never matched any error + ->disableReportingUnmatchedIgnores() + // disable detection of dev dependencies in production code globally ->ignoreErrors([ErrorType::DEV_DEPENDENCY_IN_PROD]) diff --git a/bin/composer-dependency-analyser b/bin/composer-dependency-analyser index 7cdc0b8..4de01f1 100755 --- a/bin/composer-dependency-analyser +++ b/bin/composer-dependency-analyser @@ -26,11 +26,11 @@ Options: Ignore options: (or use --config for better granularity) - --ignore-unknown-classes Ignore when class is not found in classmap - --ignore-unused-deps Ignore all unused dependency issues - --ignore-shadow-deps Ignore all shadow dependency issues - --ignore-dev-in-prod-deps Ignore all dev dependency in production code issues - --ignore-prod-only-in-dev-deps Ignore all prod dependency used only in dev paths issues + --ignore-unknown-classes Ignore when class is not found in classmap + --ignore-unused-deps Ignore all unused dependency issues + --ignore-shadow-deps Ignore all shadow dependency issues + --ignore-dev-in-prod-deps Ignore all dev dependency in production code issues + --ignore-prod-only-in-dev-deps Ignore all prod dependency used only in dev paths issues EOD; @@ -197,7 +197,7 @@ $stopwatch = new Stopwatch(); $analyser = new Analyser($stopwatch, $config, $vendorDir, $loaders[$vendorDir]->getClassMap(), $composerJson->dependencies); $result = $analyser->run(); -$exitCode = $printer->printResult($result, isset($providedOptions['verbose'])); +$exitCode = $printer->printResult($result, isset($providedOptions['verbose']), $config->shouldReportUnmatchedIgnoredErrors()); exit($exitCode); diff --git a/src/Analyser.php b/src/Analyser.php index 1d6a749..d3e0d0b 100644 --- a/src/Analyser.php +++ b/src/Analyser.php @@ -101,6 +101,8 @@ public function run(): AnalysisResult $usedPackages = []; $prodPackagesUsedInProdPath = []; + $ignoreList = $this->config->getIgnoreList(); + foreach ($this->config->getPathsToScan() as $scanPath) { foreach ($this->listPhpFilesIn($scanPath->getPath()) as $filePath) { if ($this->config->isExcludedFilepath($filePath)) { @@ -121,8 +123,8 @@ public function run(): AnalysisResult if (!$this->isInClassmap($usedSymbol)) { if ( !$this->isConstOrFunction($usedSymbol) - && !$this->config->shouldIgnoreUnknownClass($usedSymbol) - && !$this->config->shouldIgnoreError(ErrorType::UNKNOWN_CLASS, $filePath, null) + && !$ignoreList->shouldIgnoreUnknownClass($usedSymbol) + && !$ignoreList->shouldIgnoreError(ErrorType::UNKNOWN_CLASS, $filePath, null) ) { foreach ($lineNumbers as $lineNumber) { $classmapErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber); @@ -142,7 +144,7 @@ public function run(): AnalysisResult if ( $this->isShadowDependency($packageName) - && !$this->config->shouldIgnoreError(ErrorType::SHADOW_DEPENDENCY, $filePath, $packageName) + && !$ignoreList->shouldIgnoreError(ErrorType::SHADOW_DEPENDENCY, $filePath, $packageName) ) { foreach ($lineNumbers as $lineNumber) { $shadowErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber); @@ -152,7 +154,7 @@ public function run(): AnalysisResult if ( !$scanPath->isDev() && $this->isDevDependency($packageName) - && !$this->config->shouldIgnoreError(ErrorType::DEV_DEPENDENCY_IN_PROD, $filePath, $packageName) + && !$ignoreList->shouldIgnoreError(ErrorType::DEV_DEPENDENCY_IN_PROD, $filePath, $packageName) ) { foreach ($lineNumbers as $lineNumber) { $devInProdErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber); @@ -203,7 +205,7 @@ public function run(): AnalysisResult ); foreach ($unusedDependencies as $unusedDependency) { - if (!$this->config->shouldIgnoreError(ErrorType::UNUSED_DEPENDENCY, null, $unusedDependency)) { + if (!$ignoreList->shouldIgnoreError(ErrorType::UNUSED_DEPENDENCY, null, $unusedDependency)) { $unusedErrors[] = $unusedDependency; } } @@ -219,7 +221,7 @@ public function run(): AnalysisResult ); foreach ($prodPackagesUsedOnlyInDev as $prodPackageUsedOnlyInDev) { - if (!$this->config->shouldIgnoreError(ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV, null, $prodPackageUsedOnlyInDev)) { + if (!$ignoreList->shouldIgnoreError(ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV, null, $prodPackageUsedOnlyInDev)) { $prodOnlyInDevErrors[] = $prodPackageUsedOnlyInDev; } } @@ -237,7 +239,8 @@ public function run(): AnalysisResult $shadowErrors, $devInProdErrors, $prodOnlyInDevErrors, - $unusedErrors + $unusedErrors, + $ignoreList->getUnusedIgnores() ); } diff --git a/src/Config/Configuration.php b/src/Config/Configuration.php index b8ba677..7cb9bc4 100644 --- a/src/Config/Configuration.php +++ b/src/Config/Configuration.php @@ -3,13 +3,10 @@ namespace ShipMonk\ComposerDependencyAnalyser\Config; use LogicException; -use function array_flip; +use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\IgnoreList; use function array_keys; use function array_merge; -use function get_defined_constants; use function in_array; -use function preg_last_error; -use function preg_match; use function realpath; use function strpos; @@ -26,6 +23,11 @@ class Configuration */ private $reportUnusedDevDependencies = false; + /** + * @var bool + */ + private $reportUnmatchedIgnores = true; + /** * @var list */ @@ -80,6 +82,12 @@ public function disableComposerAutoloadPathScan(): self return $this; } + public function disableReportingUnmatchedIgnores(): self + { + $this->reportUnmatchedIgnores = false; + return $this; + } + /** * @return $this */ @@ -259,7 +267,16 @@ public function ignoreUnknownClassesRegex(string $classNameRegex): self return $this; } - // getters below + public function getIgnoreList(): IgnoreList + { + return new IgnoreList( + $this->ignoredErrors, + $this->ignoredErrorsOnPath, + $this->ignoredErrorsOnPackage, + $this->ignoredUnknownClasses, + $this->ignoredUnknownClassesRegexes + ); + } /** * @return list @@ -311,78 +328,9 @@ public function shouldReportUnusedDevDependencies(): bool return $this->reportUnusedDevDependencies; } - public function shouldIgnoreUnknownClass(string $class): bool - { - if (in_array($class, $this->ignoredUnknownClasses, true)) { - return true; - } - - foreach ($this->ignoredUnknownClassesRegexes as $regex) { - $matches = preg_match($regex, $class); - - if ($matches === false) { - /** @var array $pcreConstants */ - $pcreConstants = get_defined_constants(true)['pcre'] ?? []; - $error = array_flip($pcreConstants)[preg_last_error()] ?? 'unknown error'; - throw new LogicException("Invalid regex: '$regex', error: $error"); - } - - if ($matches === 1) { - return true; - } - } - - return false; - } - - /** - * @param ErrorType::* $errorType - */ - public function shouldIgnoreError(string $errorType, ?string $filePath, ?string $packageName): bool - { - if ($this->shouldIgnoreErrorGlobally($errorType)) { - return true; - } - - if ($filePath !== null && $this->shouldIgnoreErrorOnPath($errorType, $filePath)) { - return true; - } - - if ($packageName !== null && $this->shouldIgnoreErrorOnPackage($errorType, $packageName)) { - return true; - } - - return false; - } - - /** - * @param ErrorType::* $errorType - */ - private function shouldIgnoreErrorGlobally(string $errorType): bool - { - return in_array($errorType, $this->ignoredErrors, true); - } - - /** - * @param ErrorType::* $errorType - */ - private function shouldIgnoreErrorOnPath(string $errorType, string $filePath): bool - { - foreach ($this->ignoredErrorsOnPath as $path => $errorTypes) { - if ($this->isFilepathWithinPath($filePath, $path)) { - return in_array($errorType, $errorTypes, true); - } - } - - return false; - } - - /** - * @param ErrorType::* $errorType - */ - private function shouldIgnoreErrorOnPackage(string $errorType, string $packageName): bool + public function shouldReportUnmatchedIgnoredErrors(): bool { - return in_array($errorType, $this->ignoredErrorsOnPackage[$packageName] ?? [], true); + return $this->reportUnmatchedIgnores; } public function isExcludedFilepath(string $filePath): bool diff --git a/src/Config/Ignore/IgnoreList.php b/src/Config/Ignore/IgnoreList.php new file mode 100644 index 0000000..56dc7f3 --- /dev/null +++ b/src/Config/Ignore/IgnoreList.php @@ -0,0 +1,207 @@ + + */ + private $ignoredErrors; + + /** + * @var array> + */ + private $ignoredErrorsOnPath = []; + + /** + * @var array> + */ + private $ignoredErrorsOnPackage = []; + + /** + * @var array + */ + private $ignoredUnknownClasses; + + /** + * @var array + */ + private $ignoredUnknownClassesRegexes; + + /** + * @param list $ignoredErrors + * @param array> $ignoredErrorsOnPath + * @param array> $ignoredErrorsOnPackage + * @param list $ignoredUnknownClasses + * @param list $ignoredUnknownClassesRegexes + */ + public function __construct( + array $ignoredErrors, + array $ignoredErrorsOnPath, + array $ignoredErrorsOnPackage, + array $ignoredUnknownClasses, + array $ignoredUnknownClassesRegexes + ) + { + $this->ignoredErrors = array_fill_keys($ignoredErrors, false); + + foreach ($ignoredErrorsOnPath as $path => $errorTypes) { + $this->ignoredErrorsOnPath[$path] = array_fill_keys($errorTypes, false); + } + + foreach ($ignoredErrorsOnPackage as $packageName => $errorTypes) { + $this->ignoredErrorsOnPackage[$packageName] = array_fill_keys($errorTypes, false); + } + + $this->ignoredUnknownClasses = array_fill_keys($ignoredUnknownClasses, false); + $this->ignoredUnknownClassesRegexes = array_fill_keys($ignoredUnknownClassesRegexes, false); + } + + /** + * @return list + */ + public function getUnusedIgnores(): array + { + $unused = []; + + foreach ($this->ignoredErrors as $errorType => $ignored) { + if (!$ignored) { + $unused[] = new UnusedErrorIgnore($errorType, null, null); + } + } + + foreach ($this->ignoredErrorsOnPath as $path => $errorTypes) { + foreach ($errorTypes as $errorType => $ignored) { + if (!$ignored) { + $unused[] = new UnusedErrorIgnore($errorType, $path, null); + } + } + } + + foreach ($this->ignoredErrorsOnPackage as $packageName => $errorTypes) { + foreach ($errorTypes as $errorType => $ignored) { + if (!$ignored) { + $unused[] = new UnusedErrorIgnore($errorType, null, $packageName); + } + } + } + + foreach ($this->ignoredUnknownClasses as $class => $ignored) { + if (!$ignored) { + $unused[] = new UnusedClassIgnore($class, false); + } + } + + foreach ($this->ignoredUnknownClassesRegexes as $regex => $ignored) { + if (!$ignored) { + $unused[] = new UnusedClassIgnore($regex, true); + } + } + + return $unused; + } + + public function shouldIgnoreUnknownClass(string $class): bool + { + if (isset($this->ignoredUnknownClasses[$class])) { + $this->ignoredUnknownClasses[$class] = true; + return true; + } + + foreach ($this->ignoredUnknownClassesRegexes as $regex => $ignoreUsed) { + $matches = preg_match($regex, $class); + + if ($matches === false) { + /** @var array $pcreConstants */ + $pcreConstants = get_defined_constants(true)['pcre'] ?? []; + $error = array_flip($pcreConstants)[preg_last_error()] ?? 'unknown error'; + throw new LogicException("Invalid regex: '$regex', error: $error"); + } + + if ($matches === 1) { + $this->ignoredUnknownClassesRegexes[$regex] = true; + return true; + } + } + + return false; + } + + /** + * @param ErrorType::* $errorType + */ + public function shouldIgnoreError(string $errorType, ?string $filePath, ?string $packageName): bool + { + if ($this->shouldIgnoreErrorGlobally($errorType)) { + return true; + } + + if ($filePath !== null && $this->shouldIgnoreErrorOnPath($errorType, $filePath)) { + return true; + } + + if ($packageName !== null && $this->shouldIgnoreErrorOnPackage($errorType, $packageName)) { + return true; + } + + return false; + } + + /** + * @param ErrorType::* $errorType + */ + private function shouldIgnoreErrorGlobally(string $errorType): bool + { + if (isset($this->ignoredErrors[$errorType])) { + $this->ignoredErrors[$errorType] = true; + return true; + } + + return false; + } + + /** + * @param ErrorType::* $errorType + */ + private function shouldIgnoreErrorOnPath(string $errorType, string $filePath): bool + { + foreach ($this->ignoredErrorsOnPath as $path => $errorTypes) { + if ($this->isFilepathWithinPath($filePath, $path) && isset($errorTypes[$errorType])) { + $this->ignoredErrorsOnPath[$path][$errorType] = true; + return true; + } + } + + return false; + } + + /** + * @param ErrorType::* $errorType + */ + private function shouldIgnoreErrorOnPackage(string $errorType, string $packageName): bool + { + if (isset($this->ignoredErrorsOnPackage[$packageName][$errorType])) { + $this->ignoredErrorsOnPackage[$packageName][$errorType] = true; + return true; + } + + return false; + } + + private function isFilepathWithinPath(string $filePath, string $path): bool + { + return strpos($filePath, $path) === 0; + } + +} diff --git a/src/Config/Ignore/UnusedClassIgnore.php b/src/Config/Ignore/UnusedClassIgnore.php new file mode 100644 index 0000000..89e755b --- /dev/null +++ b/src/Config/Ignore/UnusedClassIgnore.php @@ -0,0 +1,34 @@ +unknownClass = $unknownClass; + $this->isRegex = $isRegex; + } + + public function getUnknownClass(): string + { + return $this->unknownClass; + } + + public function isRegex(): bool + { + return $this->isRegex; + } + +} diff --git a/src/Config/Ignore/UnusedErrorIgnore.php b/src/Config/Ignore/UnusedErrorIgnore.php new file mode 100644 index 0000000..a63305a --- /dev/null +++ b/src/Config/Ignore/UnusedErrorIgnore.php @@ -0,0 +1,55 @@ +errorType = $errorType; + $this->filePath = $filePath; + $this->package = $package; + } + + public function getErrorType(): string + { + return $this->errorType; + } + + public function getPath(): ?string + { + return $this->filePath; + } + + public function getPackage(): ?string + { + return $this->package; + } + +} diff --git a/src/Printer.php b/src/Printer.php index 09890dc..807d973 100644 --- a/src/Printer.php +++ b/src/Printer.php @@ -2,6 +2,8 @@ namespace ShipMonk\ComposerDependencyAnalyser; +use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedClassIgnore; +use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore; use ShipMonk\ComposerDependencyAnalyser\Result\AnalysisResult; use ShipMonk\ComposerDependencyAnalyser\Result\SymbolUsage; use function array_fill_keys; @@ -42,15 +44,14 @@ public function __construct(string $cwd) $this->cwd = $cwd; } - public function printResult(AnalysisResult $result, bool $verbose): int + public function printResult( + AnalysisResult $result, + bool $verbose, + bool $reportUnmatchedIgnores + ): int { - if ($result->hasNoErrors()) { - $elapsed = round($result->getElapsedTime(), 3); - $this->printLine(''); - $this->printLine('No composer issues found'); - $this->printLine("(scanned {$result->getScannedFilesCount()} files in {$elapsed} s)" . PHP_EOL); - return 0; - } + $hasError = false; + $unusedIgnores = $result->getUnusedIgnores(); $classmapErrors = $result->getClassmapErrors(); $shadowDependencyErrors = $result->getShadowDependencyErrors(); @@ -59,6 +60,7 @@ public function printResult(AnalysisResult $result, bool $verbose): int $unusedDependencyErrors = $result->getUnusedDependencyErrors(); if (count($classmapErrors) > 0) { + $hasError = true; $this->printClassBasedErrors( 'Unknown classes!', 'those are not present in composer classmap, so we cannot check them', @@ -68,6 +70,7 @@ public function printResult(AnalysisResult $result, bool $verbose): int } if (count($shadowDependencyErrors) > 0) { + $hasError = true; $this->printPackageBasedErrors( 'Found shadow dependencies!', 'those are used, but not listed as dependency in composer.json', @@ -77,6 +80,7 @@ public function printResult(AnalysisResult $result, bool $verbose): int } if (count($devDependencyInProductionErrors) > 0) { + $hasError = true; $this->printPackageBasedErrors( 'Found dev dependencies in production code!', 'those should probably be moved to "require" section in composer.json', @@ -86,6 +90,7 @@ public function printResult(AnalysisResult $result, bool $verbose): int } if (count($prodDependencyOnlyInDevErrors) > 0) { + $hasError = true; $this->printPackageBasedErrors( 'Found prod dependencies used only in dev paths!', 'those should probably be moved to "require-dev" section in composer.json', @@ -95,6 +100,7 @@ public function printResult(AnalysisResult $result, bool $verbose): int } if (count($unusedDependencyErrors) > 0) { + $hasError = true; $this->printPackageBasedErrors( 'Found unused dependencies!', 'those are listed in composer.json, but no usage was found in scanned paths', @@ -103,7 +109,23 @@ public function printResult(AnalysisResult $result, bool $verbose): int ); } - return 255; + if ($unusedIgnores !== [] && $reportUnmatchedIgnores) { + $hasError = true; + $this->printLine(''); + $this->printLine('Some ignored issues never occurred:'); + $this->printUnusedIgnores($unusedIgnores); + } + + if ($hasError) { + return 255; + } + + $elapsed = round($result->getElapsedTime(), 3); + $this->printLine(''); + $this->printLine('No composer issues found'); + $this->printLine("(scanned {$result->getScannedFilesCount()} files in {$elapsed} s)" . PHP_EOL); + + return 0; } /** @@ -218,15 +240,16 @@ public function printLine(string $string): void private function relativizeUsage(SymbolUsage $usage): string { - $filePath = $usage->getFilepath(); + return "{$this->relativizePath($usage->getFilepath())}:{$usage->getLineNumber()}"; + } - if (strpos($filePath, $this->cwd) === 0) { - $relativeFilePath = substr($filePath, strlen($this->cwd) + 1); - } else { - $relativeFilePath = $filePath; + private function relativizePath(string $path): string + { + if (strpos($path, $this->cwd) === 0) { + return (string) substr($path, strlen($this->cwd) + 1); } - return "{$relativeFilePath}:{$usage->getLineNumber()}"; + return $path; } private function colorize(string $string): string @@ -234,4 +257,44 @@ private function colorize(string $string): string return str_replace(array_keys(self::COLORS), array_values(self::COLORS), $string); } + /** + * @param list $unusedIgnores + */ + private function printUnusedIgnores(array $unusedIgnores): void + { + foreach ($unusedIgnores as $unusedIgnore) { + if ($unusedIgnore instanceof UnusedClassIgnore) { + $this->printClassBasedUnusedIgnore($unusedIgnore); + } else { + $this->printErrorBasedUnusedIgnore($unusedIgnore); + } + } + + $this->printLine(''); + } + + private function printClassBasedUnusedIgnore(UnusedClassIgnore $unusedIgnore): void + { + $regex = $unusedIgnore->isRegex() ? ' regex' : ''; + $this->printLine(" • Unknown class{$regex} '{$unusedIgnore->getUnknownClass()}' was ignored, but it was never applied."); + } + + private function printErrorBasedUnusedIgnore(UnusedErrorIgnore $unusedIgnore): void + { + $package = $unusedIgnore->getPackage(); + $path = $unusedIgnore->getPath(); + + if ($package === null && $path === null) { + $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was globally ignored, but it was never applied."); + } + + if ($package !== null && $path === null) { + $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was ignored for package '{$package}', but it was never applied."); + } + + if ($package === null && $path !== null) { + $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was ignored for path '{$this->relativizePath($path)}', but it was never applied."); + } + } + } diff --git a/src/Result/AnalysisResult.php b/src/Result/AnalysisResult.php index 1cffa96..730cf58 100644 --- a/src/Result/AnalysisResult.php +++ b/src/Result/AnalysisResult.php @@ -2,6 +2,9 @@ namespace ShipMonk\ComposerDependencyAnalyser\Result; +use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedClassIgnore; +use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore; + class AnalysisResult { @@ -40,12 +43,18 @@ class AnalysisResult */ private $unusedDependencyErrors; + /** + * @var list + */ + private $unusedIgnores; + /** * @param array> $classmapErrors package => [ usage[] ] * @param array>> $shadowDependencyErrors package => [ classname => usage[] ] * @param array>> $devDependencyInProductionErrors package => [ classname => usage[] ] * @param list $prodDependencyOnlyInDevErrors package[] * @param list $unusedDependencyErrors package[] + * @param list $unusedIgnores */ public function __construct( int $scannedFilesCount, @@ -54,7 +63,8 @@ public function __construct( array $shadowDependencyErrors, array $devDependencyInProductionErrors, array $prodDependencyOnlyInDevErrors, - array $unusedDependencyErrors + array $unusedDependencyErrors, + array $unusedIgnores ) { $this->scannedFilesCount = $scannedFilesCount; @@ -64,6 +74,7 @@ public function __construct( $this->devDependencyInProductionErrors = $devDependencyInProductionErrors; $this->prodDependencyOnlyInDevErrors = $prodDependencyOnlyInDevErrors; $this->unusedDependencyErrors = $unusedDependencyErrors; + $this->unusedIgnores = $unusedIgnores; } public function getScannedFilesCount(): int @@ -116,6 +127,14 @@ public function getUnusedDependencyErrors(): array return $this->unusedDependencyErrors; } + /** + * @return list + */ + public function getUnusedIgnores(): array + { + return $this->unusedIgnores; + } + public function hasNoErrors(): bool { return $this->unusedDependencyErrors === [] diff --git a/tests/AnalyserTest.php b/tests/AnalyserTest.php index 1e6eda0..2135d1b 100644 --- a/tests/AnalyserTest.php +++ b/tests/AnalyserTest.php @@ -6,6 +6,8 @@ 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\Result\AnalysisResult; use ShipMonk\ComposerDependencyAnalyser\Result\SymbolUsage; use function array_filter; @@ -249,6 +251,9 @@ static function (Configuration $config) use ($variousUsagesPath, $unknownClasses }, $this->createAnalysisResult(2, [ ErrorType::UNUSED_DEPENDENCY => ['regular/dead'], + ], [ + new UnusedErrorIgnore(ErrorType::SHADOW_DEPENDENCY, $unknownClassesPath, null), + new UnusedErrorIgnore(ErrorType::DEV_DEPENDENCY_IN_PROD, $unknownClassesPath, null), ]) ]; @@ -361,8 +366,9 @@ static function (Configuration $config) use ($unknownClassesPath): void { /** * @param array> $args + * @param list $unusedIgnores */ - private function createAnalysisResult(int $scannedFiles, array $args): AnalysisResult + private function createAnalysisResult(int $scannedFiles, array $args, array $unusedIgnores = []): AnalysisResult { return new AnalysisResult( $scannedFiles, @@ -371,7 +377,8 @@ private function createAnalysisResult(int $scannedFiles, array $args): AnalysisR array_filter($args[ErrorType::SHADOW_DEPENDENCY] ?? []), // @phpstan-ignore-line ignore mixed array_filter($args[ErrorType::DEV_DEPENDENCY_IN_PROD] ?? []), // @phpstan-ignore-line ignore mixed array_filter($args[ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV] ?? []), // @phpstan-ignore-line ignore mixed - array_filter($args[ErrorType::UNUSED_DEPENDENCY] ?? []) // @phpstan-ignore-line ignore mixed + array_filter($args[ErrorType::UNUSED_DEPENDENCY] ?? []), // @phpstan-ignore-line ignore mixed + $unusedIgnores ); } diff --git a/tests/PrinterTest.php b/tests/PrinterTest.php index e42c424..19a748a 100644 --- a/tests/PrinterTest.php +++ b/tests/PrinterTest.php @@ -4,6 +4,8 @@ use Closure; use PHPUnit\Framework\TestCase; +use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType; +use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore; use ShipMonk\ComposerDependencyAnalyser\Result\AnalysisResult; use ShipMonk\ComposerDependencyAnalyser\Result\SymbolUsage; use function ob_get_clean; @@ -28,10 +30,14 @@ public function testPrintLine(): void public function testPrintResult(): void { + // editorconfig-checker-disable $printer = new Printer('/app'); $noIssuesOutput = $this->captureAndNormalizeOutput(static function () use ($printer): void { - $printer->printResult(new AnalysisResult(2, 0.123, [], [], [], [], []), false); + $printer->printResult(new AnalysisResult(2, 0.123, [], [], [], [], [], []), false, true); + }); + $noIssuesButUnusedIgnores = $this->captureAndNormalizeOutput(static function () use ($printer): void { + $printer->printResult(new AnalysisResult(2, 0.123, [], [], [], [], [], [new UnusedErrorIgnore(ErrorType::SHADOW_DEPENDENCY, null, null)]), false, true); }); $expectedNoIssuesOutput = <<<'OUT' @@ -40,9 +46,18 @@ public function testPrintResult(): void (scanned 2 files in 0.123 s) +OUT; + + $expectedNoIssuesButWarningsOutput = <<<'OUT' + +Some ignored issues never occurred: + • Error 'shadow-dependency' was globally ignored, but it was never applied. + + OUT; self::assertSame($this->normalizeEol($expectedNoIssuesOutput), $this->removeColors($noIssuesOutput)); + self::assertSame($this->normalizeEol($expectedNoIssuesButWarningsOutput), $this->removeColors($noIssuesButUnusedIgnores)); $analysisResult = new AnalysisResult( 10, @@ -66,17 +81,17 @@ public function testPrintResult(): void ], ['some/package' => ['Another\Command' => [new SymbolUsage('/app/src/ProductGenerator.php', 28)]]], ['misplaced/package'], - ['dead/package'] + ['dead/package'], + [] ); $regularOutput = $this->captureAndNormalizeOutput(static function () use ($printer, $analysisResult): void { - $printer->printResult($analysisResult, false); + $printer->printResult($analysisResult, false, true); }); $verboseOutput = $this->captureAndNormalizeOutput(static function () use ($printer, $analysisResult): void { - $printer->printResult($analysisResult, true); + $printer->printResult($analysisResult, true, true); }); - // editorconfig-checker-disable $expectedRegularOutput = <<<'OUT' Unknown classes! @@ -169,9 +184,10 @@ public function testPrintResult(): void OUT; - // editorconfig-checker-enable + self::assertSame($this->normalizeEol($expectedRegularOutput), $this->removeColors($regularOutput)); self::assertSame($this->normalizeEol($expectedVerboseOutput), $this->removeColors($verboseOutput)); + // editorconfig-checker-enable } private function removeColors(string $output): string