From a51b304d3a19dee2bcaefe826c687bb56360e1e5 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 20 Jun 2026 18:33:37 +0200 Subject: [PATCH] [dx] fix reporting of full unused path --- src/Application/ApplicationFileProcessor.php | 4 ++++ src/Reporting/UnusedSkipResolver.php | 6 ++++-- src/ValueObject/ProcessResult.php | 16 +++++++++++++++- tests/Reporting/UnusedSkipResolverTest.php | 15 +++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/Application/ApplicationFileProcessor.php b/src/Application/ApplicationFileProcessor.php index 192f0ff92e8..6cfd9b99bb6 100644 --- a/src/Application/ApplicationFileProcessor.php +++ b/src/Application/ApplicationFileProcessor.php @@ -101,6 +101,10 @@ public function run(Configuration $configuration, InputInterface $input): Proces $processResult->addSystemErrors($this->systemErrors); + // path-only skips are matched in the main process while finding files; in parallel runs the + // result comes from workers only, so merge those marks back in to avoid false "unused skip" + $processResult->addUsedSkips($this->usedSkipCollector->provide()); + $this->restoreErrorHandler(); return $processResult; diff --git a/src/Reporting/UnusedSkipResolver.php b/src/Reporting/UnusedSkipResolver.php index 71300b60ea3..6c66f9b5519 100644 --- a/src/Reporting/UnusedSkipResolver.php +++ b/src/Reporting/UnusedSkipResolver.php @@ -6,6 +6,7 @@ use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; +use Rector\FileSystem\FilePathHelper; use Rector\Skipper\SkipCriteriaResolver\SkippedClassResolver; use Rector\Skipper\SkipCriteriaResolver\SkippedPathsResolver; use Rector\ValueObject\ProcessResult; @@ -18,6 +19,7 @@ public function __construct( private SkippedClassResolver $skippedClassResolver, private SkippedPathsResolver $skippedPathsResolver, + private FilePathHelper $filePathHelper, ) { } @@ -47,7 +49,7 @@ public function resolve(ProcessResult $processResult): array // rule-scoped paths are intentional, so they are reported even as mask paths foreach ($paths as $path) { - $skipDisplaysByPath[$path] = $rectorClass . ' => ' . $path; + $skipDisplaysByPath[$path] = $rectorClass . ' => ' . $this->filePathHelper->relativePath($path); } } @@ -57,7 +59,7 @@ public function resolve(ProcessResult $processResult): array continue; } - $skipDisplaysByPath[$globalPath] = $globalPath; + $skipDisplaysByPath[$globalPath] = $this->filePathHelper->relativePath($globalPath); } $usedSkips = $processResult->getUsedSkips(); diff --git a/src/ValueObject/ProcessResult.php b/src/ValueObject/ProcessResult.php index 7d5a785184e..5adc7a101d6 100644 --- a/src/ValueObject/ProcessResult.php +++ b/src/ValueObject/ProcessResult.php @@ -20,7 +20,7 @@ public function __construct( private array $systemErrors, private readonly array $fileDiffs, private readonly int $totalChanged, - private readonly array $usedSkips = [] + private array $usedSkips = [] ) { Assert::allIsInstanceOf($systemErrors, SystemError::class); Assert::allIsInstanceOf($fileDiffs, FileDiff::class); @@ -57,6 +57,20 @@ public function addSystemErrors(array $systemErrors): void $this->systemErrors = [...$this->systemErrors, ...$systemErrors]; } + /** + * Path-only skips are matched while finding files in the main process, but parallel runs build + * their result from worker processes only. Merge those main-process marks back in, or they would + * be wrongly reported as unused. + * + * @param string[] $usedSkips + */ + public function addUsedSkips(array $usedSkips): void + { + Assert::allString($usedSkips); + + $this->usedSkips = array_values(array_unique([...$this->usedSkips, ...$usedSkips])); + } + public function getTotalChanged(): int { return $this->totalChanged; diff --git a/tests/Reporting/UnusedSkipResolverTest.php b/tests/Reporting/UnusedSkipResolverTest.php index 756db90cdee..bb3e14ed3b8 100644 --- a/tests/Reporting/UnusedSkipResolverTest.php +++ b/tests/Reporting/UnusedSkipResolverTest.php @@ -63,6 +63,21 @@ public function testResolvesUnusedSkipsAsRuleAndPath(): void $this->assertNotContains(ThreeMan::class, $unusedSkips); } + public function testReportsUnusedSkipAsRelativePath(): void + { + SimpleParameterProvider::setParameter(Option::REPORT_UNUSED_SKIPS, true); + + $absolutePath = getcwd() . '/src/Reporting/UnusedSkipResolver.php'; + SimpleParameterProvider::setParameter(Option::SKIP, [ + FifthElement::class => [$absolutePath], + ]); + + $unusedSkips = $this->unusedSkipResolver->resolve(new ProcessResult([], [], 0, [])); + + // the absolute path is shortened to a relative one, matching the "->withSkip()" syntax + $this->assertContains(FifthElement::class . ' => src/Reporting/UnusedSkipResolver.php', $unusedSkips); + } + public function testResolvesNothingWhenDisabled(): void { SimpleParameterProvider::setParameter(Option::REPORT_UNUSED_SKIPS, false);