Skip to content

Commit

Permalink
fix(SelfAccessorFixer): do not touch references inside lambda and/or …
Browse files Browse the repository at this point in the history
…arrow function (#7349)
  • Loading branch information
SpacePossum committed Oct 31, 2023
1 parent 7ab90e7 commit 5f102b4
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 62 deletions.
14 changes: 14 additions & 0 deletions src/Fixer/ClassNotation/SelfAccessorFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,21 @@ private function replaceNameOccurrences(Tokens $tokens, string $namespace, strin
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, ['{', ';']);

Expand Down
27 changes: 1 addition & 26 deletions src/Fixer/FunctionNotation/StaticLambdaFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
$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)) {
Expand All @@ -91,31 +91,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
}
}

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.
*/
Expand Down
33 changes: 2 additions & 31 deletions src/Fixer/Operator/BinaryOperatorSpacesFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ private function injectAlignmentPlaceholdersDefault(Tokens $tokens, int $startAt

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;

Expand Down Expand Up @@ -715,7 +715,7 @@ private function injectAlignmentPlaceholdersForArrow(Tokens $tokens, int $startA
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;

Expand Down Expand Up @@ -935,33 +935,4 @@ private function replacePlaceholders(Tokens $tokens, string $alignStrategy, stri

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 changes: 34 additions & 0 deletions src/Tokenizer/TokensAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,40 @@ public function isLambda(int $index): bool
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.
*/
Expand Down
32 changes: 27 additions & 5 deletions tests/Fixer/ClassNotation/SelfAccessorFixerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,6 @@ public static function provideFixCases(): iterable
'<?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; }',
Expand Down Expand Up @@ -194,6 +189,33 @@ public function baz31(\Test\Foo\Foo2\Bar $bar);
"<?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;
}
}',
];
}

/**
Expand Down
147 changes: 147 additions & 0 deletions tests/Tokenizer/TokensAnalyzerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2802,6 +2802,153 @@ public function testGetClassyModifiersForNonClassyThrowsAnException(): void
$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
*/
Expand Down

0 comments on commit 5f102b4

Please sign in to comment.