Skip to content

Only search for nested arrow functions if necessary #342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 2, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 39 additions & 29 deletions VariableAnalysis/Lib/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -428,17 +428,20 @@ public static function findVariableScope(File $phpcsFile, $stackPtr, $varName =
$token = $tokens[$stackPtr];
$varName = isset($varName) ? $varName : self::normalizeVarName($token['content']);

$arrowFunctionIndex = self::getContainingArrowFunctionIndex($phpcsFile, $stackPtr);
$isTokenInsideArrowFunctionBody = is_int($arrowFunctionIndex);
if ($isTokenInsideArrowFunctionBody) {
// Get the list of variables defined by the arrow function
// If this matches any of them, the scope is the arrow function,
// otherwise, it uses the enclosing scope.
if ($arrowFunctionIndex) {
$variableNames = self::getVariablesDefinedByArrowFunction($phpcsFile, $arrowFunctionIndex);
self::debug('findVariableScope: looking for', $varName, 'in arrow function variables', $variableNames);
if (in_array($varName, $variableNames, true)) {
return $arrowFunctionIndex;
$enclosingScopeIndex = self::findVariableScopeExceptArrowFunctions($phpcsFile, $stackPtr);
if ($enclosingScopeIndex) {
$arrowFunctionIndex = self::getContainingArrowFunctionIndex($phpcsFile, $stackPtr, $enclosingScopeIndex);
$isTokenInsideArrowFunctionBody = is_int($arrowFunctionIndex);
if ($isTokenInsideArrowFunctionBody) {
// Get the list of variables defined by the arrow function
// If this matches any of them, the scope is the arrow function,
// otherwise, it uses the enclosing scope.
if ($arrowFunctionIndex) {
$variableNames = self::getVariablesDefinedByArrowFunction($phpcsFile, $arrowFunctionIndex);
self::debug('findVariableScope: looking for', $varName, 'in arrow function variables', $variableNames);
if (in_array($varName, $variableNames, true)) {
return $arrowFunctionIndex;
}
}
}
}
Expand Down Expand Up @@ -635,53 +638,60 @@ public static function isTokenInsideArrowFunctionDefinition(File $phpcsFile, $st
/**
* @param File $phpcsFile
* @param int $stackPtr
* @param int $enclosingScopeIndex
*
* @return ?int
*/
public static function getContainingArrowFunctionIndex(File $phpcsFile, $stackPtr)
public static function getContainingArrowFunctionIndex(File $phpcsFile, $stackPtr, $enclosingScopeIndex)
{
$arrowFunctionIndex = self::getPreviousArrowFunctionIndex($phpcsFile, $stackPtr);
$arrowFunctionIndex = self::getPreviousArrowFunctionIndex($phpcsFile, $stackPtr, $enclosingScopeIndex);
if (! is_int($arrowFunctionIndex)) {
return null;
}
$arrowFunctionInfo = self::getArrowFunctionOpenClose($phpcsFile, $arrowFunctionIndex);
if (! $arrowFunctionInfo) {
return null;
}
$arrowFunctionScopeStart = $arrowFunctionInfo['scope_opener'];
$arrowFunctionScopeEnd = $arrowFunctionInfo['scope_closer'];
if ($stackPtr > $arrowFunctionScopeStart && $stackPtr < $arrowFunctionScopeEnd) {

// We found the closest arrow function before this token. If the token is
// within the scope of that arrow function, then return it.
if ($stackPtr > $arrowFunctionInfo['scope_opener'] && $stackPtr < $arrowFunctionInfo['scope_closer']) {
return $arrowFunctionIndex;
}

// If the token is after the scope of the closest arrow function, we may
// still be inside the scope of a nested arrow function, so we need to
// search further back until we are certain there are no more arrow
// functions.
if ($stackPtr > $arrowFunctionInfo['scope_closer']) {
return self::getContainingArrowFunctionIndex($phpcsFile, $arrowFunctionIndex, $enclosingScopeIndex);
}

return null;
}

/**
* Move back from the stackPtr to the start of the enclosing scope until we
* find a 'fn' token that starts an arrow function, returning the index of
* that token. Returns null if there are no arrow functions before stackPtr.
*
* Note that this does not guarantee that stackPtr is inside the arrow
* function scope we find!
*
* @param File $phpcsFile
* @param int $stackPtr
* @param int $enclosingScopeIndex
*
* @return ?int
*/
private static function getPreviousArrowFunctionIndex(File $phpcsFile, $stackPtr)
private static function getPreviousArrowFunctionIndex(File $phpcsFile, $stackPtr, $enclosingScopeIndex)
{
$tokens = $phpcsFile->getTokens();
$enclosingScopeIndex = self::findVariableScopeExceptArrowFunctions($phpcsFile, $stackPtr);
for ($index = $stackPtr - 1; $index > $enclosingScopeIndex; $index--) {
$token = $tokens[$index];
if ($token['content'] === 'fn' && self::isArrowFunction($phpcsFile, $index)) {
return $index;
}
// If we find a token that would close an arrow function scope before we
// find a token that would open an arrow function scope, then we've found
// a nested arrow function and we should ignore it, move back before THAT
// arrow function's scope, and continue to search.
$arrowFunctionStartIndex = $phpcsFile->findPrevious([T_FN], $index, $enclosingScopeIndex);
if (is_int($arrowFunctionStartIndex)) {
$openClose = self::getArrowFunctionOpenClose($phpcsFile, $arrowFunctionStartIndex);
if ($openClose && $openClose['scope_closer'] === $index) {
$index = $openClose['scope_opener'];
}
}
}
return null;
}
Expand Down
Loading