Skip to content

Commit eb2b351

Browse files
authored
Only search for nested arrow functions if necessary (#342)
* Do not search for nested arrow func outside of arrow func * Recursively search for containing arrow function * Guard for no enclosing scope
1 parent c3780f2 commit eb2b351

File tree

1 file changed

+39
-29
lines changed

1 file changed

+39
-29
lines changed

VariableAnalysis/Lib/Helpers.php

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -428,17 +428,20 @@ public static function findVariableScope(File $phpcsFile, $stackPtr, $varName =
428428
$token = $tokens[$stackPtr];
429429
$varName = isset($varName) ? $varName : self::normalizeVarName($token['content']);
430430

431-
$arrowFunctionIndex = self::getContainingArrowFunctionIndex($phpcsFile, $stackPtr);
432-
$isTokenInsideArrowFunctionBody = is_int($arrowFunctionIndex);
433-
if ($isTokenInsideArrowFunctionBody) {
434-
// Get the list of variables defined by the arrow function
435-
// If this matches any of them, the scope is the arrow function,
436-
// otherwise, it uses the enclosing scope.
437-
if ($arrowFunctionIndex) {
438-
$variableNames = self::getVariablesDefinedByArrowFunction($phpcsFile, $arrowFunctionIndex);
439-
self::debug('findVariableScope: looking for', $varName, 'in arrow function variables', $variableNames);
440-
if (in_array($varName, $variableNames, true)) {
441-
return $arrowFunctionIndex;
431+
$enclosingScopeIndex = self::findVariableScopeExceptArrowFunctions($phpcsFile, $stackPtr);
432+
if ($enclosingScopeIndex) {
433+
$arrowFunctionIndex = self::getContainingArrowFunctionIndex($phpcsFile, $stackPtr, $enclosingScopeIndex);
434+
$isTokenInsideArrowFunctionBody = is_int($arrowFunctionIndex);
435+
if ($isTokenInsideArrowFunctionBody) {
436+
// Get the list of variables defined by the arrow function
437+
// If this matches any of them, the scope is the arrow function,
438+
// otherwise, it uses the enclosing scope.
439+
if ($arrowFunctionIndex) {
440+
$variableNames = self::getVariablesDefinedByArrowFunction($phpcsFile, $arrowFunctionIndex);
441+
self::debug('findVariableScope: looking for', $varName, 'in arrow function variables', $variableNames);
442+
if (in_array($varName, $variableNames, true)) {
443+
return $arrowFunctionIndex;
444+
}
442445
}
443446
}
444447
}
@@ -635,53 +638,60 @@ public static function isTokenInsideArrowFunctionDefinition(File $phpcsFile, $st
635638
/**
636639
* @param File $phpcsFile
637640
* @param int $stackPtr
641+
* @param int $enclosingScopeIndex
638642
*
639643
* @return ?int
640644
*/
641-
public static function getContainingArrowFunctionIndex(File $phpcsFile, $stackPtr)
645+
public static function getContainingArrowFunctionIndex(File $phpcsFile, $stackPtr, $enclosingScopeIndex)
642646
{
643-
$arrowFunctionIndex = self::getPreviousArrowFunctionIndex($phpcsFile, $stackPtr);
647+
$arrowFunctionIndex = self::getPreviousArrowFunctionIndex($phpcsFile, $stackPtr, $enclosingScopeIndex);
644648
if (! is_int($arrowFunctionIndex)) {
645649
return null;
646650
}
647651
$arrowFunctionInfo = self::getArrowFunctionOpenClose($phpcsFile, $arrowFunctionIndex);
648652
if (! $arrowFunctionInfo) {
649653
return null;
650654
}
651-
$arrowFunctionScopeStart = $arrowFunctionInfo['scope_opener'];
652-
$arrowFunctionScopeEnd = $arrowFunctionInfo['scope_closer'];
653-
if ($stackPtr > $arrowFunctionScopeStart && $stackPtr < $arrowFunctionScopeEnd) {
655+
656+
// We found the closest arrow function before this token. If the token is
657+
// within the scope of that arrow function, then return it.
658+
if ($stackPtr > $arrowFunctionInfo['scope_opener'] && $stackPtr < $arrowFunctionInfo['scope_closer']) {
654659
return $arrowFunctionIndex;
655660
}
661+
662+
// If the token is after the scope of the closest arrow function, we may
663+
// still be inside the scope of a nested arrow function, so we need to
664+
// search further back until we are certain there are no more arrow
665+
// functions.
666+
if ($stackPtr > $arrowFunctionInfo['scope_closer']) {
667+
return self::getContainingArrowFunctionIndex($phpcsFile, $arrowFunctionIndex, $enclosingScopeIndex);
668+
}
669+
656670
return null;
657671
}
658672

659673
/**
674+
* Move back from the stackPtr to the start of the enclosing scope until we
675+
* find a 'fn' token that starts an arrow function, returning the index of
676+
* that token. Returns null if there are no arrow functions before stackPtr.
677+
*
678+
* Note that this does not guarantee that stackPtr is inside the arrow
679+
* function scope we find!
680+
*
660681
* @param File $phpcsFile
661682
* @param int $stackPtr
683+
* @param int $enclosingScopeIndex
662684
*
663685
* @return ?int
664686
*/
665-
private static function getPreviousArrowFunctionIndex(File $phpcsFile, $stackPtr)
687+
private static function getPreviousArrowFunctionIndex(File $phpcsFile, $stackPtr, $enclosingScopeIndex)
666688
{
667689
$tokens = $phpcsFile->getTokens();
668-
$enclosingScopeIndex = self::findVariableScopeExceptArrowFunctions($phpcsFile, $stackPtr);
669690
for ($index = $stackPtr - 1; $index > $enclosingScopeIndex; $index--) {
670691
$token = $tokens[$index];
671692
if ($token['content'] === 'fn' && self::isArrowFunction($phpcsFile, $index)) {
672693
return $index;
673694
}
674-
// If we find a token that would close an arrow function scope before we
675-
// find a token that would open an arrow function scope, then we've found
676-
// a nested arrow function and we should ignore it, move back before THAT
677-
// arrow function's scope, and continue to search.
678-
$arrowFunctionStartIndex = $phpcsFile->findPrevious([T_FN], $index, $enclosingScopeIndex);
679-
if (is_int($arrowFunctionStartIndex)) {
680-
$openClose = self::getArrowFunctionOpenClose($phpcsFile, $arrowFunctionStartIndex);
681-
if ($openClose && $openClose['scope_closer'] === $index) {
682-
$index = $openClose['scope_opener'];
683-
}
684-
}
685695
}
686696
return null;
687697
}

0 commit comments

Comments
 (0)