Просмотр исходного кода

fix(SelfAccessorFixer): do not touch references inside lambda and/or arrow function (#7349)

SpacePossum 1 год назад
Родитель
Сommit
5f102b4361

+ 14 - 0
src/Fixer/ClassNotation/SelfAccessorFixer.php

@@ -120,7 +120,21 @@ class Sample
                 continue;
             }
 
+            if ($token->isGivenKind(T_FN)) {
+                $i = $tokensAnalyzer->getLastTokenIndexOfArrowFunction($i);
+                $i = $tokens->getNextMeaningfulToken($i);
+
+                continue;
+            }
+
             if ($token->isGivenKind(T_FUNCTION)) {
+                if ($tokensAnalyzer->isLambda($i)) {
+                    $i = $tokens->getNextTokenOfKind($i, ['{']);
+                    $i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i);
+
+                    continue;
+                }
+
                 $i = $tokens->getNextTokenOfKind($i, ['(']);
                 $insideMethodSignatureUntil = $tokens->getNextTokenOfKind($i, ['{', ';']);
 

+ 1 - 26
src/Fixer/FunctionNotation/StaticLambdaFixer.php

@@ -71,7 +71,7 @@ final class StaticLambdaFixer extends AbstractFixer
                 $lambdaEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $lambdaOpenIndex);
             } else { // T_FN
                 $lambdaOpenIndex = $tokens->getNextTokenOfKind($argumentsEndIndex, [[T_DOUBLE_ARROW]]);
-                $lambdaEndIndex = $this->findExpressionEnd($tokens, $lambdaOpenIndex);
+                $lambdaEndIndex = $analyzer->getLastTokenIndexOfArrowFunction($index);
             }
 
             if ($this->hasPossibleReferenceToThis($tokens, $lambdaOpenIndex, $lambdaEndIndex)) {
@@ -91,31 +91,6 @@ final class StaticLambdaFixer extends AbstractFixer
         }
     }
 
-    private function findExpressionEnd(Tokens $tokens, int $index): int
-    {
-        $nextIndex = $tokens->getNextMeaningfulToken($index);
-
-        while (null !== $nextIndex) {
-            /** @var Token $nextToken */
-            $nextToken = $tokens[$nextIndex];
-
-            if ($nextToken->equalsAny([',', ';', [T_CLOSE_TAG]])) {
-                break;
-            }
-
-            $blockType = Tokens::detectBlockType($nextToken);
-
-            if (null !== $blockType && $blockType['isStart']) {
-                $nextIndex = $tokens->findBlockEnd($blockType['type'], $nextIndex);
-            }
-
-            $index = $nextIndex;
-            $nextIndex = $tokens->getNextMeaningfulToken($index);
-        }
-
-        return $index;
-    }
-
     /**
      * Returns 'true' if there is a possible reference to '$this' within the given tokens index range.
      */

+ 2 - 31
src/Fixer/Operator/BinaryOperatorSpacesFixer.php

@@ -626,7 +626,7 @@ $array = [
 
             if ($token->isGivenKind(T_FN)) {
                 $from = $tokens->getNextMeaningfulToken($index);
-                $until = $this->getLastTokenIndexOfFn($tokens, $index);
+                $until = $this->tokensAnalyzer->getLastTokenIndexOfArrowFunction($index);
                 $this->injectAlignmentPlaceholders($tokens, $from + 1, $until - 1, $tokenContent);
                 $index = $until;
 
@@ -715,7 +715,7 @@ $array = [
             if ($token->isGivenKind(T_FN)) {
                 $yieldFoundSinceLastPlaceholder = false;
                 $from = $tokens->getNextMeaningfulToken($index);
-                $until = $this->getLastTokenIndexOfFn($tokens, $index);
+                $until = $this->tokensAnalyzer->getLastTokenIndexOfArrowFunction($index);
                 $this->injectArrayAlignmentPlaceholders($tokens, $from + 1, $until - 1);
                 $index = $until;
 
@@ -935,33 +935,4 @@ $array = [
 
         return $tmpCode;
     }
-
-    private function getLastTokenIndexOfFn(Tokens $tokens, int $index): int
-    {
-        $index = $tokens->getNextTokenOfKind($index, [[T_DOUBLE_ARROW]]);
-
-        while (true) {
-            $index = $tokens->getNextMeaningfulToken($index);
-
-            if ($tokens[$index]->equalsAny([';', ',', [T_CLOSE_TAG]])) {
-                break;
-            }
-
-            $blockType = Tokens::detectBlockType($tokens[$index]);
-
-            if (null === $blockType) {
-                continue;
-            }
-
-            if ($blockType['isStart']) {
-                $index = $tokens->findBlockEnd($blockType['type'], $index);
-
-                continue;
-            }
-
-            break;
-        }
-
-        return $index;
-    }
 }

+ 34 - 0
src/Tokenizer/TokensAnalyzer.php

@@ -319,6 +319,40 @@ final class TokensAnalyzer
         return $startParenthesisToken->equals('(');
     }
 
+    public function getLastTokenIndexOfArrowFunction(int $index): int
+    {
+        if (!$this->tokens[$index]->isGivenKind(T_FN)) {
+            throw new \InvalidArgumentException(sprintf('Not an "arrow function" at given index %d.', $index));
+        }
+
+        $stopTokens = [')', ']', ',', ';', [T_CLOSE_TAG]];
+        $index = $this->tokens->getNextTokenOfKind($index, [[T_DOUBLE_ARROW]]);
+
+        while (true) {
+            $index = $this->tokens->getNextMeaningfulToken($index);
+
+            if ($this->tokens[$index]->equalsAny($stopTokens)) {
+                break;
+            }
+
+            $blockType = Tokens::detectBlockType($this->tokens[$index]);
+
+            if (null === $blockType) {
+                continue;
+            }
+
+            if ($blockType['isStart']) {
+                $index = $this->tokens->findBlockEnd($blockType['type'], $index);
+
+                continue;
+            }
+
+            break;
+        }
+
+        return $this->tokens->getPrevMeaningfulToken($index);
+    }
+
     /**
      * Check if the T_STRING under given index is a constant invocation.
      */

+ 27 - 5
tests/Fixer/ClassNotation/SelfAccessorFixerTest.php

@@ -95,11 +95,6 @@ final class SelfAccessorFixerTest extends AbstractFixerTestCase
             '<?php class Foo { function bar() { Baz\Foo::class; } }',
         ];
 
-        yield [
-            '<?php class Foo { function bar() { function ($a = self::BAZ) { new self(); }; } }',
-            '<?php class Foo { function bar() { function ($a = Foo::BAZ) { new Foo(); }; } }',
-        ];
-
         yield [
             // In trait "self" will reference the class it's used in, not the actual trait, so we can't replace "Foo" with "self" here
             '<?php trait Foo { function bar() { Foo::bar(); } } class Bar { use Foo; }',
@@ -194,6 +189,33 @@ final class SelfAccessorFixerTest extends AbstractFixerTestCase
             "<?php interface Foo{ public function bar()\t/**/:?/**/self; }",
             "<?php interface Foo{ public function bar()\t/**/:?/**/Foo; }",
         ];
+
+        yield 'do not replace in lambda' => [
+            '<?php
+
+final class A
+{
+    public static function Z(): void
+    {
+        (function () {
+            var_dump(self::class, A::class);
+        })->bindTo(null, B::class)();
+    }
+}',
+        ];
+
+        yield 'do not replace in arrow function' => [
+            '<?php
+
+final class A
+{
+    public function Z($b): void
+    {
+        $a = fn($b) => self::class. A::class . $b;
+        echo $a;
+    }
+}',
+        ];
     }
 
     /**

+ 147 - 0
tests/Tokenizer/TokensAnalyzerTest.php

@@ -2802,6 +2802,153 @@ class MyTestWithAnonymousClass extends TestCase
         $tokensAnalyzer->getClassyModifiers(1);
     }
 
+    /**
+     * @dataProvider provideGetLastTokenIndexOfArrowFunctionCases
+     *
+     * @param array<int, int> $expectations
+     */
+    public function testGetLastTokenIndexOfArrowFunction(array $expectations, string $source): void
+    {
+        $tokens = Tokens::fromCode($source);
+        $tokensAnalyzer = new TokensAnalyzer($tokens);
+
+        $indices = [];
+
+        foreach ($expectations as $index => $expectedEndIndex) {
+            $indices[$index] = $tokensAnalyzer->getLastTokenIndexOfArrowFunction($index);
+        }
+
+        self::assertSame($expectations, $indices);
+    }
+
+    public static function provideGetLastTokenIndexOfArrowFunctionCases(): iterable
+    {
+        yield 'simple cases' => [
+            [
+                2 => 11,
+                16 => 25,
+                28 => 39,
+                46 => 61,
+            ],
+            '<?php
+                fn(array $x) => $x;
+
+                static fn(): int => $x;
+
+                fn($x = 42) => $x;
+                $eq = fn ($x, $y) => $x == $y;
+            ',
+        ];
+
+        yield 'references, splat and arrow cases' => [
+            [
+                2 => 10,
+                13 => 21,
+                24 => 35,
+                42 => 51,
+                65 => 77,
+            ],
+            '<?php
+                fn(&$x) => $x;
+                fn&($x) => $x;
+                fn($x, ...$rest) => $rest;
+
+                $fn = fn(&$x) => $x++;
+                $y = &$fn($x);
+                fn($x, &...$rest) => 1;
+            ',
+        ];
+
+        yield 'different endings' => [
+            [
+                9 => 21,
+                31 => 43,
+            ],
+            '<?php
+                $results = array_map(
+                    fn ($item) => $item * 2,
+                    $list
+                );
+
+                return fn ($y) => $x * $y ?>
+            ',
+        ];
+
+        yield 'nested arrow function' => [
+            [
+                1 => 26,
+                14 => 25,
+            ],
+            '<?php fn(array $x, $z) => (fn(int $z):bool => $z);',
+        ];
+
+        yield 'arrow function as argument' => [
+            [
+                5 => 14,
+            ],
+            '<?php return foo(fn(array $x) => $x);',
+        ];
+
+        yield 'arrow function as collection item' => [
+            [
+                9 => 18,
+                26 => 35,
+                46 => 55,
+                62 => 69,
+            ],
+            '<?php return [
+                [1, fn(array $x) => $x1, 1],
+                [fn(array $x) => $x2, 1],
+                [1, fn(array $x) => $x3],
+                ([(fn($x4) => $x5)]),
+            ];',
+        ];
+
+        yield 'nested inside anonymous class' => [
+            [
+                1 => 46,
+                33 => 41,
+            ],
+            '<?php fn($x) => $a = new class($x) { public function foo() { return fn(&$x) => $x; } };',
+        ];
+
+        yield 'array destructuring' => [
+            [
+                4 => 13,
+            ],
+            '<?php return [fn(array $x) => $x1] = $x;',
+        ];
+
+        yield 'array_map() callback with different token blocks' => [
+            [
+                9 => 28,
+            ],
+            '<?php
+                $a = array_map(
+                    fn (array $item) => $item[\'callback\']($item[\'value\']),
+                    [/* items */]
+                );
+            ',
+        ];
+
+        yield 'arrow function returning array' => [
+            [
+                5 => 21,
+            ],
+            '<?php $z = fn ($a) => [0, 1, $a];',
+        ];
+    }
+
+    public function testCannotGetLastTokenIndexOfArrowFunctionForNonFnToken(): void
+    {
+        $tokens = Tokens::fromCode('<?php echo 1;');
+        $tokensAnalyzer = new TokensAnalyzer($tokens);
+
+        $this->expectException(\InvalidArgumentException::class);
+
+        $tokensAnalyzer->getLastTokenIndexOfArrowFunction(1);
+    }
+
     /**
      * @param array<int, bool> $expected
      */