Skip to content

Commit b62c5ef

Browse files
committed
Get (not)analysed projectExtensionFiles from result cache
1 parent 6769891 commit b62c5ef

14 files changed

+156
-76
lines changed

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ parameters:
4646
invalidPhpDocTagLine: true
4747
detectDeadTypeInMultiCatch: true
4848
zeroFiles: true
49+
projectServicesNotInAnalysedPaths: true
4950
callUserFunc: true
5051
finalByPhpDoc: true
5152
magicConstantOutOfContext: true

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ parameters:
8181
invalidPhpDocTagLine: false
8282
detectDeadTypeInMultiCatch: false
8383
zeroFiles: false
84+
projectServicesNotInAnalysedPaths: false
8485
callUserFunc: false
8586
finalByPhpDoc: false
8687
magicConstantOutOfContext: false

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ parametersSchema:
7676
invalidPhpDocTagLine: bool()
7777
detectDeadTypeInMultiCatch: bool()
7878
zeroFiles: bool()
79+
projectServicesNotInAnalysedPaths: bool()
7980
callUserFunc: bool()
8081
finalByPhpDoc: bool()
8182
magicConstantOutOfContext: bool()

src/Analyser/ResultCache/ResultCache.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class ResultCache
1616
* @param array<string, array<CollectedData>> $collectedData
1717
* @param array<string, array<string>> $dependencies
1818
* @param array<string, array<RootExportedNode>> $exportedNodes
19+
* @param array<string, array{string, bool, string}> $projectExtensionFiles
1920
*/
2021
public function __construct(
2122
private array $filesToAnalyse,
@@ -26,6 +27,7 @@ public function __construct(
2627
private array $collectedData,
2728
private array $dependencies,
2829
private array $exportedNodes,
30+
private array $projectExtensionFiles,
2931
)
3032
{
3133
}
@@ -88,4 +90,12 @@ public function getExportedNodes(): array
8890
return $this->exportedNodes;
8991
}
9092

93+
/**
94+
* @return array<string, array{string, bool, string}>
95+
*/
96+
public function getProjectExtensionFiles(): array
97+
{
98+
return $this->projectExtensionFiles;
99+
}
100+
91101
}

src/Analyser/ResultCache/ResultCacheManager.php

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPStan\DependencyInjection\ProjectConfigHelper;
1313
use PHPStan\File\CouldNotReadFileException;
1414
use PHPStan\File\FileFinder;
15+
use PHPStan\File\FileHelper;
1516
use PHPStan\File\FileWriter;
1617
use PHPStan\Internal\ComposerHelper;
1718
use PHPStan\PhpDoc\StubFilesProvider;
@@ -34,6 +35,7 @@
3435
use function sha1_file;
3536
use function sort;
3637
use function sprintf;
38+
use function str_starts_with;
3739
use function time;
3840
use function unlink;
3941
use function var_export;
@@ -62,6 +64,7 @@ public function __construct(
6264
private FileFinder $scanFileFinder,
6365
private ReflectionProvider $reflectionProvider,
6466
private StubFilesProvider $stubFilesProvider,
67+
private FileHelper $fileHelper,
6568
private string $cacheFilePath,
6669
private array $analysedPaths,
6770
private array $composerAutoloaderProjectPaths,
@@ -85,21 +88,21 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
8588
if ($output->isDebug()) {
8689
$output->writeLineFormatted('Result cache not used because of debug mode.');
8790
}
88-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], []);
91+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
8992
}
9093
if ($onlyFiles) {
9194
if ($output->isDebug()) {
9295
$output->writeLineFormatted('Result cache not used because only files were passed as analysed paths.');
9396
}
94-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], []);
97+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
9598
}
9699

97100
$cacheFilePath = $this->cacheFilePath;
98101
if (!is_file($cacheFilePath)) {
99102
if ($output->isDebug()) {
100103
$output->writeLineFormatted('Result cache not used because the cache file does not exist.');
101104
}
102-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], []);
105+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
103106
}
104107

105108
try {
@@ -111,7 +114,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
111114

112115
@unlink($cacheFilePath);
113116

114-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], []);
117+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
115118
}
116119

117120
if (!is_array($data)) {
@@ -120,7 +123,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
120123
$output->writeLineFormatted('Result cache not used because the cache file is corrupted.');
121124
}
122125

123-
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], []);
126+
return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], []);
124127
}
125128

126129
$meta = $this->getMeta($allAnalysedFiles, $projectConfigArray);
@@ -129,23 +132,30 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
129132
$diffs = $this->getMetaKeyDifferences($data['meta'], $meta);
130133
$output->writeLineFormatted('Result cache not used because the metadata do not match: ' . implode(', ', $diffs));
131134
}
132-
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], []);
135+
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], []);
133136
}
134137

135138
if (time() - $data['lastFullAnalysisTime'] >= 60 * 60 * 24 * 7) {
136139
if ($output->isDebug()) {
137140
$output->writeLineFormatted('Result cache not used because it\'s more than 7 days since last full analysis.');
138141
}
139142
// run full analysis if the result cache is older than 7 days
140-
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], []);
143+
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], []);
141144
}
142145

143-
foreach ($data['projectExtensionFiles'] as $extensionFile => $fileHash) {
146+
/**
147+
* @var string $fileHash
148+
* @var bool $isAnalysed
149+
*/
150+
foreach ($data['projectExtensionFiles'] as $extensionFile => [$fileHash, $isAnalysed]) {
151+
if (!$isAnalysed) {
152+
continue;
153+
}
144154
if (!is_file($extensionFile)) {
145155
if ($output->isDebug()) {
146156
$output->writeLineFormatted(sprintf('Result cache not used because extension file %s was not found.', $extensionFile));
147157
}
148-
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], []);
158+
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], []);
149159
}
150160

151161
if ($this->getFileHash($extensionFile) === $fileHash) {
@@ -156,7 +166,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
156166
$output->writeLineFormatted(sprintf('Result cache not used because extension file %s hash does not match.', $extensionFile));
157167
}
158168

159-
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], []);
169+
return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], []);
160170
}
161171

162172
$invertedDependencies = $data['dependencies'];
@@ -252,7 +262,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
252262
}
253263
}
254264

255-
return new ResultCache(array_unique($filesToAnalyse), false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $filteredCollectedData, $invertedDependenciesToReturn, $filteredExportedNodes);
265+
return new ResultCache(array_unique($filesToAnalyse), false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $filteredCollectedData, $invertedDependenciesToReturn, $filteredExportedNodes, $data['projectExtensionFiles']);
256266
}
257267

258268
/**
@@ -355,7 +365,11 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
355365
}
356366

357367
$meta = $resultCache->getMeta();
358-
$doSave = function (array $errorsByFile, $collectedDataByFile, ?array $dependencies, array $exportedNodes) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool {
368+
$projectConfigArray = $meta['projectConfig'];
369+
if ($projectConfigArray !== null) {
370+
$meta['projectConfig'] = Neon::encode($projectConfigArray);
371+
}
372+
$doSave = function (array $errorsByFile, $collectedDataByFile, ?array $dependencies, array $exportedNodes, array $projectExtensionFiles) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool {
359373
if ($onlyFiles) {
360374
if ($output->isDebug()) {
361375
$output->writeLineFormatted('Result cache was not saved because only files were passed as analysed paths.');
@@ -390,7 +404,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
390404
}
391405
}
392406

393-
$this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $collectedDataByFile, $dependencies, $exportedNodes, $meta);
407+
$this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $collectedDataByFile, $dependencies, $exportedNodes, $projectExtensionFiles, $meta);
394408

395409
if ($output->isDebug()) {
396410
$output->writeLineFormatted('Result cache is saved.');
@@ -402,7 +416,11 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
402416
if ($resultCache->isFullAnalysis()) {
403417
$saved = false;
404418
if ($save !== false) {
405-
$saved = $doSave($freshErrorsByFile, $freshCollectedDataByFile, $analyserResult->getDependencies(), $analyserResult->getExportedNodes());
419+
$projectExtensionFiles = [];
420+
if ($analyserResult->getDependencies() !== null) {
421+
$projectExtensionFiles = $this->getProjectExtensionFiles($projectConfigArray, $analyserResult->getDependencies());
422+
}
423+
$saved = $doSave($freshErrorsByFile, $freshCollectedDataByFile, $analyserResult->getDependencies(), $analyserResult->getExportedNodes(), $projectExtensionFiles);
406424
} else {
407425
if ($output->isDebug()) {
408426
$output->writeLineFormatted('Result cache was not saved because it was not requested.');
@@ -419,7 +437,27 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
419437

420438
$saved = false;
421439
if ($save !== false) {
422-
$saved = $doSave($errorsByFile, $collectedDataByFile, $dependencies, $exportedNodes);
440+
$projectExtensionFiles = [];
441+
foreach ($resultCache->getProjectExtensionFiles() as $file => [$hash, $isAnalysed, $className]) {
442+
if ($isAnalysed) {
443+
continue;
444+
}
445+
446+
// keep the same file hashes from the old run
447+
// so that the message "When you edit them and re-run PHPStan, the result cache will get stale."
448+
// keeps being shown on subsequent runs
449+
$projectExtensionFiles[$file] = [$hash, false, $className];
450+
}
451+
if ($dependencies !== null) {
452+
foreach ($this->getProjectExtensionFiles($projectConfigArray, $dependencies) as $file => [$hash, $isAnalysed, $className]) {
453+
if (!$isAnalysed) {
454+
continue;
455+
}
456+
457+
$projectExtensionFiles[$file] = [$hash, true, $className];
458+
}
459+
}
460+
$saved = $doSave($errorsByFile, $collectedDataByFile, $dependencies, $exportedNodes, $projectExtensionFiles);
423461
}
424462

425463
$flatErrors = [];
@@ -548,6 +586,7 @@ private function mergeExportedNodes(ResultCache $resultCache, array $freshExport
548586
* @param array<string, array<CollectedData>> $collectedData
549587
* @param array<string, array<string>> $dependencies
550588
* @param array<string, array<RootExportedNode>> $exportedNodes
589+
* @param array<string, array{string, bool, string}> $projectExtensionFiles
551590
* @param mixed[] $meta
552591
*/
553592
private function save(
@@ -556,6 +595,7 @@ private function save(
556595
array $collectedData,
557596
array $dependencies,
558597
array $exportedNodes,
598+
array $projectExtensionFiles,
559599
array $meta,
560600
): void
561601
{
@@ -602,10 +642,6 @@ private function save(
602642
ksort($exportedNodes);
603643

604644
$file = $this->cacheFilePath;
605-
$projectConfigArray = $meta['projectConfig'];
606-
if ($projectConfigArray !== null) {
607-
$meta['projectConfig'] = Neon::encode($projectConfigArray);
608-
}
609645

610646
FileWriter::write(
611647
$file,
@@ -614,7 +650,7 @@ private function save(
614650
return [
615651
'lastFullAnalysisTime' => " . var_export($lastFullAnalysisTime, true) . ",
616652
'meta' => " . var_export($meta, true) . ",
617-
'projectExtensionFiles' => " . var_export($this->getProjectExtensionFiles($projectConfigArray, $dependencies), true) . ",
653+
'projectExtensionFiles' => " . var_export($projectExtensionFiles, true) . ",
618654
'errorsCallback' => static function (): array { return " . var_export($errors, true) . "; },
619655
'collectedDataCallback' => static function (): array { return " . var_export($collectedData, true) . "; },
620656
'dependencies' => " . var_export($invertedDependencies, true) . ",
@@ -627,13 +663,23 @@ private function save(
627663
/**
628664
* @param mixed[]|null $projectConfig
629665
* @param array<string, mixed> $dependencies
630-
* @return array<string, string>
666+
* @return array<string, array{string, bool, string}>
631667
*/
632668
private function getProjectExtensionFiles(?array $projectConfig, array $dependencies): array
633669
{
634670
$this->alreadyProcessed = [];
635671
$projectExtensionFiles = [];
636672
if ($projectConfig !== null) {
673+
$vendorDirs = [];
674+
foreach ($this->composerAutoloaderProjectPaths as $autoloaderProjectPath) {
675+
$composer = ComposerHelper::getComposerConfig($autoloaderProjectPath);
676+
if ($composer === null) {
677+
continue;
678+
}
679+
$vendorDirectory = ComposerHelper::getVendorDirFromComposerConfig($autoloaderProjectPath, $composer);
680+
$vendorDirs[] = $this->fileHelper->normalizePath($vendorDirectory);
681+
}
682+
637683
$classes = ProjectConfigHelper::getServiceClassNames($projectConfig);
638684
foreach ($classes as $class) {
639685
if (!$this->reflectionProvider->hasClass($class)) {
@@ -647,12 +693,23 @@ private function getProjectExtensionFiles(?array $projectConfig, array $dependen
647693
}
648694

649695
$allServiceFiles = $this->getAllDependencies($fileName, $dependencies);
696+
if (count($allServiceFiles) === 0) {
697+
$normalizedFileName = $this->fileHelper->normalizePath($fileName);
698+
foreach ($vendorDirs as $vendorDir) {
699+
if (str_starts_with($normalizedFileName, $vendorDir)) {
700+
continue 2;
701+
}
702+
}
703+
$projectExtensionFiles[$fileName] = [$this->getFileHash($fileName), false, $class];
704+
continue;
705+
}
706+
650707
foreach ($allServiceFiles as $serviceFile) {
651708
if (array_key_exists($serviceFile, $projectExtensionFiles)) {
652709
continue;
653710
}
654711

655-
$projectExtensionFiles[$serviceFile] = $this->getFileHash($serviceFile);
712+
$projectExtensionFiles[$serviceFile] = [$this->getFileHash($serviceFile), true, $class];
656713
}
657714
}
658715
}

src/Command/AnalyseApplication.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323
use Symfony\Component\Console\Input\InputInterface;
2424
use function array_merge;
2525
use function count;
26+
use function is_file;
2627
use function is_string;
2728
use function memory_get_peak_usage;
2829
use function microtime;
30+
use function sha1_file;
2931
use function sprintf;
3032

3133
class AnalyseApplication
@@ -74,6 +76,7 @@ public function analyse(
7476
if ($errorOutput->isDebug()) {
7577
$errorOutput->writeLineFormatted('Result cache was not saved because of ignoredErrorHelperResult errors.');
7678
}
79+
$changedProjectExtensionFilesOutsideOfAnalysedPaths = [];
7780
} else {
7881
$resultCache = $resultCacheManager->restore($files, $debug, $onlyFiles, $projectConfigArray, $errorOutput);
7982
$intermediateAnalyserResult = $this->runAnalyser(
@@ -114,6 +117,32 @@ public function analyse(
114117
$memoryUsageBytes = $analyserResult->getPeakMemoryUsageBytes();
115118
$isResultCacheUsed = !$resultCache->isFullAnalysis();
116119

120+
$changedProjectExtensionFilesOutsideOfAnalysedPaths = [];
121+
if (
122+
$isResultCacheUsed
123+
&& $resultCacheResult->isSaved()
124+
&& !$onlyFiles
125+
&& $projectConfigArray !== null
126+
) {
127+
foreach ($resultCache->getProjectExtensionFiles() as $file => [$hash, $isAnalysed, $className]) {
128+
if ($isAnalysed) {
129+
continue;
130+
}
131+
132+
if (!is_file($file)) {
133+
$changedProjectExtensionFilesOutsideOfAnalysedPaths[$file] = $className;
134+
continue;
135+
}
136+
137+
$newHash = sha1_file($file);
138+
if ($newHash === $hash) {
139+
continue;
140+
}
141+
142+
$changedProjectExtensionFilesOutsideOfAnalysedPaths[$file] = $className;
143+
}
144+
}
145+
117146
if (!$hasInternalErrors) {
118147
foreach ($this->getCollectedDataErrors($analyserResult->getCollectedData(), $onlyFiles) as $error) {
119148
$errors[] = $error;
@@ -150,6 +179,7 @@ public function analyse(
150179
$savedResultCache,
151180
$memoryUsageBytes,
152181
$isResultCacheUsed,
182+
$changedProjectExtensionFilesOutsideOfAnalysedPaths,
153183
);
154184
}
155185

0 commit comments

Comments
 (0)