Repository navigation
Add an option to re-run only a changed rule on the files that need it - #6733
Open
SanderMuller wants to merge 7 commits into
Open
SanderMuller wants to merge 7 commits into
SanderMuller wants to merge 7 commits into
Conversation
When an extension file that changed is a class that implements only Rule, the result cache no longer starts a full analysis. It re-analyses only the files that passed a node of the rule's old or new type to the rules, and in these files only that rule runs. - Error records the class of the rule that reported it, and the analysed file when the error is in another file. - FileAnalyserCallback records the node classes of each analysed file, and the result cache stores them with the node types of each rule. - In a rule-scoped file, FileAnalyser runs only the changed rules, no collectors, and leaves the local ignores to the result cache. - ResultCacheManager::process() keeps the cached data of the rule-scoped files, replaces the errors of the changed rules and matches the line ignores of each rule-scoped file again. The parallel workers get the rule scope from a file that an environment variable names. This is a prototype for phpstan/phpstan#15460. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… tests Only a rule of the project itself can change without a full analysis, so the result cache now stores node types only for those rules. Before, it stored them for every registered rule, which made the cache file of e2e/result-cache-truncated four times larger and moved where its cut lands. An edited project rule is now re-run instead of starting a full analysis, so the result-cache-extension e2e tests check for that. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The main process now adds the changed rules and the job's rule-scoped files to each job request, and the worker sets them before it analyses the job. This replaces the environment variable and the temporary file, and works the same for spawned and forked workers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The new e2e test edits the project's own rules and collector, and runs PHPStan after each edit. It checks message changes, an ignore comment that stops or starts to match, an error in a trait, an error that a rule reports into another file, a CollectedDataNode rule, and a collector. The expected lines come from a cold run of the same files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rule scope stored a list of node class names for each file, so the cache file repeated the same names for every file. It now stores the classes once in a table, and each file stores a list of positions in it, as the dependency graph does with paths. restore() decodes the section back to class names, so findRuleScopedFiles() and mergeNodeTypes() do not change. A malformed rule scope now makes restore() discard the cache, as other decode errors do. This includes a cache written in the earlier format of this section. On rector-src the rule scope section was 4,104,301 bytes and is now 1,265,225. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RuleScopeCodec holds the encode and decode steps of the ruleScope section, which were private methods of ResultCacheManager. The manager still turns the file keys into absolute paths after decode. The test checks that each node class is named once, that a round trip gives the same data, and that each malformed shape throws. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SanderMuller
marked this pull request as ready for review
October 12, 2026 07:41
Collaborator
|
This pull request has been marked as ready for review. |
By default, a changed rule of the project starts a full analysis again, as on 2.3.x. A project turns the rule-scoped re-analysis on with resultCacheRuleScopedReanalysis: true. When the option is off, the result cache does not store the rule scope, so the cache file does not grow. The result-cache-extension e2e tests assert the full analysis again, and result-cache-rule-edit turns the option on. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
The red |
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.
For phpstan/phpstan#15460. The issue has the measurements and the edge cases that I checked.
With the new option
resultCacheRuleScopedReanalysis: true, a project that edits one of its own rules re-runs only that rule. It runs the rule on every file that contains a node of the rule's type, whether the file reported errors before or not. The option is off by default, so a rule change still starts a full analysis unless the project turns it on. It is meant for projects that develop their own rules. On rector-src a rule edit took 3.0 s, against 6.9-7.2 s for the full analysis, with the same errors.Why the result is the same as a full analysis: a rule's
processNode()only gets nodes of its node type. The result cache records every node class that each file passes to the rules. So the changed rule sees the same nodes as in a full analysis.How it works:
Errorrecords the class of the rule that reported it. When the error is in another file than the analysed one, it also records the analysed file.FileAnalyserCallbackrecords the node classes of each analysed file. With the option on, the result cache stores them, and the node types of each project rule, in a newruleScopeframe.RuleScopeCodecnames each node class once in a table. On rector-src the cache file is 2.9% larger than on 2.3.x (45.7 MB to 47.1 MB). With the option off, the frame is not written.ResultCacheManager::restore()checks each changed extension file. For a class that implements onlyRule, it selects the files with a node of the rule's old or new type. Any other changed extension file starts a full analysis, as before.FileAnalyserruns only the changed rules and no collectors, and it does not match the local ignores.ParallelAnalyseradds the changed rules and the job's rule-scoped files to each job request, andWorkerRunnersets them before it analyses the job.ResultCacheManager::process()keeps the cached data of these files and replaces the errors of the changed rules. Then it matches the line ignores of each file again withLocalIgnoresProcessor. Only the errors that the file's own analysis reported take part.Tests:
e2e/result-cache-rule-editturns the option on and edits the project's rules five times. After each edit, it checks the errors that a cold run reports. The cases are ignores, a trait, an error reported into another file, aCollectedDataNoderule and a collector.result-cache-extension-*e2e tests keep their 2.3.x assertions: without the option, a rule edit starts a full analysis.RuleScopeCodecTestcovers the cache format, including malformed data.Not in this PR: a change of the
rules:list in the config still starts a full analysis.🤖 Generated with Claude Code