Skip to content

Commit 04ab5a0

Browse files
committed
Fixed detection of scope closers when arrow functions used in ternary (ref #2715)
1 parent 7ee537f commit 04ab5a0

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

src/Tokenizers/PHP.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1737,12 +1737,16 @@ protected function processAdditional()
17371737
T_CLOSE_TAG => true,
17381738
];
17391739

1740+
$inTernary = false;
1741+
17401742
for ($scopeCloser = ($arrow + 1); $scopeCloser < $numTokens; $scopeCloser++) {
17411743
if (isset($endTokens[$this->tokens[$scopeCloser]['code']]) === true) {
17421744
break;
17431745
}
17441746

1745-
if (isset($this->tokens[$scopeCloser]['scope_closer']) === true) {
1747+
if (isset($this->tokens[$scopeCloser]['scope_closer']) === true
1748+
&& $this->tokens[$scopeCloser]['code'] !== T_INLINE_ELSE
1749+
) {
17461750
// We minus 1 here in case the closer can be shared with us.
17471751
$scopeCloser = ($this->tokens[$scopeCloser]['scope_closer'] - 1);
17481752
continue;
@@ -1757,6 +1761,20 @@ protected function processAdditional()
17571761
$scopeCloser = $this->tokens[$scopeCloser]['bracket_closer'];
17581762
continue;
17591763
}
1764+
1765+
if ($this->tokens[$scopeCloser]['code'] === T_INLINE_THEN) {
1766+
$inTernary = true;
1767+
continue;
1768+
}
1769+
1770+
if ($this->tokens[$scopeCloser]['code'] === T_INLINE_ELSE) {
1771+
if ($inTernary === false) {
1772+
break;
1773+
}
1774+
1775+
$inTernary = false;
1776+
continue;
1777+
}
17601778
}//end for
17611779

17621780
if ($scopeCloser !== $numTokens) {

tests/Core/Tokenizer/BackfillFnTokenTest.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,6 @@ fn(parent $a) : parent => $a;
6666

6767
/* testCallableReturnType */
6868
fn(callable $a) : callable => $a;
69+
70+
/* testTernary */
71+
$fn = fn($a) => $a ? /* testTernaryThen */ fn() : string => 'a' : /* testTernaryElse */ fn() : string => 'b';

tests/Core/Tokenizer/BackfillFnTokenTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,62 @@ public function testKeywordReturnTypes()
495495
}//end testKeywordReturnTypes()
496496

497497

498+
/**
499+
* Test arrow functions used in ternary operators.
500+
*
501+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
502+
*
503+
* @return void
504+
*/
505+
public function testTernary()
506+
{
507+
$tokens = self::$phpcsFile->getTokens();
508+
509+
$token = $this->getTargetToken('/* testTernary */', T_FN);
510+
$this->backfillHelper($token);
511+
512+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
513+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 40), 'Scope closer is not the semicolon token');
514+
515+
$opener = $tokens[$token]['scope_opener'];
516+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
517+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 40), 'Opener scope closer is not the semicolon token');
518+
519+
$closer = $tokens[$token]['scope_opener'];
520+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
521+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 40), 'Closer scope closer is not the semicolon token');
522+
523+
$token = $this->getTargetToken('/* testTernaryThen */', T_FN);
524+
$this->backfillHelper($token);
525+
526+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener for THEN is not the arrow token');
527+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 12), 'Scope closer for THEN is not the semicolon token');
528+
529+
$opener = $tokens[$token]['scope_opener'];
530+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener for THEN is not the arrow token');
531+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer for THEN is not the semicolon token');
532+
533+
$closer = $tokens[$token]['scope_opener'];
534+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener for THEN is not the arrow token');
535+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer for THEN is not the semicolon token');
536+
537+
$token = $this->getTargetToken('/* testTernaryElse */', T_FN);
538+
$this->backfillHelper($token);
539+
540+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener for ELSE is not the arrow token');
541+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 11), 'Scope closer for ELSE is not the semicolon token');
542+
543+
$opener = $tokens[$token]['scope_opener'];
544+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener for ELSE is not the arrow token');
545+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 11), 'Opener scope closer for ELSE is not the semicolon token');
546+
547+
$closer = $tokens[$token]['scope_opener'];
548+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener for ELSE is not the arrow token');
549+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 11), 'Closer scope closer for ELSE is not the semicolon token');
550+
551+
}//end testTernary()
552+
553+
498554
/**
499555
* Test that anonymous class tokens without parenthesis do not get assigned a parenthesis owner.
500556
*

0 commit comments

Comments
 (0)