Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions src/Rector/AbstractRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ abstract class AbstractRector implements RectorInterface

private CreatedByRuleDecorator $createdByRuleDecorator;

// file-level cache, recomputed once per file (see enterNode())
private ?File $cachedFile = null;

private string $cachedFilePath = '';

private bool $cachedHtmlAverseSkip = false;

private ?SkipMatch $cachedSkipMatch = null;

public function autowire(
NodeNameResolver $nodeNameResolver,
NodeTypeResolver $nodeTypeResolver,
Expand Down Expand Up @@ -106,15 +115,23 @@ public function autowire(
*/
final public function enterNode(Node $node): int|Node|null|array
{
// keep $this->file populated for BC; refactor() is only ever reached through here
$this->file = $this->getFile();
$file = $this->getFile();

// file-level invariants are constant across all nodes of a file; compute once per file
if ($this->cachedFile !== $file) {
$this->cachedFile = $file;
// keep $this->file populated for BC; refactor() is only ever reached through here
$this->file = $file;
$this->cachedFilePath = $file->getFilePath();
$this->cachedHtmlAverseSkip = is_a($this, HTMLAverseRectorInterface::class, true)
&& $file->containsHTML();
$this->cachedSkipMatch = $this->skipper->matchSkip($this, $this->cachedFilePath);
}

if (is_a($this, HTMLAverseRectorInterface::class, true) && $this->file->containsHTML()) {
if ($this->cachedHtmlAverseSkip) {
return null;
}

$filePath = $this->file->getFilePath();

// node already changed by this rule in a previous pass → hard skip
if ($this->skipper->shouldSkipCurrentNode(static::class, $node)) {
return null;
Expand All @@ -123,10 +140,9 @@ final public function enterNode(Node $node): int|Node|null|array
// class/path skip is configured for this rule and file: run the rule on a deep clone to learn
// whether it would actually have changed anything. Only a skip that prevents a real change
// 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->cachedSkipMatch instanceof SkipMatch) {
if ($this->refactor($this->cloneNode($node)) !== null) {
$this->skipper->markSkipUsed($skipMatch);
$this->skipper->markSkipUsed($this->cachedSkipMatch);
}

return null;
Expand Down Expand Up @@ -165,7 +181,7 @@ final public function enterNode(Node $node): int|Node|null|array
return $refactoredNodeOrState;
}

return $this->postRefactorProcess($originalNode, $node, $refactoredNodeOrState, $filePath);
return $this->postRefactorProcess($originalNode, $node, $refactoredNodeOrState, $this->cachedFilePath);
}

protected function getFile(): File
Expand Down
Loading