Skip to content

Commit

Permalink
Fixed support for arrow functions that return by reference + more tes…
Browse files Browse the repository at this point in the history
…ts (ref #2523)
  • Loading branch information
gsherwood committed Nov 6, 2019
1 parent 51afb54 commit ae3ffc7
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 12 deletions.
4 changes: 3 additions & 1 deletion src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -1631,7 +1631,9 @@ protected function processAdditional()
} else if ($this->tokens[$i]['code'] === T_FN) {
// Possible arrow function.
for ($x = ($i + 1); $i < $numTokens; $x++) {
if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
&& $this->tokens[$x]['code'] !== T_BITWISE_AND
) {
// Non-whitespace content.
break;
}
Expand Down
15 changes: 13 additions & 2 deletions tests/Core/Tokenizer/BackfillFnTokenTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@ $fn1 = fn /* comment here */ ($x) => $x + $y;
/* testFunctionName */
function fn() {}

/* testNested */
$fn = fn($x) => fn($y) => $x * $y + $z;
/* testNestedOuter */
$fn = fn($x) => /* testNestedInner */ fn($y) => $x * $y + $z;

/* testFunctionCall */
$extended = fn($c) => $callable($factory($c), $c);

/* testChainedFunctionCall */
$result = Collection::from([1, 2])
->map(fn($v) => $v * 2)
->reduce(/* testFunctionArgument */ fn($tmp, $v) => $tmp + $v, 0);

/* testClosure */
$extended = fn($c) => $callable(function() {
for ($x = 1; $x < 10; $x++) {
Expand All @@ -35,3 +40,9 @@ $result = array_map(
static fn(int $number) : int => $number + 1,
$numbers
);

/* testReference */
fn&($x) => $x;

/* testGrouped */
(fn($x) => $x) + $y;
158 changes: 149 additions & 9 deletions tests/Core/Tokenizer/BackfillFnTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,25 +130,53 @@ public function testFunctionName()
*
* @return void
*/
public function testNested()
public function testNestedOuter()
{
$tokens = self::$phpcsFile->getTokens();

$token = $this->getTargetToken('/* testNested */', T_FN);
$token = $this->getTargetToken('/* testNestedOuter */', T_FN);
$this->backfillHelper($token);

$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 23), 'Scope closer is not the semicolon token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 25), 'Scope closer is not the semicolon token');

$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 23), 'Opener scope closer is not the semicolon token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 25), 'Opener scope closer is not the semicolon token');

$closer = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 23), 'Closer scope closer is not the semicolon token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 25), 'Closer scope closer is not the semicolon token');

}//end testNested()
}//end testNestedOuter()


/**
* Test nested arrow functions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNestedInner()
{
$tokens = self::$phpcsFile->getTokens();

$token = $this->getTargetToken('/* testNestedInner */', T_FN);
$this->backfillHelper($token);

$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 16), 'Scope closer is not the semicolon token');

$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 16), 'Opener scope closer is not the semicolon token');

$closer = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 16), 'Closer scope closer is not the semicolon token');

}//end testNestedInner()


/**
Expand Down Expand Up @@ -179,6 +207,62 @@ public function testFunctionCall()
}//end testFunctionCall()


/**
* Test arrow functions that are included in chained calls.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testChainedFunctionCall()
{
$tokens = self::$phpcsFile->getTokens();

$token = $this->getTargetToken('/* testChainedFunctionCall */', T_FN);
$this->backfillHelper($token);

$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 12), 'Scope closer is not the bracket token');

$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer is not the bracket token');

$closer = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer is not the bracket token');

}//end testChainedFunctionCall()


/**
* Test arrow functions that are used as function arguments.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testFunctionArgument()
{
$tokens = self::$phpcsFile->getTokens();

$token = $this->getTargetToken('/* testFunctionArgument */', T_FN);
$this->backfillHelper($token);

$this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 15), 'Scope closer is not the comma token');

$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 15), 'Opener scope closer is not the comma token');

$closer = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 15), 'Closer scope closer is not the comma token');

}//end testFunctionArgument()


/**
* Test arrow functions that use closures.
*
Expand All @@ -194,15 +278,15 @@ public function testClosure()
$this->backfillHelper($token);

$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 60), 'Scope closer is not the semicolon token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 60), 'Scope closer is not the comma token');

$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 60), 'Opener scope closer is not the semicolon token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 60), 'Opener scope closer is not the comma token');

$closer = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 60), 'Closer scope closer is not the semicolon token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 60), 'Closer scope closer is not the comma token');

}//end testClosure()

Expand Down Expand Up @@ -235,6 +319,62 @@ public function testReturnType()
}//end testReturnType()


/**
* Test arrow functions that return a reference.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testReference()
{
$tokens = self::$phpcsFile->getTokens();

$token = $this->getTargetToken('/* testReference */', T_FN);
$this->backfillHelper($token);

$this->assertSame($tokens[$token]['scope_opener'], ($token + 6), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 9), 'Scope closer is not the semicolon token');

$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 6), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the semicolon token');

$closer = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 6), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the semicolon token');

}//end testReference()


/**
* Test arrow functions that are grouped by parenthesis.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testGrouped()
{
$tokens = self::$phpcsFile->getTokens();

$token = $this->getTargetToken('/* testGrouped */', T_FN);
$this->backfillHelper($token);

$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 8), 'Scope closer is not the semicolon token');

$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 8), 'Opener scope closer is not the semicolon token');

$closer = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 8), 'Closer scope closer is not the semicolon token');

}//end testGrouped()


/**
* Test that anonymous class tokens without parenthesis do not get assigned a parenthesis owner.
*
Expand Down

0 comments on commit ae3ffc7

Please sign in to comment.