diff --git a/bench/short_name_resolver_traversal_bench.php b/bench/short_name_resolver_traversal_bench.php new file mode 100644 index 00000000000..605f7c485cf --- /dev/null +++ b/bench/short_name_resolver_traversal_bench.php @@ -0,0 +1,98 @@ +create(); +$rectorConfig->boot(); + +/** @var TestingParser $testingParser */ +$testingParser = $rectorConfig->make(TestingParser::class); + +$sourceFilePath = __DIR__ . '/Source/ManyNamesSource.php'; + +$simpleCallableNodeTraverser = $rectorConfig->make(SimpleCallableNodeTraverser::class); +$nodeNameResolver = $rectorConfig->make(NodeNameResolver::class); +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); +$useImportNameMatcher = $rectorConfig->make(UseImportNameMatcher::class); +$phpDocInfoFactory = $rectorConfig->make(PhpDocInfoFactory::class); + +$makeResolver = static fn (): ShortNameResolver => new ShortNameResolver( + $simpleCallableNodeTraverser, + $nodeNameResolver, + $betterNodeFinder, + $useImportNameMatcher, + $phpDocInfoFactory +); + +$cpuSeconds = static function (): float { + $usage = getrusage(); + return $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1_000_000 + + $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1_000_000; +}; + +$run = static function () use ($makeResolver, $testingParser, $sourceFilePath): void { + $resolver = $makeResolver(); + $file = $testingParser->parseFilePathToFile($sourceFilePath); + + for ($rebuild = 0; $rebuild < REBUILDS; ++$rebuild) { + // bump version so the next call misses the cache and does the full walk + $file->changeNewStmts($file->getNewStmts()); + $resolver->resolveFromFile($file); + } +}; + +$measure = static function () use ($run, $cpuSeconds): array { + // warm up autoloading / parser once, excluded from timing + $run(); + + $cpuStart = $cpuSeconds(); + $wallStart = hrtime(true); + + for ($repeat = 0; $repeat < REPEAT; ++$repeat) { + $run(); + } + + return [ + 'cpu' => $cpuSeconds() - $cpuStart, + 'wall' => (hrtime(true) - $wallStart) / 1_000_000_000, + ]; +}; + +assert(class_exists(File::class)); + +$result = $measure(); + +printf('ShortNameResolver::resolveFromFile traversal benchmark%s', PHP_EOL); +printf(' rebuilds=%d repeat=%d%s', REBUILDS, REPEAT, PHP_EOL); +printf('%s', PHP_EOL); +printf(' forced re-resolve cpu=%.4fs wall=%.4fs%s', $result['cpu'], $result['wall'], PHP_EOL); diff --git a/rules/CodingStyle/ClassNameImport/ShortNameResolver.php b/rules/CodingStyle/ClassNameImport/ShortNameResolver.php index faa62da361c..e4cc03494e9 100644 --- a/rules/CodingStyle/ClassNameImport/ShortNameResolver.php +++ b/rules/CodingStyle/ClassNameImport/ShortNameResolver.php @@ -118,10 +118,17 @@ private function findShortClassLikeNames(File $file): array private function resolveForStmts(array $stmts): array { $shortNamesToFullyQualifiedNames = []; + $docBlockShortNames = []; + // single traversal collects both PHP names and docblock short names, so the file stmts are walked once $this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, function (Node $node) use ( - &$shortNamesToFullyQualifiedNames + &$shortNamesToFullyQualifiedNames, + &$docBlockShortNames ): null { + foreach ($this->resolveDocBlockShortNames($node) as $docBlockShortName) { + $docBlockShortNames[] = $docBlockShortName; + } + // class name is used! if ($node instanceof ClassLike && $node->name instanceof Identifier) { $fullyQualifiedName = $this->nodeNameResolver->getName($node); @@ -152,54 +159,46 @@ private function resolveForStmts(array $stmts): array return null; }); - $docBlockShortNamesToFullyQualifiedNames = $this->resolveFromStmtsDocBlocks($stmts); + $docBlockShortNamesToFullyQualifiedNames = $this->fqnizeShortNames($docBlockShortNames, $stmts); /** @var array $result */ $result = [...$shortNamesToFullyQualifiedNames, ...$docBlockShortNamesToFullyQualifiedNames]; return $result; } /** - * @param Stmt[] $stmts - * @return array + * @return string[] */ - private function resolveFromStmtsDocBlocks(array $stmts): array + private function resolveDocBlockShortNames(Node $node): array { - $shortNames = []; - $this->simpleCallableNodeTraverser->traverseNodesWithCallable($stmts, function (Node $node) use ( - &$shortNames - ): null { - // speed up for nodes that are - $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); - if (! $phpDocInfo instanceof PhpDocInfo) { - return null; - } - - $phpDocNodeTraverser = new PhpDocNodeTraverser(); - $phpDocNodeTraverser->traverseWithCallable( - $phpDocInfo->getPhpDocNode(), - '', - static function ($node) use (&$shortNames): null { - if ($node instanceof PhpDocTagNode) { - $shortName = trim($node->name, '@'); - if (ucfirst($shortName) === $shortName) { - $shortNames[] = $shortName; - } - - return null; - } + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + if (! $phpDocInfo instanceof PhpDocInfo) { + return []; + } - if ($node instanceof IdentifierTypeNode) { - $shortNames[] = $node->name; + $shortNames = []; + $phpDocNodeTraverser = new PhpDocNodeTraverser(); + $phpDocNodeTraverser->traverseWithCallable( + $phpDocInfo->getPhpDocNode(), + '', + static function ($node) use (&$shortNames): null { + if ($node instanceof PhpDocTagNode) { + $shortName = trim($node->name, '@'); + if (ucfirst($shortName) === $shortName) { + $shortNames[] = $shortName; } return null; } - ); - return null; - }); + if ($node instanceof IdentifierTypeNode) { + $shortNames[] = $node->name; + } + + return null; + } + ); - return $this->fqnizeShortNames($shortNames, $stmts); + return $shortNames; } /**