Skip to content

Commit f0d8be2

Browse files
committed
[perf] [experiment] preload all node classes once at start
1 parent 9e7f77d commit f0d8be2

5 files changed

Lines changed: 64 additions & 17 deletions

File tree

phpstan.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,5 @@ parameters:
379379
-
380380
message: '#Property Rector\\PhpParser\\NodeTraverser\\AbstractImmutableNodeTraverser\:\:\$visitors \(list<PhpParser\\NodeVisitor>\) does not accept array<int\|string, PhpParser\\NodeVisitor>#'
381381
path: src/PhpParser/NodeTraverser/AbstractImmutableNodeTraverser.php
382+
383+
- '#Method Rector\\Bridge\\PhpParser\\NodeClassFinder\:\:find\(\) should return array<class\-string<PhpParser\\Node>> but returns array<class\-string>#'
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Bridge\PhpParser;
6+
7+
use Nette\Loaders\RobotLoader;
8+
use ReflectionClass;
9+
10+
final class NodeClassFinder
11+
{
12+
/**
13+
* @return array<class-string<\PhpParser\Node>>
14+
*/
15+
public static function find(): array
16+
{
17+
$robotLoader = new RobotLoader();
18+
$robotLoader->acceptFiles = ['*.php'];
19+
20+
$phpParserNodeDirectory = __DIR__ . '/../../../vendor/nikic/php-parser/lib/PhpParser/Node/';
21+
$robotLoader->addDirectory($phpParserNodeDirectory);
22+
23+
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/node-classes');
24+
$robotLoader->refresh();
25+
26+
/** @var array<class-string> $nodeClasses */
27+
$nodeClasses = array_keys($robotLoader->getIndexedClasses());
28+
29+
return array_filter($nodeClasses, function (string $nodeClass): bool {
30+
$nodeClassReflection = new ReflectionClass($nodeClass);
31+
if ($nodeClassReflection->isAbstract()) {
32+
return false;
33+
}
34+
35+
return ! $nodeClassReflection->isInterface();
36+
});
37+
}
38+
}

src/Configuration/ConfigurationRuleFilter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function filter(array $rectors): array
3131

3232
$onlyRule = $this->configuration->getOnlyRule();
3333
if ($onlyRule !== null) {
34-
return $this->filterOnlyRule($rectors, $onlyRule);
34+
return [$onlyRule];
3535
}
3636

3737
return $rectors;
@@ -46,7 +46,7 @@ public function filterOnlyRule(array $rectors, string $onlyRule): array
4646
$activeRectors = [];
4747
foreach ($rectors as $rector) {
4848
if ($rector instanceof $onlyRule) {
49-
$activeRectors[] = $rector;
49+
return [$rector];
5050
}
5151
}
5252

src/PhpParser/NodeTraverser/RectorNodeTraverser.php

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Rector\Configuration\ConfigurationRuleFilter;
1111
use Rector\Contract\Rector\RectorInterface;
1212
use Rector\VersionBonding\PhpVersionedFilter;
13+
use Webmozart\Assert\Assert;
1314

1415
/**
1516
* @see \Rector\Tests\PhpParser\NodeTraverser\RectorNodeTraverserTest
@@ -63,22 +64,9 @@ public function refreshPhpRectors(array $rectors): void
6364
*/
6465
public function getVisitorsForNode(Node $node): array
6566
{
66-
$nodeClass = $node::class;
67+
Assert::true($this->areNodeVisitorsPrepared);
6768

68-
if (! isset($this->visitorsPerNodeClass[$nodeClass])) {
69-
$this->visitorsPerNodeClass[$nodeClass] = [];
70-
/** @var RectorInterface $visitor */
71-
foreach ($this->visitors as $visitor) {
72-
foreach ($visitor->getNodeTypes() as $nodeType) {
73-
if (is_a($nodeClass, $nodeType, true)) {
74-
$this->visitorsPerNodeClass[$nodeClass][] = $visitor;
75-
continue 2;
76-
}
77-
}
78-
}
79-
}
80-
81-
return $this->visitorsPerNodeClass[$nodeClass];
69+
return $this->visitorsPerNodeClass[$node::class] ?? [];
8270
}
8371

8472
/**
@@ -97,6 +85,21 @@ private function prepareNodeVisitors(): void
9785
// filter by configuration
9886
$this->visitors = $this->configurationRuleFilter->filter($this->visitors);
9987

88+
// static $counter = 0;
89+
90+
// 1. get all node non-interface, non-abstract classes
91+
// 2. iterate through them
92+
foreach (\Rector\Bridge\PhpParser\NodeClassFinder::find() as $nodeClass) {
93+
/** @var RectorInterface $visitor */
94+
foreach ($this->visitors as $visitor) {
95+
foreach ($visitor->getNodeTypes() as $matchingNodeType) {
96+
if (is_a($nodeClass, $matchingNodeType, true)) {
97+
$this->visitorsPerNodeClass[$nodeClass][] = $visitor;
98+
}
99+
}
100+
}
101+
}
102+
100103
$this->areNodeVisitorsPrepared = true;
101104
}
102105
}

src/ValueObject/Configuration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Rector\ChangesReporting\Output\ConsoleOutputFormatter;
88
use Rector\Configuration\Option;
99
use Rector\Configuration\Parameter\SimpleParameterProvider;
10+
use Rector\Contract\Rector\RectorInterface;
1011
use Rector\ValueObject\Configuration\LevelOverflow;
1112
use Webmozart\Assert\Assert;
1213

@@ -61,6 +62,9 @@ public function getFileExtensions(): array
6162
return $this->fileExtensions;
6263
}
6364

65+
/**
66+
* @return class-string<RectorInterface>|null
67+
*/
6468
public function getOnlyRule(): ?string
6569
{
6670
return $this->onlyRule;

0 commit comments

Comments
 (0)