Skip to content

Commit 2ad3390

Browse files
authored
Merge pull request #77 from PHPCSStandards/feature/add-support-for-arrow-functions
Add support for arrow functions
2 parents 024e957 + 020627d commit 2ad3390

28 files changed

+1723
-32
lines changed

PHPCSUtils/BackCompat/BCFile.php

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@
3838
use PHP_CodeSniffer\Files\File;
3939
use PHP_CodeSniffer\Util\Tokens;
4040
use PHPCSUtils\BackCompat\BCTokens;
41+
use PHPCSUtils\BackCompat\Helper;
4142
use PHPCSUtils\Tokens\Collections;
43+
use PHPCSUtils\Utils\FunctionDeclarations;
4244

4345
/**
4446
* PHPCS native utility functions.
@@ -94,6 +96,8 @@ class BCFile
9496
* - PHPCS 3.0.0: Added support for ES6 class/method syntax.
9597
* - PHPCS 3.0.0: The Exception thrown changed from a `PHP_CodeSniffer_Exception` to
9698
* `\PHP_CodeSniffer\Exceptions\RuntimeException`.
99+
* - PHPCS 3.5.3: Allow for functions to be called `fn` for backwards compatibility.
100+
* Related to PHP 7.4 T_FN arrow functions.
97101
*
98102
* Note: For ES6 classes in combination with PHPCS 2.x, passing a `T_STRING` token to
99103
* this method will be accepted for JS files.
@@ -103,6 +107,7 @@ class BCFile
103107
* @see \PHPCSUtils\Utils\ObjectDeclarations::getName() PHPCSUtils native improved version.
104108
*
105109
* @since 1.0.0
110+
* @since 1.0.0-alpha2 Added BC support for PHP 7.4 arrow functions.
106111
*
107112
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
108113
* @param int $stackPtr The position of the declaration token
@@ -153,7 +158,9 @@ public static function getDeclarationName(File $phpcsFile, $stackPtr)
153158

154159
$content = null;
155160
for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) {
156-
if ($tokens[$i]['code'] === T_STRING) {
161+
if ($tokens[$i]['code'] === T_STRING
162+
|| $tokens[$i]['type'] === 'T_FN'
163+
) {
157164
/*
158165
* BC: In PHPCS 2.6.0, in case of live coding, the last token in a file will be tokenized
159166
* as T_STRING, but won't have the `content` index set.
@@ -238,11 +245,13 @@ public static function getDeclarationName(File $phpcsFile, $stackPtr)
238245
* be set in a "variadic_token" array index.
239246
* - PHPCS 3.5.3: Fixed a bug where the "type_hint_end_token" array index for a type hinted
240247
* parameter would bleed through to the next (non-type hinted) parameter.
248+
* - PHPCS 3.5.3: Added support for PHP 7.4 T_FN arrow functions.
241249
*
242250
* @see \PHP_CodeSniffer\Files\File::getMethodParameters() Original source.
243251
* @see \PHPCSUtils\Utils\FunctionDeclarations::getParameters() PHPCSUtils native improved version.
244252
*
245253
* @since 1.0.0
254+
* @since 1.0.0-alpha2 Added BC support for PHP 7.4 arrow functions.
246255
*
247256
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
248257
* @param int $stackPtr The position in the stack of the function token
@@ -251,7 +260,8 @@ public static function getDeclarationName(File $phpcsFile, $stackPtr)
251260
* @return array
252261
*
253262
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified $stackPtr is not of
254-
* type T_FUNCTION, T_CLOSURE, or T_USE.
263+
* type T_FUNCTION, T_CLOSURE, T_USE,
264+
* or T_FN.
255265
*/
256266
public static function getMethodParameters(File $phpcsFile, $stackPtr)
257267
{
@@ -260,15 +270,25 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
260270
if ($tokens[$stackPtr]['code'] !== T_FUNCTION
261271
&& $tokens[$stackPtr]['code'] !== T_CLOSURE
262272
&& $tokens[$stackPtr]['code'] !== T_USE
273+
&& FunctionDeclarations::isArrowFunction($phpcsFile, $stackPtr) === false
263274
) {
264-
throw new RuntimeException('$stackPtr must be of type T_FUNCTION or T_CLOSURE or T_USE');
275+
throw new RuntimeException('$stackPtr must be of type T_FUNCTION or T_CLOSURE or T_USE or T_FN');
265276
}
266277

267278
if ($tokens[$stackPtr]['code'] === T_USE) {
268279
$opener = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($stackPtr + 1));
269280
if ($opener === false || isset($tokens[$opener]['parenthesis_owner']) === true) {
270281
throw new RuntimeException('$stackPtr was not a valid T_USE');
271282
}
283+
} elseif ($tokens[$stackPtr]['code'] === \T_STRING || $tokens[$stackPtr]['type'] === 'T_FN') {
284+
/*
285+
* Arrow function in combination with PHP < 7.4 or PHPCS < 3.5.3.
286+
*/
287+
$opener = $phpcsFile->findNext((Tokens::$emptyTokens + [\T_BITWISE_AND]), ($stackPtr + 1), null, true);
288+
if ($opener === false || $tokens[$opener]['code'] !== T_OPEN_PARENTHESIS) {
289+
// Live coding or syntax error, so no params to find.
290+
return [];
291+
}
272292
} else {
273293
if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) {
274294
// Live coding or syntax error, so no params to find.
@@ -508,11 +528,13 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
508528
* or `true` otherwise.
509529
* - PHPCS 3.5.0: The Exception thrown changed from a `TokenizerException` to
510530
* `\PHP_CodeSniffer\Exceptions\RuntimeException`.
531+
* - PHPCS 3.5.3: Added support for PHP 7.4 T_FN arrow functions.
511532
*
512533
* @see \PHP_CodeSniffer\Files\File::getMethodProperties() Original source.
513534
* @see \PHPCSUtils\Utils\FunctionDeclarations::getProperties() PHPCSUtils native improved version.
514535
*
515536
* @since 1.0.0
537+
* @since 1.0.0-alpha2 Added BC support for PHP 7.4 arrow functions.
516538
*
517539
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
518540
* @param int $stackPtr The position in the stack of the function token to
@@ -521,16 +543,18 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
521543
* @return array
522544
*
523545
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a
524-
* T_FUNCTION or a T_CLOSURE token.
546+
* T_FUNCTION, T_CLOSURE, or T_FN token.
525547
*/
526548
public static function getMethodProperties(File $phpcsFile, $stackPtr)
527549
{
528-
$tokens = $phpcsFile->getTokens();
550+
$tokens = $phpcsFile->getTokens();
551+
$arrowOpenClose = FunctionDeclarations::getArrowFunctionOpenClose($phpcsFile, $stackPtr);
529552

530553
if ($tokens[$stackPtr]['code'] !== T_FUNCTION
531554
&& $tokens[$stackPtr]['code'] !== T_CLOSURE
555+
&& $arrowOpenClose === []
532556
) {
533-
throw new RuntimeException('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
557+
throw new RuntimeException('$stackPtr must be of type T_FUNCTION or T_CLOSURE or T_FN');
534558
}
535559

536560
if ($tokens[$stackPtr]['code'] === T_FUNCTION) {
@@ -595,13 +619,24 @@ public static function getMethodProperties(File $phpcsFile, $stackPtr)
595619
$nullableReturnType = false;
596620
$hasBody = true;
597621

622+
$parenthesisCloser = null;
598623
if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
624+
$parenthesisCloser = $tokens[$stackPtr]['parenthesis_closer'];
625+
} elseif ($arrowOpenClose !== [] && $arrowOpenClose['parenthesis_closer'] !== false) {
626+
// Arrow function in combination with PHP < 7.4 or PHPCS < 3.5.3.
627+
$parenthesisCloser = $arrowOpenClose['parenthesis_closer'];
628+
}
629+
630+
if (isset($parenthesisCloser) === true) {
599631
$scopeOpener = null;
600632
if (isset($tokens[$stackPtr]['scope_opener']) === true) {
601633
$scopeOpener = $tokens[$stackPtr]['scope_opener'];
634+
} elseif ($arrowOpenClose !== [] && $arrowOpenClose['scope_opener'] !== false) {
635+
// Arrow function in combination with PHP < 7.4 or PHPCS < 3.5.3.
636+
$scopeOpener = $arrowOpenClose['scope_opener'];
602637
}
603638

604-
for ($i = $tokens[$stackPtr]['parenthesis_closer']; $i < $phpcsFile->numTokens; $i++) {
639+
for ($i = $parenthesisCloser; $i < $phpcsFile->numTokens; $i++) {
605640
if (($scopeOpener === null && $tokens[$i]['code'] === T_SEMICOLON)
606641
|| ($scopeOpener !== null && $i === $scopeOpener)
607642
) {
@@ -612,6 +647,9 @@ public static function getMethodProperties(File $phpcsFile, $stackPtr)
612647
if ($tokens[$i]['type'] === 'T_NULLABLE'
613648
// Handle nullable tokens in PHPCS < 2.8.0.
614649
|| (defined('T_NULLABLE') === false && $tokens[$i]['code'] === T_INLINE_THEN)
650+
// Handle nullable tokens with arrow functions in PHPCS 2.8.0 - 2.9.0.
651+
|| ($arrowOpenClose !== [] && $tokens[$i]['code'] === T_INLINE_THEN
652+
&& version_compare(Helper::getVersion(), '2.9.1', '<') === true)
615653
) {
616654
$nullableReturnType = true;
617655
}
@@ -625,8 +663,17 @@ public static function getMethodProperties(File $phpcsFile, $stackPtr)
625663
}
626664
}
627665

628-
$end = $phpcsFile->findNext([T_OPEN_CURLY_BRACKET, T_SEMICOLON], $tokens[$stackPtr]['parenthesis_closer']);
629-
$hasBody = $tokens[$end]['code'] === T_OPEN_CURLY_BRACKET;
666+
$bodyTokens = [T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET];
667+
if ($arrowOpenClose !== []) {
668+
$bodyTokens = [T_DOUBLE_ARROW => T_DOUBLE_ARROW];
669+
if (defined('T_FN_ARROW') === true) {
670+
// PHPCS 3.5.3+.
671+
$bodyTokens = [T_FN_ARROW => T_FN_ARROW];
672+
}
673+
}
674+
675+
$end = $phpcsFile->findNext(($bodyTokens + [T_SEMICOLON]), $parenthesisCloser);
676+
$hasBody = isset($bodyTokens[$tokens[$end]['code']]);
630677
}
631678

632679
if ($returnType !== '' && $nullableReturnType === true) {
@@ -923,11 +970,13 @@ public static function getClassProperties(File $phpcsFile, $stackPtr)
923970
* - New by reference was not recognized as a reference.
924971
* - References to class properties with `self::`, `parent::`, `static::`,
925972
* `namespace\ClassName::`, `classname::` were not recognized as references.
973+
* - PHPCS 3.5.3: Added support for PHP 7.4 T_FN arrow functions returning by reference.
926974
*
927975
* @see \PHP_CodeSniffer\Files\File::isReference() Original source.
928976
* @see \PHPCSUtils\Utils\Operators::isReference() PHPCSUtils native improved version.
929977
*
930978
* @since 1.0.0
979+
* @since 1.0.0-alpha2 Added BC support for PHP 7.4 arrow functions.
931980
*
932981
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
933982
* @param int $stackPtr The position of the T_BITWISE_AND token.
@@ -945,7 +994,9 @@ public static function isReference(File $phpcsFile, $stackPtr)
945994

946995
$tokenBefore = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
947996

948-
if ($tokens[$tokenBefore]['code'] === T_FUNCTION) {
997+
if ($tokens[$tokenBefore]['code'] === T_FUNCTION
998+
|| FunctionDeclarations::isArrowFunction($phpcsFile, $tokenBefore) === true
999+
) {
9491000
// Function returns a reference.
9501001
return true;
9511002
}
@@ -1116,10 +1167,12 @@ public static function getTokensAsString(File $phpcsFile, $start, $length, $orig
11161167
* Changelog for the PHPCS native function:
11171168
* - Introduced in PHPCS 2.1.0.
11181169
* - PHPCS 2.6.2: New optional `$ignore` parameter to selectively ignore stop points.
1170+
* - PHPCS 3.5.5: Added support for PHP 7.4 T_FN arrow functions.
11191171
*
11201172
* @see \PHP_CodeSniffer\Files\File::findStartOfStatement() Original source.
11211173
*
11221174
* @since 1.0.0
1175+
* @since 1.0.0-alpha2 Added BC support for PHP 7.4 arrow functions.
11231176
*
11241177
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
11251178
* @param int $start The position to start searching from in the token stack.
@@ -1158,6 +1211,7 @@ public static function findStartOfStatement(File $phpcsFile, $start, $ignore = n
11581211

11591212
if (isset($tokens[$i]['scope_opener']) === true
11601213
&& $i === $tokens[$i]['scope_closer']
1214+
&& $tokens[$i]['code'] !== T_CLOSE_PARENTHESIS
11611215
) {
11621216
// Found the end of the previous scope block.
11631217
return $lastNotEmpty;
@@ -1193,10 +1247,13 @@ public static function findStartOfStatement(File $phpcsFile, $start, $ignore = n
11931247
* - PHPCS 2.7.1: Improved handling of short arrays, PHPCS #1203.
11941248
* - PHPCS 3.3.0: Bug fix: end of statement detection when passed a scope opener, PHPCS #1863.
11951249
* - PHPCS 3.5.0: Improved handling of group use statements.
1250+
* - PHPCS 3.5.3: Added support for PHP 7.4 T_FN arrow functions.
1251+
* - PHPCS 3.5.4: Improved support for PHP 7.4 T_FN arrow functions.
11961252
*
11971253
* @see \PHP_CodeSniffer\Files\File::findEndOfStatement() Original source.
11981254
*
11991255
* @since 1.0.0
1256+
* @since 1.0.0-alpha2 Added BC support for PHP 7.4 arrow functions.
12001257
*
12011258
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
12021259
* @param int $start The position to start searching from in the token stack.
@@ -1251,6 +1308,12 @@ public static function findEndOfStatement(File $phpcsFile, $start, $ignore = nul
12511308
&& ($i === $tokens[$i]['scope_opener']
12521309
|| $i === $tokens[$i]['scope_condition'])
12531310
) {
1311+
if ($tokens[$i]['type'] === 'T_FN') {
1312+
// Minus 1 as the closer can be shared.
1313+
$i = ($tokens[$i]['scope_closer'] - 1);
1314+
continue;
1315+
}
1316+
12541317
if ($i === $start && isset(Tokens::$scopeOpeners[$tokens[$i]['code']]) === true) {
12551318
return $tokens[$i]['scope_closer'];
12561319
}
@@ -1269,6 +1332,19 @@ public static function findEndOfStatement(File $phpcsFile, $start, $ignore = nul
12691332
if ($end !== false) {
12701333
$i = $end;
12711334
}
1335+
} elseif ($tokens[$i]['code'] === T_STRING || $tokens[$i]['type'] === 'T_FN') {
1336+
// Potentially a PHP 7.4 arrow function in combination with PHP < 7.4 or PHPCS < 3.5.3/3.5.4.
1337+
$arrowFunctionOpenClose = FunctionDeclarations::getArrowFunctionOpenClose($phpcsFile, $i);
1338+
if ($arrowFunctionOpenClose !== []
1339+
&& $arrowFunctionOpenClose['scope_closer'] !== false
1340+
) {
1341+
if ($i === $start) {
1342+
return $arrowFunctionOpenClose['scope_closer'];
1343+
}
1344+
1345+
// Minus 1 as the closer can be shared.
1346+
$i = ($arrowFunctionOpenClose['scope_closer'] - 1);
1347+
}
12721348
}
12731349

12741350
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === false) {

PHPCSUtils/Tokens/Collections.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ class Collections
308308
\T_PARENT => \T_PARENT,
309309
\T_NS_SEPARATOR => \T_NS_SEPARATOR,
310310
\T_RETURN_TYPE => \T_RETURN_TYPE, // PHPCS 2.4.0 < 3.3.0.
311-
\T_ARRAY_HINT => \T_ARRAY_HINT, // PHPCS < 2.8.0.
311+
\T_ARRAY_HINT => \T_ARRAY_HINT, // PHPCS < 2.8.0 / PHPCS < 3.5.3 for arrow functions.
312+
\T_ARRAY => \T_ARRAY, // PHPCS < 3.5.4 for select arrow functions.
312313
];
313314

314315
/**

PHPCSUtils/Utils/Arrays.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use PHP_CodeSniffer\Util\Tokens;
1616
use PHPCSUtils\BackCompat\Helper;
1717
use PHPCSUtils\Tokens\Collections;
18+
use PHPCSUtils\Utils\FunctionDeclarations;
1819
use PHPCSUtils\Utils\Lists;
1920

2021
/**
@@ -28,12 +29,16 @@ class Arrays
2829
/**
2930
* The tokens to target to find the double arrow in an array item.
3031
*
32+
* Note: this array does not contain the `T_FN` token as it may or may not exist.
33+
* If it exists, it will be added in the `getDoubleArrowPtr()` function.
34+
*
3135
* @var array <int|string> => <int|string>
3236
*/
3337
private static $doubleArrowTargets = [
3438
\T_DOUBLE_ARROW => \T_DOUBLE_ARROW,
3539
\T_ARRAY => \T_ARRAY,
3640
\T_OPEN_SHORT_ARRAY => \T_OPEN_SHORT_ARRAY,
41+
\T_STRING => \T_STRING, // BC for T_FN token in PHPCS < 3.5.3 icw PHP < 7.4.
3742
];
3843

3944
/**
@@ -236,6 +241,9 @@ public static function getOpenClose(File $phpcsFile, $stackPtr, $isShortArray =
236241
* Expects to be passed the array item start and end tokens as retrieved via
237242
* {@see \PHPCSUtils\Utils\PassedParameters::getParameters()}.
238243
*
244+
* @since 1.0.0
245+
* @since 1.0.0-alpha2 Now allows for arrow functions in arrays.
246+
*
239247
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being examined.
240248
* @param int $start Stack pointer to the start of the array item.
241249
* @param int $end Stack pointer to the end of the array item (inclusive).
@@ -256,6 +264,9 @@ public static function getDoubleArrowPtr(File $phpcsFile, $start, $end)
256264
}
257265

258266
$targets = self::$doubleArrowTargets + Collections::$closedScopes;
267+
if (\defined('T_FN') === true) {
268+
$targets[\T_FN] = \T_FN;
269+
}
259270

260271
$doubleArrow = ($start - 1);
261272
++$end;
@@ -282,7 +293,15 @@ public static function getDoubleArrowPtr(File $phpcsFile, $start, $end)
282293
continue;
283294
}
284295

285-
// Start of nested long/short array.
296+
// BC for PHP 7.4 arrow functions with PHPCS < 3.5.3.
297+
if ($tokens[$doubleArrow]['code'] === \T_STRING
298+
&& FunctionDeclarations::isArrowFunction($phpcsFile, $doubleArrow) === false
299+
) {
300+
// Not an arrow function, continue looking.
301+
continue;
302+
}
303+
304+
// Start of nested long/short array or arrow function.
286305
break;
287306
} while ($doubleArrow < $end);
288307

0 commit comments

Comments
 (0)