diff --git a/package.xml b/package.xml index 2ba1ae8b43..bfd7d49e1d 100644 --- a/package.xml +++ b/package.xml @@ -36,6 +36,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> - Fixed bug #3316 : Arrow function not tokenized correctly when using null in union type - Fixed bug #3317 : Problem with how phpcs handles ignored files when running in parallel -- Thanks to Emil Andersson for the patch + - Fixed bug #3324 : PHPCS hangs processing some nested arrow functions inside a function call diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index e2aa2ba45a..60c89c4507 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2355,6 +2355,16 @@ protected function processAdditional() break; } + if ($inTernary === false + && isset($this->tokens[$scopeCloser]['scope_closer'], $this->tokens[$scopeCloser]['scope_condition']) === true + && $scopeCloser === $this->tokens[$scopeCloser]['scope_closer'] + && $this->tokens[$this->tokens[$scopeCloser]['scope_condition']]['code'] === T_FN + ) { + // Found a nested arrow function that already has the closer set and is in + // the same scope as us, so we can use its closer. + break; + } + if (isset($this->tokens[$scopeCloser]['scope_closer']) === true && $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE && $this->tokens[$scopeCloser]['code'] !== T_END_HEREDOC diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.inc b/tests/Core/Tokenizer/BackfillFnTokenTest.inc index 56e8c29c3d..ddd502bb61 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.inc @@ -23,6 +23,9 @@ function fn() {} /* testNestedOuter */ $fn = fn($x) => /* testNestedInner */ fn($y) => $x * $y + $z; +/* testNestedSharedCloserOuter */ +$foo = foo(fn() => /* testNestedSharedCloserInner */ fn() => bar() === true); + /* testFunctionCall */ $extended = fn($c) => $callable($factory($c), $c); diff --git a/tests/Core/Tokenizer/BackfillFnTokenTest.php b/tests/Core/Tokenizer/BackfillFnTokenTest.php index 76b39819d2..5529d2e96a 100644 --- a/tests/Core/Tokenizer/BackfillFnTokenTest.php +++ b/tests/Core/Tokenizer/BackfillFnTokenTest.php @@ -128,6 +128,41 @@ public function testNestedInner() }//end testNestedInner() + /** + * Test nested arrow functions with a shared closer. + * + * @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional + * + * @return void + */ + public function testNestedSharedCloser() + { + $tokens = self::$phpcsFile->getTokens(); + + $token = $this->getTargetToken('/* testNestedSharedCloserOuter */', T_FN); + $this->backfillHelper($token); + $this->scopePositionTestHelper($token, 4, 20); + + $token = $this->getTargetToken('/* testNestedSharedCloserInner */', T_FN); + $this->backfillHelper($token, true); + + $expectedScopeOpener = ($token + 4); + $expectedScopeCloser = ($token + 12); + + $this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener for "inner" arrow function is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer for "inner" arrow function is not the TRUE token'); + + $opener = $tokens[$token]['scope_opener']; + $this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener for "inner" arrow function is not the arrow token'); + $this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer for "inner" arrow function is not the semicolon token'); + + $closer = $tokens[$token]['scope_closer']; + $this->assertSame(($token - 4), $tokens[$closer]['scope_opener'], 'Closer scope opener for "inner" arrow function is not the arrow token of the "outer" arrow function (shared scope closer)'); + $this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer for "inner" arrow function is not the TRUE token'); + + }//end testNestedSharedCloser() + + /** * Test arrow functions that call functions. *