Skip to content

Commit b2e76b9

Browse files
[perf] Cache per-file import facts in ClassNameImportContext (#8687)
Co-authored-by: GitHub Action <actions@github.com>
1 parent 90951da commit b2e76b9

5 files changed

Lines changed: 115 additions & 42 deletions

File tree

‎rules/CodingStyle/ClassNameImport/AliasUsesResolver.php‎

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44

55
namespace Rector\CodingStyle\ClassNameImport;
66

7-
use PhpParser\Node;
87
use PhpParser\Node\Identifier;
98
use PhpParser\Node\Stmt;
10-
use PhpParser\Node\Stmt\Namespace_;
119
use PhpParser\Node\Stmt\Use_;
1210
use PhpParser\Node\UseItem;
13-
use Rector\PhpParser\Node\FileNode;
1411

1512
final readonly class AliasUsesResolver
1613
{
@@ -19,28 +16,6 @@ public function __construct(
1916
) {
2017
}
2118

22-
/**
23-
* @param Stmt[] $stmts
24-
* @return string[]
25-
*/
26-
public function resolveFromNode(Node $node, array $stmts): array
27-
{
28-
if (! $node instanceof Namespace_ && ! $node instanceof FileNode) {
29-
/** @var Namespace_[]|FileNode[] $namespaces */
30-
$namespaces = array_filter(
31-
$stmts,
32-
static fn (Stmt $stmt): bool => $stmt instanceof Namespace_ || $stmt instanceof FileNode
33-
);
34-
if (count($namespaces) !== 1) {
35-
return [];
36-
}
37-
38-
$node = current($namespaces);
39-
}
40-
41-
return $this->resolveFromStmts($node->stmts);
42-
}
43-
4419
/**
4520
* @param Stmt[] $stmts
4621
* @return string[]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\CodingStyle\ClassNameImport;
6+
7+
use PhpParser\Node\Stmt;
8+
use PhpParser\Node\Stmt\Namespace_;
9+
use Rector\PhpParser\Node\FileNode;
10+
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
11+
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
12+
use Rector\ValueObject\Application\File;
13+
14+
/**
15+
* Per-file import facts shared across skip voters. Aliased uses and used import types are
16+
* file-global, so they are resolved once per file stmts version and reused for every imported name.
17+
*/
18+
final class ClassNameImportContext
19+
{
20+
private ?File $cachedFile = null;
21+
22+
private int $cachedVersion = -1;
23+
24+
/**
25+
* @var string[]
26+
*/
27+
private array $aliasedUses = [];
28+
29+
/**
30+
* @var array<AliasedObjectType|FullyQualifiedObjectType>
31+
*/
32+
private array $usedImportTypes = [];
33+
34+
public function __construct(
35+
private readonly AliasUsesResolver $aliasUsesResolver
36+
) {
37+
}
38+
39+
/**
40+
* @return string[]
41+
*/
42+
public function resolveAliasedUses(File $file): array
43+
{
44+
$this->ensureResolved($file);
45+
46+
return $this->aliasedUses;
47+
}
48+
49+
/**
50+
* @return array<AliasedObjectType|FullyQualifiedObjectType>
51+
*/
52+
public function resolveUsedImportTypes(File $file): array
53+
{
54+
$this->ensureResolved($file);
55+
56+
return $this->usedImportTypes;
57+
}
58+
59+
private function ensureResolved(File $file): void
60+
{
61+
$newStmtsVersion = $file->getNewStmtsVersion();
62+
if ($file === $this->cachedFile && $newStmtsVersion === $this->cachedVersion) {
63+
return;
64+
}
65+
66+
$this->cachedFile = $file;
67+
$this->cachedVersion = $newStmtsVersion;
68+
$this->aliasedUses = $this->resolveAliasedUsesFromFile($file);
69+
$this->usedImportTypes = $this->resolveUsedImportTypesFromFile($file);
70+
}
71+
72+
/**
73+
* @return string[]
74+
*/
75+
private function resolveAliasedUsesFromFile(File $file): array
76+
{
77+
/** @var Namespace_[]|FileNode[] $namespaces */
78+
$namespaces = array_filter(
79+
$file->getNewStmts(),
80+
static fn (Stmt $stmt): bool => $stmt instanceof Namespace_ || $stmt instanceof FileNode
81+
);
82+
83+
if (count($namespaces) !== 1) {
84+
return [];
85+
}
86+
87+
$namespace = current($namespaces);
88+
89+
return $this->aliasUsesResolver->resolveFromStmts($namespace->stmts);
90+
}
91+
92+
/**
93+
* @return array<AliasedObjectType|FullyQualifiedObjectType>
94+
*/
95+
private function resolveUsedImportTypesFromFile(File $file): array
96+
{
97+
$fileNode = $file->getFileNode();
98+
if (! $fileNode instanceof FileNode) {
99+
return [];
100+
}
101+
102+
return $fileNode->resolveUsedImportTypes();
103+
}
104+
}

‎rules/CodingStyle/ClassNameImport/ClassNameImportSkipVoter/AliasClassNameImportSkipVoter.php‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Rector\CodingStyle\ClassNameImport\ClassNameImportSkipVoter;
66

77
use PhpParser\Node;
8-
use Rector\CodingStyle\ClassNameImport\AliasUsesResolver;
8+
use Rector\CodingStyle\ClassNameImport\ClassNameImportContext;
99
use Rector\CodingStyle\Contract\ClassNameImport\ClassNameImportSkipVoterInterface;
1010
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
1111
use Rector\ValueObject\Application\File;
@@ -22,13 +22,13 @@
2222
final readonly class AliasClassNameImportSkipVoter implements ClassNameImportSkipVoterInterface
2323
{
2424
public function __construct(
25-
private AliasUsesResolver $aliasUsesResolver
25+
private ClassNameImportContext $classNameImportContext
2626
) {
2727
}
2828

2929
public function shouldSkip(File $file, FullyQualifiedObjectType $fullyQualifiedObjectType, Node $node): bool
3030
{
31-
$aliasedUses = $this->aliasUsesResolver->resolveFromNode($node, $file->getNewStmts());
31+
$aliasedUses = $this->classNameImportContext->resolveAliasedUses($file);
3232
$longNameLowered = strtolower($fullyQualifiedObjectType->getClassName());
3333
$shortNameLowered = $fullyQualifiedObjectType->getShortNameLowered();
3434

‎rules/CodingStyle/ClassNameImport/ClassNameImportSkipVoter/UsesClassNameImportSkipVoter.php‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
namespace Rector\CodingStyle\ClassNameImport\ClassNameImportSkipVoter;
66

77
use PhpParser\Node;
8+
use Rector\CodingStyle\ClassNameImport\ClassNameImportContext;
89
use Rector\CodingStyle\Contract\ClassNameImport\ClassNameImportSkipVoterInterface;
9-
use Rector\PhpParser\Node\FileNode;
1010
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
1111
use Rector\ValueObject\Application\File;
1212

@@ -19,14 +19,14 @@
1919
*/
2020
final readonly class UsesClassNameImportSkipVoter implements ClassNameImportSkipVoterInterface
2121
{
22+
public function __construct(
23+
private ClassNameImportContext $classNameImportContext
24+
) {
25+
}
26+
2227
public function shouldSkip(File $file, FullyQualifiedObjectType $fullyQualifiedObjectType, Node $node): bool
2328
{
24-
$fileNode = $file->getFileNode();
25-
if (! $fileNode instanceof FileNode) {
26-
return false;
27-
}
28-
29-
$useImportTypes = $fileNode->resolveUsedImportTypes();
29+
$useImportTypes = $this->classNameImportContext->resolveUsedImportTypes($file);
3030

3131
foreach ($useImportTypes as $useImportType) {
3232
if (! $useImportType->equals($fullyQualifiedObjectType) && $useImportType->areShortNamesEqual(

‎rules/Visibility/Rector/ClassMethod/ChangeMethodVisibilityRector.php‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,6 @@ public function configure(array $configuration): void
133133

134134
private function hasMatchingMethodName(ClassMethod $classMethod): bool
135135
{
136-
foreach ($this->methodVisibilities as $methodVisibility) {
137-
if ($this->isName($classMethod, $methodVisibility->getMethod())) {
138-
return true;
139-
}
140-
}
141-
142-
return false;
136+
return array_any($this->methodVisibilities, fn(ChangeMethodVisibility $methodVisibility): bool => $this->isName($classMethod, $methodVisibility->getMethod()));
143137
}
144138
}

0 commit comments

Comments
 (0)