Skip to content

Commit

Permalink
Rewrite OptimizedDirectorySourceLocator to use PhpFileCleaner from Co…
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Oct 6, 2021
1 parent b9a0f01 commit b7bd0a9
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 69 deletions.
9 changes: 7 additions & 2 deletions phpstan-baseline.neon
Expand Up @@ -130,14 +130,19 @@ parameters:
path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php

-
message: "#^Only booleans are allowed in a negated boolean, int\\|false given\\.$#"
message: "#^Only booleans are allowed in a negated boolean, int\\|false\\|null given\\.$#"
count: 1
path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php

-
message: "#^Only booleans are allowed in &&, int\\|false given on the right side\\.$#"
count: 1
path: src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php

-
message: "#^Only booleans are allowed in an if condition, int\\|false given\\.$#"
count: 1
path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php
path: src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php

-
message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:__construct\\(\\) has parameter \\$reflection with generic class ReflectionClass but does not specify its types\\: T$#"
Expand Down
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Expand Up @@ -142,4 +142,9 @@ public function supportsReadOnlyProperties(): bool
return $this->versionId >= 80100;
}

public function supportsEnums(): bool
{
return $this->versionId >= 80100;
}

}
Expand Up @@ -8,16 +8,23 @@
use PHPStan\BetterReflection\Reflector\Reflector;
use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use PHPStan\Php\PhpVersion;
use function array_key_exists;

class OptimizedDirectorySourceLocator implements SourceLocator
{

private \PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher $fileNodesFetcher;

private PhpVersion $phpVersion;

private PhpFileCleaner $cleaner;

/** @var string[] */
private array $files;

private string $extraTypes;

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

Expand All @@ -39,11 +46,24 @@ class OptimizedDirectorySourceLocator implements SourceLocator
*/
public function __construct(
FileNodesFetcher $fileNodesFetcher,
PhpVersion $phpVersion,
array $files
)
{
$this->fileNodesFetcher = $fileNodesFetcher;
$this->phpVersion = $phpVersion;
$this->files = $files;

$extraTypes = '';
$extraTypesArray = [];
if ($this->phpVersion->supportsEnums()) {
$extraTypes = '|enum';
$extraTypesArray[] = 'enum';
}

$this->extraTypes = $extraTypes;

$this->cleaner = new PhpFileCleaner(array_merge(['class', 'interface', 'trait', 'function'], $extraTypesArray));
}

public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection
Expand Down Expand Up @@ -197,79 +217,18 @@ private function findSymbols(string $file): array
return ['classes' => [], 'functions' => []];
}

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

// strip heredocs/nowdocs
$heredocRegex = '{
# opening heredoc/nowdoc delimiter (word-chars)
<<<[ \t]*+([\'"]?)(\w++)\\1
# needs to be followed by a newline
(?:\r\n|\n|\r)
# the meat of it, matching line by line until end delimiter
(?:
# a valid line is optional white-space (possessive match) not followed by the end delimiter, then anything goes for the rest of the line
[\t ]*+(?!\\2 \b)[^\r\n]*+
# end of line(s)
[\r\n]++
)*
# end delimiter
[\t ]*+ \\2 (?=\b)
}x';

// run first assuming the file is valid unicode
$contentWithoutHeredoc = preg_replace($heredocRegex . 'u', 'null', $contents);
if ($contentWithoutHeredoc === null) {
// run again without unicode support if the file failed to be parsed
$contents = preg_replace($heredocRegex, 'null', $contents);
} else {
$contents = $contentWithoutHeredoc;
}
unset($contentWithoutHeredoc);

if ($contents === null) {
return ['classes' => [], 'functions' => []];
}
// strip strings
$contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
if ($contents === null) {
return ['classes' => [], 'functions' => []];
}
// strip leading non-php code if needed
if (strpos($contents, '<?') !== 0) {
$contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
if ($contents === null) {
return ['classes' => [], 'functions' => []];
}
if ($replacements === 0) {
return ['classes' => [], 'functions' => []];
}
}
// strip non-php blocks in the file
$contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?><?', $contents);
if ($contents === null) {
return ['classes' => [], 'functions' => []];
}
// strip trailing non-php code if needed
$pos = strrpos($contents, '?>');
if ($pos !== false && strpos(substr($contents, $pos), '<?') === false) {
$contents = substr($contents, 0, $pos);
}
// strip comments if short open tags are in the file
if (preg_match('{(<\?)(?!(php|hh))}i', $contents)) {
$contents = preg_replace('{//.* | /\*(?:[^*]++|\*(?!/))*\*/}x', '', $contents);
if ($contents === null) {
return ['classes' => [], 'functions' => []];
}
}
$contents = $this->cleaner->clean($contents, count($matches[0]));

preg_match_all('{
preg_match_all(sprintf('{
(?:
\b(?<![\$:>])(?P<type>class|interface|trait|function) \s++ (?P<byref>&\s*)? (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
\b(?<![\$:>])(?P<type>class|interface|trait|function%s) \s++ (?P<byref>&\s*)? (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
| \b(?<![\$:>])(?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', $contents, $matches);
}ix', $this->extraTypes), $contents, $matches);

$classes = [];
$functions = [];
Expand Down
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Reflection\BetterReflection\SourceLocator;

use PHPStan\File\FileFinder;
use PHPStan\Php\PhpVersion;

class OptimizedDirectorySourceLocatorFactory
{
Expand All @@ -11,16 +12,20 @@ class OptimizedDirectorySourceLocatorFactory

private FileFinder $fileFinder;

public function __construct(FileNodesFetcher $fileNodesFetcher, FileFinder $fileFinder)
private PhpVersion $phpVersion;

public function __construct(FileNodesFetcher $fileNodesFetcher, FileFinder $fileFinder, PhpVersion $phpVersion)
{
$this->fileNodesFetcher = $fileNodesFetcher;
$this->fileFinder = $fileFinder;
$this->phpVersion = $phpVersion;
}

public function createByDirectory(string $directory): OptimizedDirectorySourceLocator
{
return new OptimizedDirectorySourceLocator(
$this->fileNodesFetcher,
$this->phpVersion,
$this->fileFinder->findFiles([$directory])->getFiles()
);
}
Expand All @@ -33,6 +38,7 @@ public function createByFiles(array $files): OptimizedDirectorySourceLocator
{
return new OptimizedDirectorySourceLocator(
$this->fileNodesFetcher,
$this->phpVersion,
$files
);
}
Expand Down

0 comments on commit b7bd0a9

Please sign in to comment.