@@ -34,9 +34,9 @@ public function __construct(SkippedClassResolver $skippedClassResolver, SkippedP
3434 }
3535 /**
3636 * Resolves skips configured via "->withSkip()" that never matched any element during the run.
37- * Rule-scoped skips are returned as "rule => path" so the user knows exactly what to remove;
38- * global skips are returned as a plain path. Returns an empty array unless
39- * "->reportUnusedSkips()" is enabled.
37+ * Rule-scoped skips are grouped under their rule ( "rule => path", or "rule => [ path\n path ]"
38+ * for multiple paths) so the user knows exactly what to remove; global skips are returned as a
39+ * plain path. Returns an empty array unless "->reportUnusedSkips()" is enabled.
4040 *
4141 * @return string[]
4242 */
@@ -45,32 +45,55 @@ public function resolve(ProcessResult $processResult): array
4545 if (!SimpleParameterProvider::provideBoolParameter (Option::REPORT_UNUSED_SKIPS , \false)) {
4646 return [];
4747 }
48- // map of trackable skip path => human-readable display; skips are tracked at runtime by
49- // their path, but rule-scoped ones are printed as "rule => path" so the user knows exactly
50- // what to remove. Skip-everywhere rule skips (null path) are forgotten from the container
51- // at boot, so they never reach the skipper and cannot be tracked.
52- $ skipDisplaysByPath = [];
48+ // a narrowed run (cli paths, "--only" or "--only-suffix") only touches part of the codebase,
49+ // so skips outside that scope look falsely unused - reporting them would be noise
50+ if (SimpleParameterProvider::provideBoolParameter (Option::IS_RUN_NARROWED , \false)) {
51+ return [];
52+ }
53+ // map of rule => (trackable skip path => relative display path); skips are tracked at
54+ // runtime by their path, but rule-scoped ones are printed grouped under their rule so the
55+ // user knows exactly what to remove. Skip-everywhere rule skips (null path) are forgotten
56+ // from the container at boot, so they never reach the skipper and cannot be tracked.
57+ $ relativePathsByClass = [];
5358 foreach ($ this ->skippedClassResolver ->resolve () as $ rectorClass => $ paths ) {
5459 if ($ paths === null ) {
5560 continue ;
5661 }
5762 // rule-scoped paths are intentional, so they are reported even as mask paths
5863 foreach ($ paths as $ path ) {
59- $ skipDisplaysByPath [ $ path ] = $ rectorClass . ' => ' . $ this ->filePathHelper ->relativePath ($ path );
64+ $ relativePathsByClass [ $ rectorClass ][ $ path ] = $ this ->filePathHelper ->relativePath ($ path );
6065 }
6166 }
6267 // global mask paths like "*/some/*" are hard to spot and report false positives, skip them
68+ $ globalRelativePaths = [];
6369 foreach ($ this ->skippedPathsResolver ->resolve () as $ globalPath ) {
6470 if (strpos ($ globalPath , '* ' ) !== \false) {
6571 continue ;
6672 }
67- $ skipDisplaysByPath [$ globalPath ] = $ this ->filePathHelper ->relativePath ($ globalPath );
73+ $ globalRelativePaths [$ globalPath ] = $ this ->filePathHelper ->relativePath ($ globalPath );
6874 }
6975 $ usedSkips = $ processResult ->getUsedSkips ();
7076 $ unusedSkips = [];
71- foreach ($ skipDisplaysByPath as $ path => $ skipDisplay ) {
77+ // group unused rule-scoped paths under their rule, matching the "->withSkip()" config shape
78+ foreach ($ relativePathsByClass as $ rectorClass => $ relativePaths ) {
79+ $ unusedRelativePaths = [];
80+ foreach ($ relativePaths as $ path => $ relativePath ) {
81+ if (!in_array ($ path , $ usedSkips , \true)) {
82+ $ unusedRelativePaths [] = $ relativePath ;
83+ }
84+ }
85+ if ($ unusedRelativePaths === []) {
86+ continue ;
87+ }
88+ if (count ($ unusedRelativePaths ) === 1 ) {
89+ $ unusedSkips [] = $ rectorClass . ' => ' . $ unusedRelativePaths [0 ];
90+ continue ;
91+ }
92+ $ unusedSkips [] = $ rectorClass . ' => [ ' . implode ("\n " , $ unusedRelativePaths ) . ' ] ' ;
93+ }
94+ foreach ($ globalRelativePaths as $ path => $ relativePath ) {
7295 if (!in_array ($ path , $ usedSkips , \true)) {
73- $ unusedSkips [] = $ skipDisplay ;
96+ $ unusedSkips [] = $ relativePath ;
7497 }
7598 }
7699 return $ unusedSkips ;
0 commit comments