Reporting Unused Skips

Over time, the ->withSkip() list in your rector.php grows. Rules get removed, files get renamed or deleted, bugs get fixed - but the skips for them stay behind. These dead skips are easy to miss and hide what Rector would actually do.

Rector can report skips that never matched anything during a run, so you can safely remove them. This works just like PHPStan's "reporting unused ignores".

Turn it on

It is off by default. Enable it with ->reportUnusedSkips():

<?php

use Rector\Config\RectorConfig;
use Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector;

return RectorConfig::configure()
    ->withRules([SimplifyUselessVariableRector::class])
    ->reportUnusedSkips()
    ->withSkip([
        SimplifyUselessVariableRector::class => [
            __DIR__ . '/src/Used.php',         // matched → not reported
            __DIR__ . '/src/NeverMatches.php', // never matched → reported
        ],
    ]);

Turn it off

Omit the call, or pass false to disable it explicitly:

return RectorConfig::configure()
    ->reportUnusedSkips(false);

What it reports

After the run, any skip that never matched a file is printed as a warning, with the full rule => path so you know exactly what to delete:

 [WARNING] This skip is unused, it never matched any element. You can remove it
           from "->withSkip()"

 * Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector => /src/NeverMatches.php

JSON output

When you run with --output-format=json (typical for editor and CI integrations), unused skips are added under a new unused_skips key. The key only appears when reporting is enabled and something is actually unused, so the rest of the JSON schema stays untouched:

{
    "totals": { "changed_files": 1, "errors": 0 },
    "file_diffs": [ ],
    "changed_files": [ "src/SomeClass.php" ],
    "unused_skips": [
        "Rector\\CodeQuality\\Rector\\FunctionLike\\SimplifyUselessVariableRector => /src/NeverMatches.php"
    ]
}

Notes

  • Both rule-scoped paths (Rule::class => ['...']) and plain global paths are tracked.
  • Global mask paths like */some/* are skipped from the report - they are hard to spot and prone to false positives. Rule-scoped masks are reported, since they are intentional.
  • Skip-everywhere rule skips (a rule with no path) are not tracked, as they are removed at boot before the run.
  • It works in parallel mode too - used skips are aggregated across workers before the report.