Skip to content

Commit ae3ffc7

Browse files
committed
Fixed support for arrow functions that return by reference + more tests (ref #2523)
1 parent 51afb54 commit ae3ffc7

File tree

3 files changed

+165
-12
lines changed

3 files changed

+165
-12
lines changed

src/Tokenizers/PHP.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1631,7 +1631,9 @@ protected function processAdditional()
16311631
} else if ($this->tokens[$i]['code'] === T_FN) {
16321632
// Possible arrow function.
16331633
for ($x = ($i + 1); $i < $numTokens; $x++) {
1634-
if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
1634+
if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false
1635+
&& $this->tokens[$x]['code'] !== T_BITWISE_AND
1636+
) {
16351637
// Non-whitespace content.
16361638
break;
16371639
}

tests/Core/Tokenizer/BackfillFnTokenTest.inc

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@ $fn1 = fn /* comment here */ ($x) => $x + $y;
1515
/* testFunctionName */
1616
function fn() {}
1717

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

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

24+
/* testChainedFunctionCall */
25+
$result = Collection::from([1, 2])
26+
->map(fn($v) => $v * 2)
27+
->reduce(/* testFunctionArgument */ fn($tmp, $v) => $tmp + $v, 0);
28+
2429
/* testClosure */
2530
$extended = fn($c) => $callable(function() {
2631
for ($x = 1; $x < 10; $x++) {
@@ -35,3 +40,9 @@ $result = array_map(
3540
static fn(int $number) : int => $number + 1,
3641
$numbers
3742
);
43+
44+
/* testReference */
45+
fn&($x) => $x;
46+
47+
/* testGrouped */
48+
(fn($x) => $x) + $y;

tests/Core/Tokenizer/BackfillFnTokenTest.php

Lines changed: 149 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -130,25 +130,53 @@ public function testFunctionName()
130130
*
131131
* @return void
132132
*/
133-
public function testNested()
133+
public function testNestedOuter()
134134
{
135135
$tokens = self::$phpcsFile->getTokens();
136136

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

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

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

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

151-
}//end testNested()
151+
}//end testNestedOuter()
152+
153+
154+
/**
155+
* Test nested arrow functions.
156+
*
157+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
158+
*
159+
* @return void
160+
*/
161+
public function testNestedInner()
162+
{
163+
$tokens = self::$phpcsFile->getTokens();
164+
165+
$token = $this->getTargetToken('/* testNestedInner */', T_FN);
166+
$this->backfillHelper($token);
167+
168+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
169+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 16), 'Scope closer is not the semicolon token');
170+
171+
$opener = $tokens[$token]['scope_opener'];
172+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
173+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 16), 'Opener scope closer is not the semicolon token');
174+
175+
$closer = $tokens[$token]['scope_opener'];
176+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
177+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 16), 'Closer scope closer is not the semicolon token');
178+
179+
}//end testNestedInner()
152180

153181

154182
/**
@@ -179,6 +207,62 @@ public function testFunctionCall()
179207
}//end testFunctionCall()
180208

181209

210+
/**
211+
* Test arrow functions that are included in chained calls.
212+
*
213+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
214+
*
215+
* @return void
216+
*/
217+
public function testChainedFunctionCall()
218+
{
219+
$tokens = self::$phpcsFile->getTokens();
220+
221+
$token = $this->getTargetToken('/* testChainedFunctionCall */', T_FN);
222+
$this->backfillHelper($token);
223+
224+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
225+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 12), 'Scope closer is not the bracket token');
226+
227+
$opener = $tokens[$token]['scope_opener'];
228+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
229+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer is not the bracket token');
230+
231+
$closer = $tokens[$token]['scope_opener'];
232+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
233+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer is not the bracket token');
234+
235+
}//end testChainedFunctionCall()
236+
237+
238+
/**
239+
* Test arrow functions that are used as function arguments.
240+
*
241+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
242+
*
243+
* @return void
244+
*/
245+
public function testFunctionArgument()
246+
{
247+
$tokens = self::$phpcsFile->getTokens();
248+
249+
$token = $this->getTargetToken('/* testFunctionArgument */', T_FN);
250+
$this->backfillHelper($token);
251+
252+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener is not the arrow token');
253+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 15), 'Scope closer is not the comma token');
254+
255+
$opener = $tokens[$token]['scope_opener'];
256+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener is not the arrow token');
257+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 15), 'Opener scope closer is not the comma token');
258+
259+
$closer = $tokens[$token]['scope_opener'];
260+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener is not the arrow token');
261+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 15), 'Closer scope closer is not the comma token');
262+
263+
}//end testFunctionArgument()
264+
265+
182266
/**
183267
* Test arrow functions that use closures.
184268
*
@@ -194,15 +278,15 @@ public function testClosure()
194278
$this->backfillHelper($token);
195279

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

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

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

207291
}//end testClosure()
208292

@@ -235,6 +319,62 @@ public function testReturnType()
235319
}//end testReturnType()
236320

237321

322+
/**
323+
* Test arrow functions that return a reference.
324+
*
325+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
326+
*
327+
* @return void
328+
*/
329+
public function testReference()
330+
{
331+
$tokens = self::$phpcsFile->getTokens();
332+
333+
$token = $this->getTargetToken('/* testReference */', T_FN);
334+
$this->backfillHelper($token);
335+
336+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 6), 'Scope opener is not the arrow token');
337+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 9), 'Scope closer is not the semicolon token');
338+
339+
$opener = $tokens[$token]['scope_opener'];
340+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 6), 'Opener scope opener is not the arrow token');
341+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the semicolon token');
342+
343+
$closer = $tokens[$token]['scope_opener'];
344+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 6), 'Closer scope opener is not the arrow token');
345+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the semicolon token');
346+
347+
}//end testReference()
348+
349+
350+
/**
351+
* Test arrow functions that are grouped by parenthesis.
352+
*
353+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
354+
*
355+
* @return void
356+
*/
357+
public function testGrouped()
358+
{
359+
$tokens = self::$phpcsFile->getTokens();
360+
361+
$token = $this->getTargetToken('/* testGrouped */', T_FN);
362+
$this->backfillHelper($token);
363+
364+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
365+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 8), 'Scope closer is not the semicolon token');
366+
367+
$opener = $tokens[$token]['scope_opener'];
368+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
369+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 8), 'Opener scope closer is not the semicolon token');
370+
371+
$closer = $tokens[$token]['scope_opener'];
372+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
373+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 8), 'Closer scope closer is not the semicolon token');
374+
375+
}//end testGrouped()
376+
377+
238378
/**
239379
* Test that anonymous class tokens without parenthesis do not get assigned a parenthesis owner.
240380
*

0 commit comments

Comments
 (0)