@@ -22,18 +22,29 @@ class VariableAnalysisSniff implements Sniff {
2222 protected $ currentFile = null ;
2323
2424 /**
25- * A list of scopes encountered so far and the variables within them.
25+ * An associative array of scopes for variables encountered so far and the variables within them.
26+ *
27+ * Each scope is keyed by a string of the form `filename:scopeStartIndex` (see `getScopeKey`).
2628 *
2729 * @var ScopeInfo[]
2830 */
2931 private $ scopes = [];
3032
3133 /**
32- * A list of token indices which start scopes and will be used to check for unused variables.
34+ * An associative array of a list of token index pairs which start and end scopes and will be used to check for unused variables.
35+ *
36+ * Each array of scopes is keyed by a string containing the filename (see `getFilename`).
37+ *
38+ * @var ScopeInfo[][]
39+ */
40+ private $ scopeStartEndPairs = [];
41+
42+ /**
43+ * A cache of scope end indices in the current file to improve performance.
3344 *
3445 * @var int[]
3546 */
36- private $ scopeStartIndices = [0 ];
47+ private $ scopeEndIndexCache = [];
3748
3849 /**
3950 * A list of custom functions which pass in variables to be initialized by
@@ -196,30 +207,20 @@ public function process(File $phpcsFile, $stackPtr) {
196207 T_CLOSURE ,
197208 ];
198209
199- $ scopeIndicesThisCloses = array_reduce ($ this ->scopeStartIndices , function ($ found , $ scopeStartIndex ) use ($ phpcsFile , $ stackPtr ) {
200- $ scopeCloserIndex = Helpers::getScopeCloseForScopeOpen ($ phpcsFile , $ scopeStartIndex );
201-
202- if (!$ scopeCloserIndex ) {
203- Helpers::debug ('No scope closer found for scope start ' , $ scopeStartIndex );
204- }
205-
206- if ($ stackPtr === $ scopeCloserIndex ) {
207- $ found [] = $ scopeStartIndex ;
208- }
209- return $ found ;
210- }, []);
211-
212- foreach ($ scopeIndicesThisCloses as $ scopeIndexThisCloses ) {
213- Helpers::debug ('found closing scope at ' , $ stackPtr , 'for scope ' , $ scopeIndexThisCloses );
214- $ this ->processScopeClose ($ phpcsFile , $ scopeIndexThisCloses );
215- }
216-
217210 $ token = $ tokens [$ stackPtr ];
218211
219212 if ($ this ->currentFile !== $ phpcsFile ) {
220213 $ this ->currentFile = $ phpcsFile ;
214+ $ this ->scopeEndIndexCache = [];
221215 }
222216
217+ // Add the global scope
218+ if (empty ($ this ->scopeStartEndPairs [$ this ->getFilename ()])) {
219+ $ this ->recordScopeStartAndEnd ($ phpcsFile , 0 );
220+ }
221+
222+ $ this ->searchForAndProcessClosingScopesAt ($ phpcsFile , $ stackPtr );
223+
223224 if ($ token ['code ' ] === T_VARIABLE ) {
224225 $ this ->processVariable ($ phpcsFile , $ stackPtr );
225226 return ;
@@ -241,11 +242,57 @@ public function process(File $phpcsFile, $stackPtr) {
241242 || FunctionDeclarations::isArrowFunction ($ phpcsFile , $ stackPtr )
242243 ) {
243244 Helpers::debug ('found scope condition ' , $ token );
244- $ this ->scopeStartIndices [] = $ stackPtr ;
245+ $ this ->recordScopeStartAndEnd ( $ phpcsFile , $ stackPtr) ;
245246 return ;
246247 }
247248 }
248249
250+ /**
251+ * @param File $phpcsFile
252+ * @param int $scopeStartIndex
253+ *
254+ * @return void
255+ */
256+ private function recordScopeStartAndEnd ($ phpcsFile , $ scopeStartIndex ) {
257+ $ scopeEndIndex = Helpers::getScopeCloseForScopeOpen ($ phpcsFile , $ scopeStartIndex );
258+ $ filename = $ this ->getFilename ();
259+ if (! isset ($ this ->scopeStartEndPairs [$ filename ])) {
260+ $ this ->scopeStartEndPairs [$ filename ] = [];
261+ }
262+ Helpers::debug ('recording scope for file ' , $ filename , 'start/end ' , $ scopeStartIndex , $ scopeEndIndex );
263+ $ this ->scopeStartEndPairs [$ filename ][] = new ScopeInfo ($ scopeStartIndex , $ scopeEndIndex );
264+ $ this ->scopeEndIndexCache [] = $ scopeEndIndex ;
265+ }
266+
267+ /**
268+ * @param File $phpcsFile
269+ * @param int $stackPtr
270+ *
271+ * @return void
272+ */
273+ private function searchForAndProcessClosingScopesAt ($ phpcsFile , $ stackPtr ) {
274+ if (! in_array ($ stackPtr , $ this ->scopeEndIndexCache , true )) {
275+ return ;
276+ }
277+ $ scopePairsForFile = isset ($ this ->scopeStartEndPairs [$ this ->getFilename ()]) ? $ this ->scopeStartEndPairs [$ this ->getFilename ()] : [];
278+ $ scopeIndicesThisCloses = array_reduce ($ scopePairsForFile , function ($ found , $ scope ) use ($ stackPtr ) {
279+ if (! is_int ($ scope ->scopeEndIndex )) {
280+ Helpers::debug ('No scope closer found for scope start ' , $ scope ->scopeStartIndex );
281+ return $ found ;
282+ }
283+
284+ if ($ stackPtr === $ scope ->scopeEndIndex ) {
285+ $ found [] = $ scope ;
286+ }
287+ return $ found ;
288+ }, []);
289+
290+ foreach ($ scopeIndicesThisCloses as $ scopeIndexThisCloses ) {
291+ Helpers::debug ('found closing scope at index ' , $ stackPtr , 'for scopes starting at: ' , $ scopeIndexThisCloses );
292+ $ this ->processScopeClose ($ phpcsFile , $ scopeIndexThisCloses ->scopeStartIndex );
293+ }
294+ }
295+
249296 /**
250297 * @param File $phpcsFile
251298 * @param int $stackPtr
@@ -272,7 +319,14 @@ protected function isGetDefinedVars(File $phpcsFile, $stackPtr) {
272319 * @return string
273320 */
274321 protected function getScopeKey ($ currScope ) {
275- return ($ this ->currentFile ? $ this ->currentFile ->getFilename () : 'unknown file ' ) . ': ' . $ currScope ;
322+ return $ this ->getFilename () . ': ' . $ currScope ;
323+ }
324+
325+ /**
326+ * @return string
327+ */
328+ protected function getFilename () {
329+ return $ this ->currentFile ? $ this ->currentFile ->getFilename () : 'unknown file ' ;
276330 }
277331
278332 /**
@@ -332,7 +386,7 @@ protected function getOrCreateVariableInfo($varName, $currScope) {
332386 if (isset ($ this ->ignoreUnusedRegexp ) && preg_match ($ this ->ignoreUnusedRegexp , $ varName ) === 1 ) {
333387 $ scopeInfo ->variables [$ varName ]->ignoreUnused = true ;
334388 }
335- if ($ scopeInfo ->owner === 0 && $ this ->allowUndefinedVariablesInFileScope ) {
389+ if ($ scopeInfo ->scopeStartIndex === 0 && $ this ->allowUndefinedVariablesInFileScope ) {
336390 $ scopeInfo ->variables [$ varName ]->ignoreUndefined = true ;
337391 }
338392 if (in_array ($ varName , $ validUndefinedVariableNames )) {
@@ -530,7 +584,7 @@ protected function markAllVariablesRead(File $phpcsFile, $stackPtr) {
530584 $ count = count ($ scopeInfo ->variables );
531585 Helpers::debug ("marking all $ count variables in scope as read " );
532586 foreach ($ scopeInfo ->variables as $ varInfo ) {
533- $ this ->markVariableRead ($ varInfo ->name , $ stackPtr , $ scopeInfo ->owner );
587+ $ this ->markVariableRead ($ varInfo ->name , $ stackPtr , $ scopeInfo ->scopeStartIndex );
534588 }
535589 }
536590
0 commit comments