Skip to content

Commit

Permalink
Cache OptimizedDirectorySourceLocator for faster subsequent runs and …
Browse files Browse the repository at this point in the history
…less work needed by child processes
  • Loading branch information
ondrejmirtes committed Aug 24, 2023
1 parent 4e37a2d commit b66210f
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,28 @@
use PHPStan\BetterReflection\Reflector\Reflector;
use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\ConstantNameHelper;
use PHPStan\ShouldNotHappenException;
use function array_key_exists;
use function array_values;
use function count;
use function current;
use function ltrim;
use function php_strip_whitespace;
use function preg_match_all;
use function preg_replace;
use function sprintf;
use function strtolower;

class OptimizedDirectorySourceLocator implements SourceLocator
{

private PhpFileCleaner $cleaner;

private string $extraTypes;

/** @var array<string, string>|null */
private ?array $classToFile = null;

/** @var array<string, string>|null */
private ?array $constantToFile = null;

/** @var array<string, array<int, string>>|null */
private ?array $functionToFiles = null;

/**
* @param string[] $files
* @param array<string, string> $classToFile
* @param array<string, array<int, string>> $functionToFiles
* @param array<string, string> $constantToFile
*/
public function __construct(
private FileNodesFetcher $fileNodesFetcher,
private PhpVersion $phpVersion,
private array $files,
private array $classToFile,
private array $functionToFiles,
private array $constantToFile,
)
{
$this->extraTypes = $this->phpVersion->supportsEnums() ? '|enum' : '';

$this->cleaner = new PhpFileCleaner();
}

public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection
Expand Down Expand Up @@ -141,13 +121,6 @@ private function nodeToReflection(Reflector $reflector, FetchedNode $fetchedNode

private function findFileByClass(string $className): ?string
{
if ($this->classToFile === null) {
$this->init();
if ($this->classToFile === null) {
throw new ShouldNotHappenException();
}
}

if (!array_key_exists($className, $this->classToFile)) {
return null;
}
Expand All @@ -157,13 +130,6 @@ private function findFileByClass(string $className): ?string

private function findFileByConstant(string $constantName): ?string
{
if ($this->constantToFile === null) {
$this->init();
if ($this->constantToFile === null) {
throw new ShouldNotHappenException();
}
}

if (!array_key_exists($constantName, $this->constantToFile)) {
return null;
}
Expand All @@ -176,132 +142,18 @@ private function findFileByConstant(string $constantName): ?string
*/
private function findFilesByFunction(string $functionName): array
{
if ($this->functionToFiles === null) {
$this->init();
if ($this->functionToFiles === null) {
throw new ShouldNotHappenException();
}
}

if (!array_key_exists($functionName, $this->functionToFiles)) {
return [];
}

return $this->functionToFiles[$functionName];
}

private function init(): void
{
$classToFile = [];
$constantToFile = [];
$functionToFiles = [];
foreach ($this->files as $file) {
$symbols = $this->findSymbols($file);
foreach ($symbols['classes'] as $classInFile) {
$classToFile[$classInFile] = $file;
}
foreach ($symbols['constants'] as $constantInFile) {
$constantToFile[$constantInFile] = $file;
}
foreach ($symbols['functions'] as $functionInFile) {
if (!array_key_exists($functionInFile, $functionToFiles)) {
$functionToFiles[$functionInFile] = [];
}
$functionToFiles[$functionInFile][] = $file;
}
}

$this->classToFile = $classToFile;
$this->functionToFiles = $functionToFiles;
$this->constantToFile = $constantToFile;
}

/**
* Inspired by Composer\Autoload\ClassMapGenerator::findClasses()
* @link https://github.com/composer/composer/blob/45d3e133a4691eccb12e9cd6f9dfd76eddc1906d/src/Composer/Autoload/ClassMapGenerator.php#L216
*
* @return array{classes: string[], functions: string[], constants: string[]}
*/
private function findSymbols(string $file): array
{
$contents = @php_strip_whitespace($file);
if ($contents === '') {
return ['classes' => [], 'functions' => [], 'constants' => []];
}

$matchResults = (bool) preg_match_all(sprintf('{\b(?:(?:class|interface|trait|const|function%s)\s)|(?:define\s*\()}i', $this->extraTypes), $contents, $matches);
if (!$matchResults) {
return ['classes' => [], 'functions' => [], 'constants' => []];
}

$contents = $this->cleaner->clean($contents, count($matches[0]));

preg_match_all(sprintf('{
(?:
\b(?<![\$:>])(?:
(?: (?P<type>class|interface|trait%s) \s++ (?P<name>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) )
| (?: (?P<function>function) \s++ (?:&\s*)? (?P<fname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) \s*+ [&\(] )
| (?: (?P<constant>const) \s++ (?P<cname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) \s*+ [^;] )
| (?: (?:\\\)? (?P<define>define) \s*+ \( \s*+ [\'"] (?P<dname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:[\\\\]{1,2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+) )
| (?: (?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] )
)
)
}ix', $this->extraTypes), $contents, $matches);

$classes = [];
$functions = [];
$constants = [];
$namespace = '';

for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') {
$namespace = preg_replace('~\s+~', '', strtolower($matches['nsname'][$i])) . '\\';
continue;
}

if ($matches['function'][$i] !== '') {
$functions[] = strtolower(ltrim($namespace . $matches['fname'][$i], '\\'));
continue;
}

if ($matches['constant'][$i] !== '') {
$constants[] = ConstantNameHelper::normalize(ltrim($namespace . $matches['cname'][$i], '\\'));
}

if ($matches['define'][$i] !== '') {
$constants[] = ConstantNameHelper::normalize($matches['dname'][$i]);
continue;
}

$name = $matches['name'][$i];

// skip anon classes extending/implementing
if ($name === 'extends' || $name === 'implements') {
continue;
}

$classes[] = strtolower(ltrim($namespace . $name, '\\'));
}

return [
'classes' => $classes,
'functions' => $functions,
'constants' => $constants,
];
}

/**
* @return list<Reflection>
*/
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
{
if ($this->classToFile === null || $this->functionToFiles === null || $this->constantToFile === null) {
$this->init();
if ($this->classToFile === null || $this->functionToFiles === null || $this->constantToFile === null) {
throw new ShouldNotHappenException();
}
}

$reflections = [];
if ($identifierType->isClass()) {
foreach ($this->classToFile as $file) {
Expand Down

0 comments on commit b66210f

Please sign in to comment.