Browse Source

Add ReturnAssignmentFixer

SpacePossum 7 years ago
parent
commit
581ed46aea

+ 1 - 0
.php_cs.dist

@@ -62,6 +62,7 @@ $config = PhpCsFixer\Config::create()
         'phpdoc_add_missing_param_annotation' => true,
         'phpdoc_order' => true,
         'phpdoc_types_order' => true,
+        'return_assignment' => true,
         'semicolon_after_instruction' => true,
         'single_line_comment_style' => true,
         'strict_comparison' => true,

+ 5 - 0
README.rst

@@ -1369,6 +1369,11 @@ Choose from the list of available rules:
     ones; defaults to ``['getrandmax' => 'mt_getrandmax', 'rand' =>
     'mt_rand', 'srand' => 'mt_srand']``
 
+* **return_assignment**
+
+  Non global, static or indirectly referenced variables should not be
+  assigned and directly returned by a function or method.
+
 * **return_type_declaration** [@Symfony]
 
   There should be one or no space before colon, and one space after it in

+ 1 - 3
src/Console/Command/ReadmeCommand.php

@@ -241,9 +241,7 @@ EOF;
                     $result = Preg::replace("#<\\?{$matches[1]}\\s*#", '', $result);
                 }
 
-                $result = Preg::replace("#\n\n +\\?>#", '', $result);
-
-                return $result;
+                return Preg::replace("#\n\n +\\?>#", '', $result);
             },
             $help
         );

+ 3 - 5
src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php

@@ -443,13 +443,12 @@ public function testItDoesSomething() {}}'.$this->whitespacesConfig->getLineEndi
         $lineContent = $this->getSingleLineDocBlockEntry($lines);
         $lineEnd = $this->whitespacesConfig->getLineEnding();
         $originalIndent = $this->detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
-        $lines = [
+
+        return [
             new Line('/**'.$lineEnd),
             new Line($originalIndent.' * '.$lineContent.$lineEnd),
             new Line($originalIndent.' */'),
         ];
-
-        return $lines;
     }
 
     /**
@@ -471,9 +470,8 @@ public function testItDoesSomething() {}}'.$this->whitespacesConfig->getLineEndi
             ++$i;
         }
         $line = array_slice($line, $i);
-        $line = implode($line);
 
-        return $line;
+        return implode($line);
     }
 
     /**

+ 356 - 0
src/Fixer/ReturnNotation/ReturnAssignmentFixer.php

@@ -0,0 +1,356 @@
+<?php
+
+/*
+ * This file is part of PHP CS Fixer.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace PhpCsFixer\Fixer\ReturnNotation;
+
+use PhpCsFixer\AbstractFixer;
+use PhpCsFixer\FixerDefinition\CodeSample;
+use PhpCsFixer\FixerDefinition\FixerDefinition;
+use PhpCsFixer\Tokenizer\CT;
+use PhpCsFixer\Tokenizer\Token;
+use PhpCsFixer\Tokenizer\Tokens;
+
+/**
+ * @author SpacePossum
+ */
+final class ReturnAssignmentFixer extends AbstractFixer
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function getDefinition()
+    {
+        return new FixerDefinition(
+            'Non global, static or indirectly referenced variables should not be assigned and directly returned by a function or method.',
+            [new CodeSample("<?php\nfunction a() {\n    \$a = 1;\n    return \$a;\n}\n")]
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getPriority()
+    {
+        // must run after the NoEmptyStatementFixer
+        // must run before BlankLineBeforeStatementFixer
+        return -15;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isCandidate(Tokens $tokens)
+    {
+        return $tokens->isAllTokenKindsFound([T_FUNCTION, T_RETURN, T_VARIABLE]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
+    {
+        $tokenCount = count($tokens);
+
+        for ($index = 1; $index < $tokenCount; ++$index) {
+            if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
+                continue;
+            }
+
+            $functionOpenIndex = $tokens->getNextTokenOfKind($index, ['{', ';']);
+            if ($tokens[$functionOpenIndex]->equals(';')) { // abstract function
+                $index = $functionOpenIndex - 1;
+
+                continue;
+            }
+
+            $functionCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $functionOpenIndex);
+
+            $tokensAdded = $this->fixFunction(
+                $tokens,
+                $index,
+                $functionOpenIndex,
+                $functionCloseIndex
+            );
+
+            $index = $functionCloseIndex + $tokensAdded;
+            $tokenCount += $tokensAdded;
+        }
+    }
+
+    /**
+     * @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
+     */
+    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]->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    $assignVarIndex
+     * @param int    $assignVarOperatorIndex
+     * @param int    $returnIndex
+     * @param int    $returnVarEndIndex
+     *
+     * @return int >= 0 number of tokens inserted into the Tokens collection
+     */
+    private function simplifyReturnStatement(
+        Tokens $tokens,
+        $assignVarIndex,
+        $assignVarOperatorIndex,
+        $returnIndex,
+        $returnVarEndIndex
+    ) {
+        $inserted = 0;
+        $originalIndent = $tokens[$assignVarIndex - 1]->isWhitespace()
+            ? $tokens[$assignVarIndex - 1]->getContent()
+            : null
+        ;
+
+        // remove the return statement
+        if ($tokens[$returnVarEndIndex]->equals(';')) { // do not remove PHP close tags
+            $tokens->clearTokenAndMergeSurroundingWhitespace($returnVarEndIndex);
+        }
+
+        for ($i = $returnIndex; $i <= $returnVarEndIndex - 1; ++$i) {
+            $this->clearIfSave($tokens, $i);
+        }
+
+        // remove no longer needed indentation of the old/remove return statement
+        if ($tokens[$returnIndex - 1]->isWhitespace()) {
+            $content = $tokens[$returnIndex - 1]->getContent();
+            $fistLinebreakPos = strrpos($content, "\n");
+            $content = false === $fistLinebreakPos
+                ? ' '
+                : substr($content, $fistLinebreakPos)
+            ;
+
+            $tokens[$returnIndex - 1] = new Token([T_WHITESPACE, $content]);
+        }
+
+        // remove the variable and the assignment
+        for ($i = $assignVarIndex; $i <= $assignVarOperatorIndex; ++$i) {
+            $this->clearIfSave($tokens, $i);
+        }
+
+        // insert new return statement
+        $tokens->insertAt($assignVarIndex, new Token([T_RETURN, 'return']));
+        ++$inserted;
+
+        // use the original indent of the var assignment for the new return statement
+        if (
+            null !== $originalIndent
+            && $tokens[$assignVarIndex - 1]->isWhitespace()
+            && $originalIndent !== $tokens[$assignVarIndex - 1]->getContent()
+        ) {
+            $tokens[$assignVarIndex - 1] = new Token([T_WHITESPACE, $originalIndent]);
+        }
+
+        // remove trailing space after the new return statement which might be added during the clean up process
+        $nextIndex = $tokens->getNonEmptySibling($assignVarIndex, 1);
+        if (!$tokens[$nextIndex]->isWhitespace()) {
+            $tokens->insertAt($nextIndex, new Token([T_WHITESPACE, ' ']));
+            ++$inserted;
+        }
+
+        return $inserted;
+    }
+
+    private function clearIfSave(Tokens $tokens, $index)
+    {
+        if ($tokens[$index]->isComment()) {
+            return;
+        }
+
+        if ($tokens[$index]->isWhitespace() && $tokens[$tokens->getPrevNonWhitespace($index)]->isComment()) {
+            return;
+        }
+
+        $tokens->clearTokenAndMergeSurroundingWhitespace($index);
+    }
+
+    /**
+     * @param Token $token
+     *
+     * @return bool
+     */
+    private function isSuperGlobal(Token $token)
+    {
+        static $superNames = [
+            '$_COOKIE' => true,
+            '$_ENV' => true,
+            '$_FILES' => true,
+            '$_GET' => true,
+            '$_POST' => true,
+            '$_REQUEST' => true,
+            '$_SERVER' => true,
+            '$_SESSION' => true,
+            '$GLOBALS' => true,
+        ];
+
+        if (!$token->isGivenKind(T_VARIABLE)) {
+            return false;
+        }
+
+        return isset($superNames[strtoupper($token->getContent())]);
+    }
+}

+ 2 - 0
tests/AutoReview/FixerFactoryTest.php

@@ -113,6 +113,7 @@ final class FixerFactoryTest extends TestCase
             [$fixers['no_empty_statement'], $fixers['no_useless_else']],
             [$fixers['no_empty_statement'], $fixers['no_useless_return']],
             [$fixers['no_empty_statement'], $fixers['no_whitespace_in_blank_line']],
+            [$fixers['no_empty_statement'], $fixers['return_assignment']],
             [$fixers['no_empty_statement'], $fixers['space_after_semicolon']],
             [$fixers['no_empty_statement'], $fixers['switch_case_semicolon_to_colon']],
             [$fixers['no_extra_blank_lines'], $fixers['blank_line_before_statement']],
@@ -182,6 +183,7 @@ final class FixerFactoryTest extends TestCase
             [$fixers['pow_to_exponentiation'], $fixers['no_spaces_after_function_name']],
             [$fixers['pow_to_exponentiation'], $fixers['no_spaces_inside_parenthesis']],
             [$fixers['protected_to_private'], $fixers['ordered_class_elements']],
+            [$fixers['return_assignment'], $fixers['blank_line_before_statement']],
             [$fixers['simplified_null_return'], $fixers['no_useless_return']],
             [$fixers['single_import_per_statement'], $fixers['no_leading_import_slash']],
             [$fixers['single_import_per_statement'], $fixers['multiline_whitespace_before_semicolons']],

+ 1 - 3
tests/ConfigTest.php

@@ -218,11 +218,9 @@ final class ConfigTest extends TestCase
             new \PhpCsFixer\Fixer\ControlStructure\IncludeFixer(),
         ];
 
-        $cases = [
+        return [
             [$fixers, $fixers],
             [$fixers, new \ArrayIterator($fixers)],
         ];
-
-        return $cases;
     }
 }

+ 1 - 3
tests/Console/Output/ErrorOutputTest.php

@@ -170,9 +170,7 @@ EOT;
         // normalize line breaks,
         // as we output using SF `writeln` we are not sure what line ending has been used as it is
         // based on the platform/console/terminal used
-        $displayed = str_replace(PHP_EOL, "\n", $displayed);
-
-        return $displayed;
+        return str_replace(PHP_EOL, "\n", $displayed);
     }
 
     private function getErrorAndLineNumber()

+ 732 - 0
tests/Fixer/ReturnNotation/ReturnAssignmentFixerTest.php

@@ -0,0 +1,732 @@
+<?php
+
+/*
+ * This file is part of PHP CS Fixer.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace PhpCsFixer\Tests\Fixer\ReturnNotation;
+
+use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
+
+/**
+ * @author SpacePossum
+ *
+ * @internal
+ *
+ * @covers \PhpCsFixer\Fixer\ReturnNotation\ReturnAssignmentFixer
+ */
+final class ReturnAssignmentFixerTest extends AbstractFixerTestCase
+{
+    /**
+     * @dataProvider provideFixNestedFunctionsCases
+     *
+     * @param string $expected
+     * @param string $input
+     */
+    public function testFixNestedFunctions($expected, $input)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFixNestedFunctionsCases()
+    {
+        return [
+            [
+                '<?php
+function A($a0,$a1,$a2,$d)
+{
+    if ($a0) {
+        return 1;
+          // fix me
+    }
+
+    if ($a1) {
+        return 2;
+          // fix me
+    }
+
+    $nested0 = function() {
+        global $a;
+
+        ++$a;
+        $d = 2;
+
+        $nested1 = function () use ($d) {
+            if ($d) {
+                return 3;
+                  // fix me
+            }
+
+            $nested2 = function (&$d) {
+                if ($d) {
+                    $f = 1;
+                    return $f; // fix me not
+                }
+
+                $d = function () {
+                    return 4;
+                      // fix me
+                };
+
+                if ($d+1) {
+                    $f = 1;
+                    return $f; // fix me not
+                }
+            };
+
+            return $nested2();
+        };
+
+        return $a; // fix me not
+    };
+
+    if ($a2) {
+        return 5;
+          // fix me
+    }
+}
+
+function B($b0, $b1, $b2)
+{
+    if ($b0) {
+        return 10;
+          // fix me
+    }
+
+    if ($b1) {
+        return 20;
+          // fix me
+    }
+
+    if ($b2) {
+        return 30;
+          // fix me
+    }
+}
+',
+                '<?php
+function A($a0,$a1,$a2,$d)
+{
+    if ($a0) {
+        $b = 1;
+        return $b; // fix me
+    }
+
+    if ($a1) {
+        $c = 2;
+        return $c; // fix me
+    }
+
+    $nested0 = function() {
+        global $a;
+
+        ++$a;
+        $d = 2;
+
+        $nested1 = function () use ($d) {
+            if ($d) {
+                $f = 3;
+                return $f; // fix me
+            }
+
+            $nested2 = function (&$d) {
+                if ($d) {
+                    $f = 1;
+                    return $f; // fix me not
+                }
+
+                $d = function () {
+                    $a = 4;
+                    return $a; // fix me
+                };
+
+                if ($d+1) {
+                    $f = 1;
+                    return $f; // fix me not
+                }
+            };
+
+            return $nested2();
+        };
+
+        return $a; // fix me not
+    };
+
+    if ($a2) {
+        $d = 5;
+        return $d; // fix me
+    }
+}
+
+function B($b0, $b1, $b2)
+{
+    if ($b0) {
+        $b = 10;
+        return $b; // fix me
+    }
+
+    if ($b1) {
+        $c = 20;
+        return $c; // fix me
+    }
+
+    if ($b2) {
+        $d = 30;
+        return $d; // fix me
+    }
+}
+',
+            ],
+        ];
+    }
+
+    /**
+     * @dataProvider provideFixCases
+     *
+     * @param string $expected
+     * @param string $input
+     */
+    public function testFix($expected, $input)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFixCases()
+    {
+        return [
+            [
+                '<?php
+                    function A()
+                    {
+                        return 15;
+                    }
+                ',
+                '<?php
+                    function A()
+                    {
+                        $a = 15;
+                        return $a;
+                    }
+                ',
+            ],
+            [
+                '<?php
+                    function A()
+                    {
+                        /*0*/return /*1*//*2*/15;/*3*//*4*/ /*5*/ /*6*//*7*//*8*/
+                    }
+                ',
+                '<?php
+                    function A()
+                    {
+                        /*0*/$a/*1*/=/*2*/15;/*3*//*4*/ /*5*/ return/*6*/$a/*7*/;/*8*/
+                    }
+                ',
+            ],
+            'comments with leading space' => [
+                '<?php
+                    function A()
+                    { #1
+ #2
+ return #3
+ #4
+  #5
+ #6
+ 15 #7
+ ; #8
+ #9
+  #10
+ #11
+   #12
+ #13
+   #14
+ #15
+                    }
+                ',
+                '<?php
+                    function A()
+                    { #1
+ #2
+ $a #3
+ #4
+ = #5
+ #6
+ 15 #7
+ ; #8
+ #9
+ return #10
+ #11
+ $a  #12
+ #13
+ ;  #14
+ #15
+                    }
+                ',
+            ],
+            [
+                '<?php
+                    abstract class B
+                    {
+                        abstract protected function Z();public function A()
+                        {
+                            return 16;
+                        }
+                    }
+                ',
+                '<?php
+                    abstract class B
+                    {
+                        abstract protected function Z();public function A()
+                        {
+                            $a = 16; return $a;
+                        }
+                    }
+                ',
+            ],
+            [
+                '<?php
+                    function b() {
+                        if ($c) {
+                            return 0;
+                        }
+                        return testFunction(654+1);
+                    }
+                ',
+                '<?php
+                    function b() {
+                        if ($c) {
+                            $b = 0;
+                            return $b;
+                        }
+                        $a = testFunction(654+1);
+                        return $a;
+                    }
+                ',
+            ],
+            'minimal notation' => [
+                '<?php $e=function(){return 1;};$f=function(){return 1;};$g=function(){return 1;};',
+                '<?php $e=function(){$a=1;return$a;};$f=function(){$a=1;return$a;};$g=function(){$a=1;return$a;};',
+            ],
+            [
+                '<?php
+                    function A()
+                    {#1
+#2                    '.'
+return #3
+#4
+#5
+#6
+15#7
+;#8
+#9
+#10
+#11
+#12
+#13
+#14
+#15
+                    }
+                ',
+                '<?php
+                    function A()
+                    {#1
+#2                    '.'
+$a#3
+#4
+=#5
+#6
+15#7
+;#8
+#9
+return#10
+#11
+$a#12
+#13
+;#14
+#15
+                    }
+                ',
+            ],
+            [
+                '<?php
+function A($b)
+{
+        // Comment
+        return a("2", 4, $b);
+}
+',
+                '<?php
+function A($b)
+{
+        // Comment
+        $value = a("2", 4, $b);
+
+        return $value;
+}
+',
+            ],
+            [
+                '<?php function a($b,$c)  {if($c>1){echo 1;} return (1 + 2 + $b);  }',
+                '<?php function a($b,$c)  {if($c>1){echo 1;} $a= (1 + 2 + $b);return $a; }',
+            ],
+            [
+                '<?php function a($b,$c)  {return (3 * 4 + $b);  }',
+                '<?php function a($b,$c)  {$zz= (3 * 4 + $b);return $zz; }',
+            ],
+            [
+                '<?php
+                    function a() {
+                        return 4563;
+                          ?> <?php
+                    }
+                ',
+                '<?php
+                    function a() {
+                        $a = 4563;
+                        return $a ?> <?php
+                    }
+                ',
+            ],
+            [
+                '<?php
+                    function a()
+                    {
+                        return $c + 1; /*
+var names are case insensitive */ }
+                ',
+                '<?php
+                    function a()
+                    {
+                        $A = $c + 1; /*
+var names are case insensitive */ return $a   ;}
+                ',
+            ],
+        ];
+    }
+
+    /**
+     * @dataProvider provideDoNotFixCases
+     *
+     * @param string $expected
+     */
+    public function testDoNotFix($expected)
+    {
+        $this->doTest($expected);
+    }
+
+    public function provideDoNotFixCases()
+    {
+        return [
+            'static' => [
+                '<?php
+                    function a() {
+                        static $a;
+                        $a = time();
+                        return $a;
+                    }
+                ',
+            ],
+            'global' => [
+                '<?php
+                    function a() {
+                        global $a;
+                        $a = time();
+                        return $a;
+                    }
+                ',
+            ],
+            'passed by reference' => [
+                '<?php
+                function foo(&$var)
+                    {
+                        $var = 1;
+                        return $var;
+                    }
+                ',
+            ],
+            'not in function scope' => [
+                '<?php
+                    $a = 1; // var might be global here
+                    return $a;
+                ',
+            ],
+            [
+                '<?php
+                    function a()
+                    {
+                        $a = 1;
+                        ?>
+                        <?php
+                        ;
+                        return $a;
+                    }
+                ',
+            ],
+            [
+                '<?php
+                    function a()
+                    {
+                        $a = 1 ?><?php return $a;
+                    }',
+            ],
+            [
+                '<?php
+                    function a()
+                    {
+                        $a = 1
+                        ?>
+                        <?php
+                        return $a;
+                    }
+                ',
+            ],
+            [
+                '<?php
+                    $zz = 1 ?><?php
+                    function a($zz)
+                    {
+                        ;
+                        return $zz;
+                    }
+                ',
+            ],
+            'return complex statement' => [
+                '<?php
+                    function a($c)
+                    {
+                        $a = 1;
+                        return $a + $c;
+                    }
+                ',
+            ],
+            'array assign' => [
+                '<?php
+                    function a($c)
+                    {
+                        $_SERVER["abc"] = 3;
+                        return $_SERVER;
+                    }
+                ',
+            ],
+            'if assign' => [
+                '<?php
+                    function foo ($bar)
+                    {
+                        $a = 123;
+                        if ($bar)
+                            $a = 12345;
+                        return $a;
+                    }
+                ',
+            ],
+            'else assign' => [
+                '<?php
+                    function foo ($bar)
+                    {
+                        $a = 123;
+                        if ($bar)
+                            ;
+                        else
+                            $a = 12345;
+                        return $a;
+                    }
+                ',
+            ],
+            'elseif assign' => [
+                '<?php
+                    function foo ($bar)
+                    {
+                        $a = 123;
+                        if ($bar)
+                            ;
+                        elseif($b)
+                            $a = 12345;
+                        return $a;
+                    }
+                ',
+            ],
+            'echo $a = N / comment $a = N;' => [
+                '<?php
+                    function a($c)
+                    {
+                        $a = 1;
+                        echo $a."=1";
+                        return $a;
+                    }
+
+                    function b($c)
+                    {
+                        $a = 1;
+                        echo $a."=1;";
+                        return $a;
+                    }
+
+                    function c($c)
+                    {
+                        $a = 1;
+                        echo $a;
+                        // $a =1;
+                        return $a;
+                    }
+                ',
+            ],
+            'if ($a = N)' => [
+                '<?php
+                    function a($c)
+                    {
+                        if ($a = 1)
+                            return $a;
+                    }
+                ',
+            ],
+            'changed after declaration' => [
+                '<?php
+                    function a($c)
+                    {
+                        $a = 1;
+                        $a += 1;
+                        return $a;
+                    }
+
+                    function b($c)
+                    {
+                        $a = 1;
+                        $a -= 1;
+                        return $a;
+                    }
+                ',
+            ],
+            'complex statement' => [
+                '<?php
+                    function a($c)
+                    {
+                        $d = $c && $a = 1;
+                        return $a;
+                    }
+                ',
+            ],
+            'PHP close tag within function' => [
+                '<?php
+                    function a($zz)
+                    {
+                        $zz = 1 ?><?php
+                        ;
+                        return $zz;
+                    }
+                ',
+            ],
+            'import global using "require"' => [
+                '<?php
+                    function a()
+                    {
+                        require __DIR__."/test3.php";
+                        $b = 1;
+                        return $b;
+                    }
+                ',
+            ],
+            'import global using "require_once"' => [
+                '<?php
+                    function a()
+                    {
+                        require_once __DIR__."/test3.php";
+                        $b = 1;
+                        return $b;
+                    }
+                ',
+            ],
+            'import global using "include"' => [
+                '<?php
+                    function a()
+                    {
+                        include __DIR__."/test3.php";
+                        $b = 1;
+                        return $b;
+                    }
+                ',
+            ],
+            'import global using "include_once"' => [
+                '<?php
+                    function a()
+                    {
+                        include_once __DIR__."/test3.php";
+                        $b = 1;
+                        return $b;
+                    }
+                ',
+            ],
+            'eval' => [
+                '<?php
+                    $b = function ($z) {
+                        $c = eval($z);
+
+                        return $c;
+                    };
+
+                    $c = function ($x) {
+                        $x = eval($x);
+                        $x = 1;
+                        return $x;
+                    };
+                ',
+            ],
+            '${X}' => [
+                '<?php
+                    function A($g)
+                    {
+                        $h = ${$g};
+
+                        return $h;
+                    }
+                ',
+            ],
+            '$$' => [
+                '<?php
+                    function B($c)
+                    {
+                        $b = $$c;
+
+                        return $b;
+                    }
+                ',
+            ],
+            [
+                '<?php
+class XYZ
+{
+    public function test1()
+    {
+        $GLOBALS = 2;
+
+        return $GLOBALS;
+    }
+
+    public function test2()
+    {
+        $_server = 2;
+
+        return $_server;
+    }
+
+    public function __destruct()
+    {
+        $GLOBALS[\'a\'] = 2;
+
+        return $GLOBALS[\'a\']; // destruct cannot return but still lints
+    }
+};
+
+$a = new XYZ();
+$a = 1;
+var_dump($a); // $a = 2 here _╯°□°╯︵┻━┻
+',
+            ],
+        ];
+    }
+}

+ 20 - 0
tests/Fixtures/Integration/priority/no_empty_statement,return_assignment.test

@@ -0,0 +1,20 @@
+--TEST--
+Integration of fixers: no_empty_statement,return_assignment.
+--RULESET--
+{"no_empty_statement": true, "return_assignment": true}
+--EXPECT--
+<?php
+
+function foo($a)
+{
+    return 1;
+}
+
+--INPUT--
+<?php
+
+function foo($a)
+{
+    $foo = 1;;
+    return $foo;
+}

Some files were not shown because too many files changed in this diff