Skip to content

Clone and print only the nodes that a fix changes - #7015

Open
SanderMuller wants to merge 1 commit into
phpstan:2.3.xfrom
SanderMuller:fix-path-copy
Open

SanderMuller wants to merge 1 commit into
phpstan:2.3.xfrom
SanderMuller:fix-path-copy

Conversation

@SanderMuller

Copy link
Copy Markdown
Contributor

For each fixable error, RuleErrorTransformer::fix() clones every node of the file and prints the whole file again. So a file with many fixable errors takes time that grows faster than the number of errors.

PathCloningVisitor now clones only the fixed nodes and their parents. Each other node stays the original object and gets itself as its origNode. PhpPrinter::p() copies the original code of such a node without printing its children. After printing, restoreOriginalNodes() removes the attribute again, because the original nodes belong to the analysed AST.

When a fix adds a class name or changes a PHPDoc, SharedNodeCloningVisitor first clones the whole tree, because NameResolver and NewClassNamesVisitor change nodes in place.

Analysis time with the result cache cleared, hyperfine with 8 runs each, PHP 8.5, macOS arm64:

Fixable errors 2.3.x This PR
100 (bug-14996.php) 1.80 s ± 0.13 1.19 s ± 0.23
600 (the methods of bug-14996.php 6 times) 30.5 s ± 0.9 5.6 s ± 0.1

On both files, --fix gives the same output as 2.3.x, byte for byte.

RenameFunctionRule is a new test rule. The analysis reaches each function after the fixes of the functions above it. So the test fails when a fix leaves a node of the analysed AST marked as its own origNode, for example without restoreOriginalNodes().

The first fix in a file still takes the old path, because it detects the indentation. A file without an indented line takes the old path for each fix.

This came up in phpstan/phpstan-strict-rules#349.

🤖 Generated with Claude Code

For each fixable error, fix() cloned every node of the file and printed
the whole file again. PathCloningVisitor now clones only the fixed nodes
and their parents. Each other node stays the original object, and
PhpPrinter copies its original code without printing its children.

The original nodes get themselves as origNode while the fix is printed,
and lose the attribute again afterwards. When a fix adds a class name,
SharedNodeCloningVisitor clones the whole tree first, because NameResolver
and NewClassNamesVisitor change nodes in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant