Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2e/parallel-unused-skips/expected-output.diff
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
[WARNING] This skip is unused, it never matched any element. You can remove it
from "->withSkip()"

* */NonexistentUnused/*
* Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector => */NonexistentUnused/*
37 changes: 25 additions & 12 deletions src/Reporting/MissConfigurationReporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,40 @@ public function reportUnusedSkips(ProcessResult $processResult): void
return;
}

// rule-scoped path skips are trackable at runtime; skip-everywhere rule skips
// (null path) are forgotten from the container at boot, so they never reach the skipper.
// rule-scoped paths are intentional, so they are reported even as mask paths
$skippedClassPaths = [];
foreach ($this->skippedClassResolver->resolve() as $paths) {
// map of trackable skip path => human-readable display; skips are tracked at runtime by
// their path, but rule-scoped ones are printed as "rule => path" so the user knows exactly
// what to remove. Skip-everywhere rule skips (null path) are forgotten from the container
// at boot, so they never reach the skipper and cannot be tracked.
$skipDisplaysByPath = [];
foreach ($this->skippedClassResolver->resolve() as $rectorClass => $paths) {
if ($paths === null) {
continue;
}

$skippedClassPaths = [...$skippedClassPaths, ...$paths];
// rule-scoped paths are intentional, so they are reported even as mask paths
foreach ($paths as $path) {
$skipDisplaysByPath[$path] = $rectorClass . ' => ' . $path;
}
}

// global mask paths like "*/some/*" are hard to spot and report false positives, skip them
$globalPaths = array_filter(
$this->skippedPathsResolver->resolve(),
static fn (string $skip): bool => ! str_contains($skip, '*')
);
foreach ($this->skippedPathsResolver->resolve() as $globalPath) {
if (str_contains($globalPath, '*')) {
continue;
}

$configuredSkips = [...$skippedClassPaths, ...$globalPaths];
$skipDisplaysByPath[$globalPath] = $globalPath;
}

$usedSkips = $processResult->getUsedSkips();

$unusedSkips = [];
foreach ($skipDisplaysByPath as $path => $skipDisplay) {
if (! in_array($path, $usedSkips, true)) {
$unusedSkips[] = $skipDisplay;
}
}

$unusedSkips = array_values(array_diff($configuredSkips, $processResult->getUsedSkips()));
if ($unusedSkips === []) {
return;
}
Expand Down
5 changes: 3 additions & 2 deletions tests/Reporting/MissConfigurationReporterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ public function testReportsOnlyTrackableUnusedSkips(): void

$output = $this->bufferedOutput->fetch();

// unused rule-scoped path is reported by path, not rule class
// unused rule-scoped path is reported as "rule => path", so both are shown
$this->assertStringContainsString('dead-rule', $output);
$this->assertStringNotContainsString('FifthElement', $output);
$this->assertStringContainsString('FifthElement', $output);

// matched rule-scoped path is excluded
$this->assertStringNotContainsString('matched-rule', $output);
$this->assertStringNotContainsString('AnotherClassToSkip', $output);

// global mask path is excluded (hard to spot, false-positive prone)
$this->assertStringNotContainsString('global-mask', $output);
Expand Down
Loading