12
12
use PHPStan \DependencyInjection \ProjectConfigHelper ;
13
13
use PHPStan \File \CouldNotReadFileException ;
14
14
use PHPStan \File \FileFinder ;
15
+ use PHPStan \File \FileHelper ;
15
16
use PHPStan \File \FileWriter ;
16
17
use PHPStan \Internal \ComposerHelper ;
17
18
use PHPStan \PhpDoc \StubFilesProvider ;
34
35
use function sha1_file ;
35
36
use function sort ;
36
37
use function sprintf ;
38
+ use function str_starts_with ;
37
39
use function time ;
38
40
use function unlink ;
39
41
use function var_export ;
@@ -62,6 +64,7 @@ public function __construct(
62
64
private FileFinder $ scanFileFinder ,
63
65
private ReflectionProvider $ reflectionProvider ,
64
66
private StubFilesProvider $ stubFilesProvider ,
67
+ private FileHelper $ fileHelper ,
65
68
private string $ cacheFilePath ,
66
69
private array $ analysedPaths ,
67
70
private array $ composerAutoloaderProjectPaths ,
@@ -85,21 +88,21 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
85
88
if ($ output ->isDebug ()) {
86
89
$ output ->writeLineFormatted ('Result cache not used because of debug mode. ' );
87
90
}
88
- return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], []);
91
+ return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], [], [] );
89
92
}
90
93
if ($ onlyFiles ) {
91
94
if ($ output ->isDebug ()) {
92
95
$ output ->writeLineFormatted ('Result cache not used because only files were passed as analysed paths. ' );
93
96
}
94
- return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], []);
97
+ return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], [], [] );
95
98
}
96
99
97
100
$ cacheFilePath = $ this ->cacheFilePath ;
98
101
if (!is_file ($ cacheFilePath )) {
99
102
if ($ output ->isDebug ()) {
100
103
$ output ->writeLineFormatted ('Result cache not used because the cache file does not exist. ' );
101
104
}
102
- return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], []);
105
+ return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], [], [] );
103
106
}
104
107
105
108
try {
@@ -111,7 +114,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
111
114
112
115
@unlink ($ cacheFilePath );
113
116
114
- return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], []);
117
+ return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], [], [] );
115
118
}
116
119
117
120
if (!is_array ($ data )) {
@@ -120,7 +123,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
120
123
$ output ->writeLineFormatted ('Result cache not used because the cache file is corrupted. ' );
121
124
}
122
125
123
- return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], []);
126
+ return new ResultCache ($ allAnalysedFiles , true , time (), $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray ), [], [], [], [], [], [] );
124
127
}
125
128
126
129
$ meta = $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray );
@@ -129,23 +132,30 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
129
132
$ diffs = $ this ->getMetaKeyDifferences ($ data ['meta ' ], $ meta );
130
133
$ output ->writeLineFormatted ('Result cache not used because the metadata do not match: ' . implode (', ' , $ diffs ));
131
134
}
132
- return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], [], [], []);
135
+ return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], [], [], [], [] );
133
136
}
134
137
135
138
if (time () - $ data ['lastFullAnalysisTime ' ] >= 60 * 60 * 24 * 7 ) {
136
139
if ($ output ->isDebug ()) {
137
140
$ output ->writeLineFormatted ('Result cache not used because it \'s more than 7 days since last full analysis. ' );
138
141
}
139
142
// 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 , [], [], [], [], [], [] );
141
144
}
142
145
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
+ }
144
154
if (!is_file ($ extensionFile )) {
145
155
if ($ output ->isDebug ()) {
146
156
$ output ->writeLineFormatted (sprintf ('Result cache not used because extension file %s was not found. ' , $ extensionFile ));
147
157
}
148
- return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], [], [], []);
158
+ return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], [], [], [], [] );
149
159
}
150
160
151
161
if ($ this ->getFileHash ($ extensionFile ) === $ fileHash ) {
@@ -156,7 +166,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
156
166
$ output ->writeLineFormatted (sprintf ('Result cache not used because extension file %s hash does not match. ' , $ extensionFile ));
157
167
}
158
168
159
- return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], [], [], []);
169
+ return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], [], [], [], [] );
160
170
}
161
171
162
172
$ invertedDependencies = $ data ['dependencies ' ];
@@ -257,7 +267,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
257
267
}
258
268
}
259
269
260
- return new ResultCache (array_unique ($ filesToAnalyse ), false , $ data ['lastFullAnalysisTime ' ], $ meta , $ filteredErrors , $ filteredLocallyIgnoredErrors , $ filteredCollectedData , $ invertedDependenciesToReturn , $ filteredExportedNodes );
270
+ return new ResultCache (array_unique ($ filesToAnalyse ), false , $ data ['lastFullAnalysisTime ' ], $ meta , $ filteredErrors , $ filteredLocallyIgnoredErrors , $ filteredCollectedData , $ invertedDependenciesToReturn , $ filteredExportedNodes, $ data [ ' projectExtensionFiles ' ] );
261
271
}
262
272
263
273
/**
@@ -362,7 +372,11 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
362
372
}
363
373
364
374
$ meta = $ resultCache ->getMeta ();
365
- $ doSave = function (array $ errorsByFile , array $ locallyIgnoredErrorsByFile , $ collectedDataByFile , ?array $ dependencies , array $ exportedNodes ) use ($ internalErrors , $ resultCache , $ output , $ onlyFiles , $ meta ): bool {
375
+ $ projectConfigArray = $ meta ['projectConfig ' ];
376
+ if ($ projectConfigArray !== null ) {
377
+ $ meta ['projectConfig ' ] = Neon::encode ($ projectConfigArray );
378
+ }
379
+ $ doSave = function (array $ errorsByFile , $ locallyIgnoredErrorsByFile , $ collectedDataByFile , ?array $ dependencies , array $ exportedNodes , array $ projectExtensionFiles ) use ($ internalErrors , $ resultCache , $ output , $ onlyFiles , $ meta ): bool {
366
380
if ($ onlyFiles ) {
367
381
if ($ output ->isDebug ()) {
368
382
$ output ->writeLineFormatted ('Result cache was not saved because only files were passed as analysed paths. ' );
@@ -397,7 +411,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
397
411
}
398
412
}
399
413
400
- $ this ->save ($ resultCache ->getLastFullAnalysisTime (), $ errorsByFile , $ locallyIgnoredErrorsByFile , $ collectedDataByFile , $ dependencies , $ exportedNodes , $ meta );
414
+ $ this ->save ($ resultCache ->getLastFullAnalysisTime (), $ errorsByFile , $ locallyIgnoredErrorsByFile , $ collectedDataByFile , $ dependencies , $ exportedNodes , $ projectExtensionFiles , $ meta );
401
415
402
416
if ($ output ->isDebug ()) {
403
417
$ output ->writeLineFormatted ('Result cache is saved. ' );
@@ -408,8 +422,12 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
408
422
409
423
if ($ resultCache ->isFullAnalysis ()) {
410
424
$ saved = false ;
411
- if ($ save ) {
412
- $ saved = $ doSave ($ freshErrorsByFile , $ freshLocallyIgnoredErrorsByFile , $ freshCollectedDataByFile , $ analyserResult ->getDependencies (), $ analyserResult ->getExportedNodes ());
425
+ if ($ save !== false ) {
426
+ $ projectExtensionFiles = [];
427
+ if ($ analyserResult ->getDependencies () !== null ) {
428
+ $ projectExtensionFiles = $ this ->getProjectExtensionFiles ($ projectConfigArray , $ analyserResult ->getDependencies ());
429
+ }
430
+ $ saved = $ doSave ($ freshErrorsByFile , $ freshLocallyIgnoredErrorsByFile , $ freshCollectedDataByFile , $ analyserResult ->getDependencies (), $ analyserResult ->getExportedNodes (), $ projectExtensionFiles );
413
431
} else {
414
432
if ($ output ->isDebug ()) {
415
433
$ output ->writeLineFormatted ('Result cache was not saved because it was not requested. ' );
@@ -426,8 +444,28 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache
426
444
$ exportedNodes = $ this ->mergeExportedNodes ($ resultCache , $ analyserResult ->getExportedNodes ());
427
445
428
446
$ saved = false ;
429
- if ($ save ) {
430
- $ saved = $ doSave ($ errorsByFile , $ locallyIgnoredErrorsByFile , $ collectedDataByFile , $ dependencies , $ exportedNodes );
447
+ if ($ save !== false ) {
448
+ $ projectExtensionFiles = [];
449
+ foreach ($ resultCache ->getProjectExtensionFiles () as $ file => [$ hash , $ isAnalysed , $ className ]) {
450
+ if ($ isAnalysed ) {
451
+ continue ;
452
+ }
453
+
454
+ // keep the same file hashes from the old run
455
+ // so that the message "When you edit them and re-run PHPStan, the result cache will get stale."
456
+ // keeps being shown on subsequent runs
457
+ $ projectExtensionFiles [$ file ] = [$ hash , false , $ className ];
458
+ }
459
+ if ($ dependencies !== null ) {
460
+ foreach ($ this ->getProjectExtensionFiles ($ projectConfigArray , $ dependencies ) as $ file => [$ hash , $ isAnalysed , $ className ]) {
461
+ if (!$ isAnalysed ) {
462
+ continue ;
463
+ }
464
+
465
+ $ projectExtensionFiles [$ file ] = [$ hash , true , $ className ];
466
+ }
467
+ }
468
+ $ saved = $ doSave ($ errorsByFile , $ locallyIgnoredErrorsByFile , $ collectedDataByFile , $ dependencies , $ exportedNodes , $ projectExtensionFiles );
431
469
}
432
470
433
471
$ flatErrors = [];
@@ -583,6 +621,7 @@ private function mergeExportedNodes(ResultCache $resultCache, array $freshExport
583
621
* @param array<string, array<CollectedData>> $collectedData
584
622
* @param array<string, array<string>> $dependencies
585
623
* @param array<string, array<RootExportedNode>> $exportedNodes
624
+ * @param array<string, array{string, bool, string}> $projectExtensionFiles
586
625
* @param mixed[] $meta
587
626
*/
588
627
private function save (
@@ -592,6 +631,7 @@ private function save(
592
631
array $ collectedData ,
593
632
array $ dependencies ,
594
633
array $ exportedNodes ,
634
+ array $ projectExtensionFiles ,
595
635
array $ meta ,
596
636
): void
597
637
{
@@ -639,10 +679,6 @@ private function save(
639
679
ksort ($ exportedNodes );
640
680
641
681
$ file = $ this ->cacheFilePath ;
642
- $ projectConfigArray = $ meta ['projectConfig ' ];
643
- if ($ projectConfigArray !== null ) {
644
- $ meta ['projectConfig ' ] = Neon::encode ($ projectConfigArray );
645
- }
646
682
647
683
FileWriter::write (
648
684
$ file ,
@@ -651,7 +687,7 @@ private function save(
651
687
return [
652
688
'lastFullAnalysisTime' => " . var_export ($ lastFullAnalysisTime , true ) . ",
653
689
'meta' => " . var_export ($ meta , true ) . ",
654
- 'projectExtensionFiles' => " . var_export ($ this -> getProjectExtensionFiles ( $ projectConfigArray , $ dependencies ) , true ) . ",
690
+ 'projectExtensionFiles' => " . var_export ($ projectExtensionFiles , true ) . ",
655
691
'errorsCallback' => static function (): array { return " . var_export ($ errors , true ) . "; },
656
692
'locallyIgnoredErrorsCallback' => static function (): array { return " . var_export ($ locallyIgnoredErrors , true ) . "; },
657
693
'collectedDataCallback' => static function (): array { return " . var_export ($ collectedData , true ) . "; },
@@ -665,13 +701,23 @@ private function save(
665
701
/**
666
702
* @param mixed[]|null $projectConfig
667
703
* @param array<string, mixed> $dependencies
668
- * @return array<string, string>
704
+ * @return array<string, array{ string, bool, string} >
669
705
*/
670
706
private function getProjectExtensionFiles (?array $ projectConfig , array $ dependencies ): array
671
707
{
672
708
$ this ->alreadyProcessed = [];
673
709
$ projectExtensionFiles = [];
674
710
if ($ projectConfig !== null ) {
711
+ $ vendorDirs = [];
712
+ foreach ($ this ->composerAutoloaderProjectPaths as $ autoloaderProjectPath ) {
713
+ $ composer = ComposerHelper::getComposerConfig ($ autoloaderProjectPath );
714
+ if ($ composer === null ) {
715
+ continue ;
716
+ }
717
+ $ vendorDirectory = ComposerHelper::getVendorDirFromComposerConfig ($ autoloaderProjectPath , $ composer );
718
+ $ vendorDirs [] = $ this ->fileHelper ->normalizePath ($ vendorDirectory );
719
+ }
720
+
675
721
$ classes = ProjectConfigHelper::getServiceClassNames ($ projectConfig );
676
722
foreach ($ classes as $ class ) {
677
723
if (!$ this ->reflectionProvider ->hasClass ($ class )) {
@@ -684,13 +730,28 @@ private function getProjectExtensionFiles(?array $projectConfig, array $dependen
684
730
continue ;
685
731
}
686
732
733
+ if (str_starts_with ($ fileName , 'phar:// ' )) {
734
+ continue ;
735
+ }
736
+
687
737
$ allServiceFiles = $ this ->getAllDependencies ($ fileName , $ dependencies );
738
+ if (count ($ allServiceFiles ) === 0 ) {
739
+ $ normalizedFileName = $ this ->fileHelper ->normalizePath ($ fileName );
740
+ foreach ($ vendorDirs as $ vendorDir ) {
741
+ if (str_starts_with ($ normalizedFileName , $ vendorDir )) {
742
+ continue 2 ;
743
+ }
744
+ }
745
+ $ projectExtensionFiles [$ fileName ] = [$ this ->getFileHash ($ fileName ), false , $ class ];
746
+ continue ;
747
+ }
748
+
688
749
foreach ($ allServiceFiles as $ serviceFile ) {
689
750
if (array_key_exists ($ serviceFile , $ projectExtensionFiles )) {
690
751
continue ;
691
752
}
692
753
693
- $ projectExtensionFiles [$ serviceFile ] = $ this ->getFileHash ($ serviceFile );
754
+ $ projectExtensionFiles [$ serviceFile ] = [ $ this ->getFileHash ($ serviceFile ), true , $ class ] ;
694
755
}
695
756
}
696
757
}
0 commit comments