66
77use Closure ;
88use Pest \TypeCoverage \Support \Cache ;
9+ use PHPStan \Analyser \Error ;
910use Pokio \Environment ;
1011
1112/**
@@ -22,22 +23,107 @@ final class Analyser
2223 public static function analyse (array $ files , Closure $ postProcessedFile , Closure $ onProcessedFile , Cache $ cache ): void
2324 {
2425 $ testCase = new TestCaseForTypeCoverage ('dummy ' );
25- $ chunkOfFiles = Environment::supportsFork () ? array_chunk ($ files , Environment::maxProcesses ()) : [$ files ];
26- $ promisses = [];
2726
28- foreach ($ chunkOfFiles as $ files ) {
29- $ promisses [] = async (function () use ($ cache , $ files , $ testCase , $ onProcessedFile ) {
27+ if (count ($ files ) === 0 ) {
28+ return ;
29+ }
30+
31+ $ filesTouched = [];
32+ $ filesInCache = [];
33+
34+ foreach ($ files as $ file ) {
35+ if ($ cache ->has ($ file )) {
36+ $ filesInCache [] = $ file ;
37+ } else {
38+ $ filesTouched [] = $ file ;
39+ }
40+ }
41+
42+ unset($ files );
43+
44+ self ::analyseChunks (
45+ [$ filesInCache ],
46+ $ testCase ,
47+ $ postProcessedFile ,
48+ $ onProcessedFile ,
49+ $ cache ,
50+ false ,
51+ );
52+
53+ // next, if we don't have touched files, we can return early
54+
55+ if (count ($ filesTouched ) === 0 ) {
56+ return ;
57+ }
58+
59+ // if not, lets warm up the cache with the first file:
60+
61+ $ firstFile = array_shift ($ filesTouched );
62+ self ::analyseChunks (
63+ [[$ firstFile ]],
64+ $ testCase ,
65+ $ postProcessedFile ,
66+ $ onProcessedFile ,
67+ $ cache ,
68+ false ,
69+ );
70+
71+ $ maxProcesses = Environment::maxProcesses () / 3 ;
72+ $ maxProcesses = max (1 , $ maxProcesses );
73+
74+ $ chunkOfFiles = array_fill (0 , $ maxProcesses , []);
75+ foreach (array_values ($ filesTouched ) as $ i => $ file ) {
76+ $ chunkOfFiles [$ i % $ maxProcesses ][] = $ file ;
77+ }
78+
79+ $ chunkOfFiles = array_values (
80+ array_filter ($ chunkOfFiles , static fn (array $ chunk ) => count ($ chunk ) > 0 ),
81+ );
82+
83+ self ::analyseChunks (
84+ $ chunkOfFiles ,
85+ $ testCase ,
86+ $ postProcessedFile ,
87+ $ onProcessedFile ,
88+ $ cache
89+ );
90+ }
91+
92+ /**
93+ * Analyse the chunks of files.
94+ */
95+ private static function analyseChunks (
96+ array $ chunks ,
97+ TestCaseForTypeCoverage $ testCase ,
98+ Closure $ postProcessedFile ,
99+ Closure $ onProcessedFile ,
100+ Cache $ cache ,
101+ bool $ useAsync = true ,
102+ ): void {
103+ $ promises = [];
104+
105+ if ($ useAsync === false ) {
106+ pokio ()->useSync ();
107+ } else {
108+ if (Environment::supportsFork () && ! isset ($ _ENV ['__PEST_PLUGIN_ENV ' ])) {
109+ pokio ()->useFork ();
110+ }
111+ }
112+
113+ foreach ($ chunks as $ files ) {
114+ $ promises [] = async (function () use ($ files , $ testCase , $ onProcessedFile ) {
115+ $ testCase ->resetIgnoredErrors ();
30116 $ results = [];
31117
32- foreach ($ files as $ file ) {
33- [$ file , $ errors , $ ignored ] = $ cache ->get ($ file , function () use ($ file , $ testCase ) {
34- $ testCase ->resetIgnoredErrors ();
118+ $ analyserErrors = $ testCase ->gatherAnalyserErrors ($ files );
119+ $ analyserIgnored = $ testCase ->getIgnoredErrors ();
35120
36- $ errors = $ testCase ->gatherAnalyserErrors ([$ file ]);
37- $ ignored = $ testCase ->getIgnoredErrors ();
121+ foreach ($ files as $ file ) {
122+ $ errors = array_filter ($ analyserErrors , static fn (Error $ error ) => $ error ->getFile () === $ file );
123+ $ ignored = array_filter ($ analyserIgnored , static fn (Error $ error ) => $ error ->getFile () === $ file );
38124
39- return [ $ file , $ errors, $ ignored ] ;
40- } );
125+ $ errors = array_values ( $ errors ) ;
126+ $ ignored = array_values ( $ ignored );
41127
42128 $ result = Result::fromPHPStanErrors ($ file , $ errors , $ ignored );
43129
@@ -50,7 +136,7 @@ public static function analyse(array $files, Closure $postProcessedFile, Closure
50136 });
51137 }
52138
53- foreach (await ($ promisses ) as $ results ) {
139+ foreach (await ($ promises ) as $ results ) {
54140 foreach ($ results as $ result ) {
55141 $ postProcessedFile ($ result );
56142 }
0 commit comments