@@ -53,7 +53,8 @@ public function testResolvesUnusedSkipsAsRuleAndPath(): void
5353 {
5454 SimpleParameterProvider ::setParameter (Option::REPORT_UNUSED_SKIPS , true );
5555
56- $ processResult = new ProcessResult ([], [], 0 , [self ::USED_RULE_MASK ]);
56+ // used skips are tracked scoped to their rule as "class|path"
57+ $ processResult = new ProcessResult ([], [], 0 , [AnotherClassToSkip::class . '| ' . self ::USED_RULE_MASK ]);
5758 $ unusedSkips = $ this ->unusedSkipResolver ->resolve ($ processResult );
5859
5960 // rule-scoped unused skip is reported as "rule:" with the path nested below
@@ -65,6 +66,28 @@ public function testResolvesUnusedSkipsAsRuleAndPath(): void
6566 $ this ->assertNotContains (ThreeMan::class, $ unusedSkips );
6667 }
6768
69+ public function testSamePathUnderAnotherRuleDoesNotMarkSkipUsed (): void
70+ {
71+ SimpleParameterProvider ::setParameter (Option::REPORT_UNUSED_SKIPS , true );
72+
73+ $ sharedMask = '*/shared-mask/* ' ;
74+ SimpleParameterProvider ::setParameter (Option::SKIP , [
75+ // same path skipped under two rules - only the first one actually matched a file
76+ AnotherClassToSkip::class => [$ sharedMask ],
77+ FifthElement::class => [$ sharedMask ],
78+ ]);
79+
80+ // used skip is scoped to its rule, so the shared path stays unused under the other rule
81+ $ processResult = new ProcessResult ([], [], 0 , [AnotherClassToSkip::class . '| ' . $ sharedMask ]);
82+ $ unusedSkips = $ this ->unusedSkipResolver ->resolve ($ processResult );
83+
84+ // the never-matched rule still reports its shared path as unused
85+ $ this ->assertContains (FifthElement::class . ': ' . "\n * " . $ sharedMask , $ unusedSkips );
86+
87+ // the matched rule is excluded
88+ $ this ->assertNotContains (AnotherClassToSkip::class . ': ' . "\n * " . $ sharedMask , $ unusedSkips );
89+ }
90+
6891 public function testReportsUnusedSkipAsRelativePath (): void
6992 {
7093 SimpleParameterProvider ::setParameter (Option::REPORT_UNUSED_SKIPS , true );
0 commit comments