src/Fixer/ReturnNotation/ReturnAssignmentFixer.php
private function fixFunction(Tokens $tokens, $functionIndex, $functionOpenIndex, $functionCloseIndex)
    {
        static $riskyKinds = [
            CT::T_DYNAMIC_VAR_BRACE_OPEN, // "$h = ${$g};" case
            T_EVAL,                       // "$c = eval('return $this;');" case
            T_GLOBAL,
            T_INCLUDE,                    // loading additional symbols we cannot analyze here
            T_INCLUDE_ONCE,               // "
            T_REQUIRE,                    // "
            T_REQUIRE_ONCE,               // "
            T_STATIC,
        ];

        $inserted = 0;
        $candidates = [];
        $isRisky = false;

        // go through the function declaration and check if references are passed
        // - check if it will be risky to fix return statements of this function
        for ($index = $functionIndex + 1; $index < $functionOpenIndex; ++$index) {
            if ($tokens[$index]->equals('&')) {
                $isRisky = true;

                break;
            }
        }

        // go through all the tokens of the body of the function:
        // - check if it will be risky to fix return statements of this function
        // - check nested functions; fix when found and update the upper limit + number of inserted token
        // - check for return statements that might be fixed (based on if fixing will be risky, which is only know after analyzing the whole function)

        for ($index = $functionOpenIndex + 1; $index < $functionCloseIndex; ++$index) {
            if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
                $nestedFunctionOpenIndex = $tokens->getNextTokenOfKind($index, ['{', ';']);
                if ($tokens[$nestedFunctionOpenIndex]->equals(';')) { // abstract function
                    $index = $nestedFunctionOpenIndex - 1;

                    continue;
                }

                $nestedFunctionCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nestedFunctionOpenIndex);

                $tokensAdded = $this->fixFunction(
                    $tokens,
                    $index,
                    $nestedFunctionOpenIndex,
                    $nestedFunctionCloseIndex
                );

                $index = $nestedFunctionCloseIndex + $tokensAdded;
                $functionCloseIndex += $tokensAdded;
                $inserted += $tokensAdded;
            }

            if ($isRisky) {
                continue; // don't bother to look into anything else than nested functions as the current is risky already
            }

            if ($tokens[$index]->equals('&')) {
                $isRisky = true;

                continue;
            }

            if ($tokens[$index]->isGivenKind(T_RETURN)) {
                $candidates[] = $index;

                continue;
            }

            // test if there this is anything in the function body that might
            // change global state or indirect changes (like through references, eval, etc.)

            if ($tokens[$index]->isGivenKind($riskyKinds)) {
                $isRisky = true;

                continue;
            }

            if ($tokens[$index]->equals('$')) {
                $nextIndex = $tokens->getNextMeaningfulToken($index);
                if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) {
                    $isRisky = true; // "$$a" case

                    continue;
                }
            }

            if ($this->isSuperGlobal($tokens[$index])) {
                $isRisky = true;

                continue;
            }
        }

        if ($isRisky) {
            return $inserted;
        }

        // fix the candidates in reverse order when applicable
        for ($i = \count($candidates) - 1; $i >= 0; --$i) {
            $index = $candidates[$i];

            // Check if returning only a variable (i.e. not the result of an expression, function call etc.)
            $returnVarIndex = $tokens->getNextMeaningfulToken($index);
            if (!$tokens[$returnVarIndex]->isGivenKind(T_VARIABLE)) {
                continue; // example: "return 1;"
            }

            $endReturnVarIndex = $tokens->getNextMeaningfulToken($returnVarIndex);
            if (!$tokens[$endReturnVarIndex]->equalsAny([';', [T_CLOSE_TAG]])) {
                continue; // example: "return $a + 1;"
            }

            // Check that the variable is assigned just before it is returned
            $assignVarEndIndex = $tokens->getPrevMeaningfulToken($index);
            if (!$tokens[$assignVarEndIndex]->equals(';')) {
                continue; // example: "? return $a;"
            }

            // Note: here we are @ "; return $a;" (or "; return $a ? >")
            $assignVarOperatorIndex = $tokens->getPrevTokenOfKind(
                $assignVarEndIndex,
                ['=', ';', '{', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]
            );

            if (null === $assignVarOperatorIndex || !$tokens[$assignVarOperatorIndex]->equals('=')) {
                continue;
            }

            // Note: here we are @ "= [^;{<? ? >] ; return $a;"
            $assignVarIndex = $tokens->getPrevMeaningfulToken($assignVarOperatorIndex);
            if (!$tokens[$assignVarIndex]->equals($tokens[$returnVarIndex], false)) {
                continue;
            }

            // Note: here we are @ "$a = [^;{<? ? >] ; return $a;"
            $beforeAssignVarIndex = $tokens->getPrevMeaningfulToken($assignVarIndex);
            if (!$tokens[$beforeAssignVarIndex]->equalsAny([';', '{', '}'])) {
                continue;
            }

            // Note: here we are @ "[;{}] $a = [^;{<? ? >] ; return $a;"
            $inserted += $this->simplifyReturnStatement(
                $tokens,
                $assignVarIndex,
                $assignVarOperatorIndex,
                $index,
                $endReturnVarIndex
            );
        }

        return $inserted;
    }
private function fixFunction(Tokens $tokens, $functionIndex, $functionOpenIndex, $functionCloseIndex)
    {
        static $riskyKinds = [
            CT::T_DYNAMIC_VAR_BRACE_OPEN, // "$h = ${$g};" case
            T_EVAL,                       // "$c = eval('return $this;');" case
            T_GLOBAL,
            T_INCLUDE,                    // loading additional symbols we cannot analyze here
            T_INCLUDE_ONCE,               // "
            T_REQUIRE,                    // "
            T_REQUIRE_ONCE,               // "
            T_STATIC,
        ];

        $inserted = 0;
        $candidates = [];
        $isRisky = false;

        // go through the function declaration and check if references are passed
        // - check if it will be risky to fix return statements of this function
        for ($index = $functionIndex + 1; $index < $functionOpenIndex; ++$index) {
            if ($tokens[$index]->equals('&')) {
                $isRisky = true;

                break;
            }
        }

        // go through all the tokens of the body of the function:
        // - check if it will be risky to fix return statements of this function
        // - check nested functions; fix when found and update the upper limit + number of inserted token
        // - check for return statements that might be fixed (based on if fixing will be risky, which is only know after analyzing the whole function)

        for ($index = $functionOpenIndex + 1; $index < $functionCloseIndex; ++$index) {
            if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
                $nestedFunctionOpenIndex = $tokens->getNextTokenOfKind($index, ['{', ';']);
                if ($tokens[$nestedFunctionOpenIndex]->equals(';')) { // abstract function
                    $index = $nestedFunctionOpenIndex - 1;

                    continue;
                }

                $nestedFunctionCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nestedFunctionOpenIndex);

                $tokensAdded = $this->fixFunction(
                    $tokens,
                    $index,
                    $nestedFunctionOpenIndex,
                    $nestedFunctionCloseIndex
                );

                $index = $nestedFunctionCloseIndex + $tokensAdded;
                $functionCloseIndex += $tokensAdded;
                $inserted += $tokensAdded;
            }

            if ($isRisky) {
                continue; // don't bother to look into anything else than nested functions as the current is risky already
            }

            if ($tokens[$index]->equals('&')) {
                $isRisky = true;

                continue;
            }

            if ($tokens[$index]->isGivenKind(T_RETURN)) {
                $candidates[] = $index;

                continue;
            }

            // test if there this is anything in the function body that might
            // change global state or indirect changes (like through references, eval, etc.)

            if ($tokens[$index]->isGivenKind($riskyKinds)) {
                $isRisky = true;

                continue;
            }

            if ($tokens[$index]->equals('$')) {
                $nextIndex = $tokens->getNextMeaningfulToken($index);
                if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) {
                    $isRisky = true; // "$$a" case

                    continue;
                }
            }

            if ($this->isSuperGlobal($tokens[$index])) {
                $isRisky = true;

                continue;
            }
        }

        if ($isRisky) {
            return $inserted;
        }

        // fix the candidates in reverse order when applicable
        for ($i = \count($candidates) - 1; $i >= 0; --$i) {
            $index = $candidates[$i];

            // Check if returning only a variable (i.e. not the result of an expression, function call etc.)
            $returnVarIndex = $tokens->getNextMeaningfulToken($index);
            if (!$tokens[$returnVarIndex]->isGivenKind(T_VARIABLE)) {
                continue; // example: "return 1;"
            }

            $endReturnVarIndex = $tokens->getNextMeaningfulToken($returnVarIndex);
            if (!$tokens[$endReturnVarIndex]->equalsAny([';', [T_CLOSE_TAG]])) {
                continue; // example: "return $a + 1;"
            }

            // Check that the variable is assigned just before it is returned
            $assignVarEndIndex = $tokens->getPrevMeaningfulToken($index);
            if (!$tokens[$assignVarEndIndex]->equals(';')) {
                continue; // example: "? return $a;"
            }

            // Note: here we are @ "; return $a;" (or "; return $a ? >")
            $assignVarOperatorIndex = $tokens->getPrevTokenOfKind(
                $assignVarEndIndex,
                ['=', ';', '{', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]
            );

            if (null === $assignVarOperatorIndex || !$tokens[$assignVarOperatorIndex]->equals('=')) {
                continue;
            }

            // Note: here we are @ "= [^;{<? ? >] ; return $a;"
            $assignVarIndex = $tokens->getPrevMeaningfulToken($assignVarOperatorIndex);
            if (!$tokens[$assignVarIndex]->equals($tokens[$returnVarIndex], false)) {
                continue;
            }

            // Note: here we are @ "$a = [^;{<? ? >] ; return $a;"
            $beforeAssignVarIndex = $tokens->getPrevMeaningfulToken($assignVarIndex);
            if (!$tokens[$beforeAssignVarIndex]->equalsAny([';', '{', '}'])) {
                continue;
            }

            // Note: here we are @ "[;{}] $a = [^;{<? ? >] ; return $a;"
            $inserted += $this->simplifyReturnStatement(
                $tokens,
                $assignVarIndex,
                $assignVarOperatorIndex,
                $index,
                $endReturnVarIndex
            );
        }

        return $inserted;
    }
private function fixFunction ( Tokens $ tokens , $ functionIndex , $ functionOpenIndex , $ functionCloseIndex ) { static $ riskyKinds = [ CT :: T_DYNAMIC_VAR_BRACE_OPEN , // "$h = ${$g};" case T_EVAL , // "$c = eval('return $this;');" case T_GLOBAL , T_INCLUDE , // loading additional symbols we cannot analyze here T_INCLUDE_ONCE , // " T_REQUIRE , // " T_REQUIRE_ONCE , // " T_STATIC , ] ; $ inserted = 0 ; $ candidates = [ ] ; $ isRisky = false ; // go through the function declaration and check if references are passed // - check if it will be risky to fix return statements of this function for ( $ index = $ functionIndex + 1 ; $ index < $ functionOpenIndex ; ++ $ index ) { if ( $ tokens [ $ index ] -> equals ( '&' ) ) { $ isRisky = true ; break ; } } // go through all the tokens of the body of the function: // - check if it will be risky to fix return statements of this function // - check nested functions; fix when found and update the upper limit + number of inserted token // - check for return statements that might be fixed (based on if fixing will be risky, which is only know after analyzing the whole function) for ( $ index = $ functionOpenIndex + 1 ; $ index < $ functionCloseIndex ; ++ $ index ) { if ( $ tokens [ $ index ] -> isGivenKind ( T_FUNCTION ) ) { $ nestedFunctionOpenIndex = $ tokens -> getNextTokenOfKind ( $ index , [ '{' , ';' ] ) ; if ( $ tokens [ $ nestedFunctionOpenIndex ] -> equals ( ';' ) ) { // abstract function $ index = $ nestedFunctionOpenIndex - 1 ; continue ; } $ nestedFunctionCloseIndex = $ tokens -> findBlockEnd ( Tokens :: BLOCK_TYPE_CURLY_BRACE , $ nestedFunctionOpenIndex ) ; $ tokensAdded = $ this -> fixFunction ( $ tokens , $ index , $ nestedFunctionOpenIndex , $ nestedFunctionCloseIndex ) ; $ index = $ nestedFunctionCloseIndex + $ tokensAdded ; $ functionCloseIndex += $ tokensAdded ; $ inserted += $ tokensAdded ; } if ( $ isRisky ) { continue ; // don't bother to look into anything else than nested functions as the current is risky already } if ( $ tokens [ $ index ] -> equals ( '&' ) ) { $ isRisky = true ; continue ; } if ( $ tokens [ $ index ] -> isGivenKind ( T_RETURN ) ) { $ candidates [ ] = $ index ; continue ; } // test if there this is anything in the function body that might // change global state or indirect changes (like through references, eval, etc.) if ( $ tokens [ $ index ] -> isGivenKind ( $ riskyKinds ) ) { $ isRisky = true ; continue ; } if ( $ tokens [ $ index ] -> equals ( '$' ) ) { $ nextIndex = $ tokens -> getNextMeaningfulToken ( $ index ) ; if ( $ tokens [ $ nextIndex ] -> isGivenKind ( T_VARIABLE ) ) { $ isRisky = true ; // "$$a" case continue ; } } if ( $ this -> isSuperGlobal ( $ tokens [ $ index ] ) ) { $ isRisky = true ; continue ; } } if ( $ isRisky ) { return $ inserted ; } // fix the candidates in reverse order when applicable for ( $ i = \ count ( $ candidates ) - 1 ; $ i >= 0 ; -- $ i ) { $ index = $ candidates [ $ i ] ; // Check if returning only a variable (i.e. not the result of an expression, function call etc.) $ returnVarIndex = $ tokens -> getNextMeaningfulToken ( $ index ) ; if ( ! $ tokens [ $ returnVarIndex ] -> isGivenKind ( T_VARIABLE ) ) { continue ; // example: "return 1;" } $ endReturnVarIndex = $ tokens -> getNextMeaningfulToken ( $ returnVarIndex ) ; if ( ! $ tokens [ $ endReturnVarIndex ] -> equalsAny ( [ ';' , [ T_CLOSE_TAG ] ] ) ) { continue ; // example: "return $a + 1;" } // Check that the variable is assigned just before it is returned $ assignVarEndIndex = $ tokens -> getPrevMeaningfulToken ( $ index ) ; if ( ! $ tokens [ $ assignVarEndIndex ] -> equals ( ';' ) ) { continue ; // example: "? return $a;" } // Note: here we are @ "; return $a;" (or "; return $a ? >") $ assignVarOperatorIndex = $ tokens -> getPrevTokenOfKind ( $ assignVarEndIndex , [ '=' , ';' , '{' , [ T_OPEN_TAG ] , [ T_OPEN_TAG_WITH_ECHO ] ] ) ; if ( null === $ assignVarOperatorIndex || ! $ tokens [ $ assignVarOperatorIndex ] -> equals ( '=' ) ) { continue ; } // Note: here we are @ "= [^;{<? ? >] ; return $a;" $ assignVarIndex = $ tokens -> getPrevMeaningfulToken ( $ assignVarOperatorIndex ) ; if ( ! $ tokens [ $ assignVarIndex ] -> equals ( $ tokens [ $ returnVarIndex ] , false ) ) { continue ; } // Note: here we are @ "$a = [^;{<? ? >] ; return $a;" $ beforeAssignVarIndex = $ tokens -> getPrevMeaningfulToken ( $ assignVarIndex ) ; if ( ! $ tokens [ $ beforeAssignVarIndex ] -> equalsAny ( [ ';' , '{' , '}' ] ) ) { continue ; } // Note: here we are @ "[;{}] $a = [^;{<? ? >] ; return $a;" $ inserted += $ this -> simplifyReturnStatement ( $ tokens , $ assignVarIndex , $ assignVarOperatorIndex , $ index , $ endReturnVarIndex ) ; } return $ inserted ; }
@param Tokens $tokens
@param int    $functionIndex      token index of T_FUNCTION
@param int    $functionOpenIndex  token index of the opening brace token of the function
@param int    $functionCloseIndex token index of the closing brace token of the function

@return int >= 0 number of tokens inserted into the Tokens collection
@param Tokens $tokens @param int $functionIndex token index of T_FUNCTION @param int $functionOpenIndex token index of the opening brace token of the function @param int $functionCloseIndex token index of the closing brace token of the function