Skip to content

Commit b7bd0a9

Browse files
committed
Rewrite OptimizedDirectorySourceLocator to use PhpFileCleaner from Composer
See composer/composer#10107
1 parent b9a0f01 commit b7bd0a9

File tree

5 files changed

+262
-69
lines changed

5 files changed

+262
-69
lines changed

phpstan-baseline.neon

+7-2
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,19 @@ parameters:
130130
path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php
131131

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

137+
-
138+
message: "#^Only booleans are allowed in &&, int\\|false given on the right side\\.$#"
139+
count: 1
140+
path: src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php
141+
137142
-
138143
message: "#^Only booleans are allowed in an if condition, int\\|false given\\.$#"
139144
count: 1
140-
path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php
145+
path: src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php
141146

142147
-
143148
message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:__construct\\(\\) has parameter \\$reflection with generic class ReflectionClass but does not specify its types\\: T$#"

src/Php/PhpVersion.php

+5
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,9 @@ public function supportsReadOnlyProperties(): bool
142142
return $this->versionId >= 80100;
143143
}
144144

145+
public function supportsEnums(): bool
146+
{
147+
return $this->versionId >= 80100;
148+
}
149+
145150
}

src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php

+25-66
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,23 @@
88
use PHPStan\BetterReflection\Reflector\Reflector;
99
use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
1010
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
11+
use PHPStan\Php\PhpVersion;
1112
use function array_key_exists;
1213

1314
class OptimizedDirectorySourceLocator implements SourceLocator
1415
{
1516

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

19+
private PhpVersion $phpVersion;
20+
21+
private PhpFileCleaner $cleaner;
22+
1823
/** @var string[] */
1924
private array $files;
2025

26+
private string $extraTypes;
27+
2128
/** @var array<string, string>|null */
2229
private ?array $classToFile = null;
2330

@@ -39,11 +46,24 @@ class OptimizedDirectorySourceLocator implements SourceLocator
3946
*/
4047
public function __construct(
4148
FileNodesFetcher $fileNodesFetcher,
49+
PhpVersion $phpVersion,
4250
array $files
4351
)
4452
{
4553
$this->fileNodesFetcher = $fileNodesFetcher;
54+
$this->phpVersion = $phpVersion;
4655
$this->files = $files;
56+
57+
$extraTypes = '';
58+
$extraTypesArray = [];
59+
if ($this->phpVersion->supportsEnums()) {
60+
$extraTypes = '|enum';
61+
$extraTypesArray[] = 'enum';
62+
}
63+
64+
$this->extraTypes = $extraTypes;
65+
66+
$this->cleaner = new PhpFileCleaner(array_merge(['class', 'interface', 'trait', 'function'], $extraTypesArray));
4767
}
4868

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

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

204-
// strip heredocs/nowdocs
205-
$heredocRegex = '{
206-
# opening heredoc/nowdoc delimiter (word-chars)
207-
<<<[ \t]*+([\'"]?)(\w++)\\1
208-
# needs to be followed by a newline
209-
(?:\r\n|\n|\r)
210-
# the meat of it, matching line by line until end delimiter
211-
(?:
212-
# a valid line is optional white-space (possessive match) not followed by the end delimiter, then anything goes for the rest of the line
213-
[\t ]*+(?!\\2 \b)[^\r\n]*+
214-
# end of line(s)
215-
[\r\n]++
216-
)*
217-
# end delimiter
218-
[\t ]*+ \\2 (?=\b)
219-
}x';
220-
221-
// run first assuming the file is valid unicode
222-
$contentWithoutHeredoc = preg_replace($heredocRegex . 'u', 'null', $contents);
223-
if ($contentWithoutHeredoc === null) {
224-
// run again without unicode support if the file failed to be parsed
225-
$contents = preg_replace($heredocRegex, 'null', $contents);
226-
} else {
227-
$contents = $contentWithoutHeredoc;
228-
}
229-
unset($contentWithoutHeredoc);
230-
231-
if ($contents === null) {
232-
return ['classes' => [], 'functions' => []];
233-
}
234-
// strip strings
235-
$contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
236-
if ($contents === null) {
237-
return ['classes' => [], 'functions' => []];
238-
}
239-
// strip leading non-php code if needed
240-
if (strpos($contents, '<?') !== 0) {
241-
$contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
242-
if ($contents === null) {
243-
return ['classes' => [], 'functions' => []];
244-
}
245-
if ($replacements === 0) {
246-
return ['classes' => [], 'functions' => []];
247-
}
248-
}
249-
// strip non-php blocks in the file
250-
$contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?><?', $contents);
251-
if ($contents === null) {
252-
return ['classes' => [], 'functions' => []];
253-
}
254-
// strip trailing non-php code if needed
255-
$pos = strrpos($contents, '?>');
256-
if ($pos !== false && strpos(substr($contents, $pos), '<?') === false) {
257-
$contents = substr($contents, 0, $pos);
258-
}
259-
// strip comments if short open tags are in the file
260-
if (preg_match('{(<\?)(?!(php|hh))}i', $contents)) {
261-
$contents = preg_replace('{//.* | /\*(?:[^*]++|\*(?!/))*\*/}x', '', $contents);
262-
if ($contents === null) {
263-
return ['classes' => [], 'functions' => []];
264-
}
265-
}
224+
$contents = $this->cleaner->clean($contents, count($matches[0]));
266225

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

274233
$classes = [];
275234
$functions = [];

src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Reflection\BetterReflection\SourceLocator;
44

55
use PHPStan\File\FileFinder;
6+
use PHPStan\Php\PhpVersion;
67

78
class OptimizedDirectorySourceLocatorFactory
89
{
@@ -11,16 +12,20 @@ class OptimizedDirectorySourceLocatorFactory
1112

1213
private FileFinder $fileFinder;
1314

14-
public function __construct(FileNodesFetcher $fileNodesFetcher, FileFinder $fileFinder)
15+
private PhpVersion $phpVersion;
16+
17+
public function __construct(FileNodesFetcher $fileNodesFetcher, FileFinder $fileFinder, PhpVersion $phpVersion)
1518
{
1619
$this->fileNodesFetcher = $fileNodesFetcher;
1720
$this->fileFinder = $fileFinder;
21+
$this->phpVersion = $phpVersion;
1822
}
1923

2024
public function createByDirectory(string $directory): OptimizedDirectorySourceLocator
2125
{
2226
return new OptimizedDirectorySourceLocator(
2327
$this->fileNodesFetcher,
28+
$this->phpVersion,
2429
$this->fileFinder->findFiles([$directory])->getFiles()
2530
);
2631
}
@@ -33,6 +38,7 @@ public function createByFiles(array $files): OptimizedDirectorySourceLocator
3338
{
3439
return new OptimizedDirectorySourceLocator(
3540
$this->fileNodesFetcher,
41+
$this->phpVersion,
3642
$files
3743
);
3844
}

0 commit comments

Comments
 (0)