Skip to content

Fix --only runs caching files as unchanged, hiding pending changes from full runs#8029

Open
SanderMuller wants to merge 3 commits into
rectorphp:mainfrom
SanderMuller:fix/only-rule-cache-poisoning
Open

Fix --only runs caching files as unchanged, hiding pending changes from full runs#8029
SanderMuller wants to merge 3 commits into
rectorphp:mainfrom
SanderMuller:fix/only-rule-cache-poisoning

Conversation

@SanderMuller

@SanderMuller SanderMuller commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Problem

A file that is clean under one rule is not necessarily clean under all rules. A run with --only SomeRule (or --only-suffix) caches such files as unchanged, so the next full run silently skips their pending changes until --clear-cache.

Repro on main:

  1. rector process --dry-run --clear-cache --only "Rector\DeadCode\Rector\ClassMethod\RemoveEmptyClassMethodRector" on a file with an always-true if → clean under that rule, file gets cached
  2. rector process --dry-run → reports [OK] Rector is done! instead of the pending RemoveAlwaysTrueIfConditionRector change

Fix

Skip the unchanged-files cache write on selective runs. One guard in ApplicationFileProcessor.

Test

Unit test on ApplicationFileProcessor::processFiles(): an --only / --only-suffix run must leave the file uncached, a plain dry-run caches it (control test). The two guard tests fail on main, pass with the fix.

Split out of #8028 as a standalone pre-existing bugfix, as discussed with @TomasVotruba.

🤖 Generated with Claude Code

A file clean under one rule is not necessarily clean under all rules. An
--only run cached such files as unchanged, so the next full run silently
skipped their pending changes until --clear-cache.

Covered by a new e2e scenario that fails on main: an --only run followed
by a full run must still report the change pending under the other rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SanderMuller added a commit to SanderMuller/rector-src that referenced this pull request Jun 10, 2026
The cache only checked each file's own content, so a clean file stayed
skipped on warm runs even when one of its dependencies changed, e.g. a
parent class method gaining a return type that lets a child file infer
its own. A fresh run reports the new change, a warm run misses it.

PHPStanNodeScopeResolver now records each file's dependencies during
scope resolution using PHPStan's own DependencyResolver. Cache entries
store the file's own hash plus one hash per dependency, all re-validated
on load; legacy string entries self-upgrade on the next write. A failed
capture skips caching entirely rather than caching a partial set.

To keep capture cheap, only nodes whose resolver branch can produce
dependencies are resolved, and native function calls are skipped via
NativeFunctionCallAnalyzer, avoiding PHPStan's function signature map
load. Two tripwire tests pin the external assumptions: one parses the
bundled DependencyResolver and fails if its branch set changes on a
PHPStan bump, the other asserts native function reflections stay
fileless.

Selective runs (--only, --only-suffix) bypass the cache write, same
guard as rectorphp#8029.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@TomasVotruba

Copy link
Copy Markdown
Member

Thanks for the fix 👍

e2e seems heavy for such a verfication.
Is there any way to test this via unit test? Should be some simple operation to check.

ApplicationFileProcessor::processFiles() with no rules registered reaches
the cache-write branch directly, so the guard is testable without an e2e
project: an --only / --only-suffix run must leave the file uncached, a
plain dry-run caches it. Fails on main, passes with the fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SanderMuller

Copy link
Copy Markdown
Contributor Author

Good call — replaced the e2e with a unit test, PR is now 81 lines total.

ApplicationFileProcessor::processFiles() with no rules registered reaches the cache-write branch directly, so no e2e project is needed: an --only / --only-suffix run must leave the file uncached, a plain dry-run caches it (control test). Verified the two guard tests fail on main and pass with the fix. Runs in ~0.6s.

Comment thread tests/Application/ApplicationFileProcessor/Source/CleanFile.php
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants