[skip] mark class/path skip used only when rule would change the file#8076
Merged
Conversation
Previously a skip was marked used the moment a rule's node matched it, even if refactor() would return null. Now the skipped rule is probed on a deep clone and the skip is marked used only when it would actually change the file; the real node is left untouched.
samsonasik
reviewed
Jun 22, 2026
| // counts as used; the original node is left untouched, so the file stays skipped either way. | ||
| $skipMatch = $this->skipper->matchSkip($this, $filePath); | ||
| if ($skipMatch instanceof SkipMatch) { | ||
| if ($this->refactor($this->cloneNode($node)) !== null) { |
Member
There was a problem hiding this comment.
The refactor() now called twice, I think this can be improved, I will look into it.
Member
There was a problem hiding this comment.
Member
Author
There was a problem hiding this comment.
There is return null; in if ($skipMatch instanceof SkipMatch) {, so its called only once.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
SkipSkipper::doesMatchSkip()calledmarkUsed()the moment a rule's node matched a skip — even when the rule would never change the file. So a skip configured for a rule that is inert on that file still got reported as used, hiding genuinely removable skip config from the unused-skip report.A skip is only truly "used" when it actually prevents a change.
Fix
Mark a class/path skip used only when the rule would actually change the file. Detection runs the rule on a deep clone, so the real node is never mutated → no change leaks into a skipped file.
AbstractRector::enterNode():Deep clone helper (attributes preserved, real node untouched):
Matching is now side-effect free and returns a small value object instead of marking eagerly:
Skipperowns marking;shouldSkipElementAndFilePath()keeps its eager behavior for thePostFileProcessorpath, whilematchSkip()/markSkipUsed()let the rule path defer marking until the change is confirmed:Test
SkipUsedTrackingTestrunsRenameClassRectorwith two skipped files — one that usesOldClass(rule would rename → change) and one that does not:Tradeoffs
refactor()runs on a clone for skipped nodes → small perf cost, on skipped files only.refactor()may still touch internal collectors during the probe — smaller risk than leaking output, not zero.