From 87aa11f0bd2becbb7073e4eaa62a1d9b8f54c953 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 9 Sep 2022 16:57:42 +0200 Subject: [PATCH] PEAR/FunctionDeclaration: examine arrow function declarations PHP 7.4 arrow functions were not yet being taken into account for this sniff. Fixed now. Includes unit tests. --- .../Functions/FunctionDeclarationSniff.php | 53 ++++++++++++++----- .../Functions/FunctionDeclarationUnitTest.inc | 21 ++++++++ .../FunctionDeclarationUnitTest.inc.fixed | 21 ++++++++ .../Functions/FunctionDeclarationUnitTest.php | 5 ++ 4 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php index bd59a7cd90..52fb1c1b9e 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php @@ -46,6 +46,7 @@ public function register() return [ T_FUNCTION, T_CLOSURE, + T_FN, ]; }//end register() @@ -75,7 +76,9 @@ public function process(File $phpcsFile, $stackPtr) $openBracket = $tokens[$stackPtr]['parenthesis_opener']; $closeBracket = $tokens[$stackPtr]['parenthesis_closer']; - if (strtolower($tokens[$stackPtr]['content']) === 'function') { + if (strtolower($tokens[$stackPtr]['content']) === 'function' + || strtolower($tokens[$stackPtr]['content']) === 'fn' + ) { // Must be one space after the FUNCTION keyword. if ($tokens[($stackPtr + 1)]['content'] === $phpcsFile->eolChar) { $spaces = 'newline'; @@ -86,9 +89,13 @@ public function process(File $phpcsFile, $stackPtr) } if ($spaces !== 1) { - $error = 'Expected 1 space after FUNCTION keyword; %s found'; - $data = [$spaces]; - $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterFunction', $data); + $error = 'Expected 1 space after %s keyword; %s found'; + $data = [ + strtoupper($tokens[$stackPtr]['content']), + $spaces, + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterFunction', $data); if ($fix === true) { if ($spaces === 0) { $phpcsFile->fixer->addContent($stackPtr, ' '); @@ -99,9 +106,9 @@ public function process(File $phpcsFile, $stackPtr) } }//end if - // Must be no space before the opening parenthesis. For closures, this is - // enforced by the previous check because there is no content between the keywords - // and the opening parenthesis. + // Must be no space before the opening parenthesis. For closures and arrow functions, + // this is enforced by the previous check because there is no content between + // the keywords and the opening parenthesis. // Unfinished closures are tokenized as T_FUNCTION however, and can be excluded // by checking for the scope_opener. if ($tokens[$stackPtr]['code'] === T_FUNCTION @@ -257,6 +264,10 @@ public function isMultiLineDeclaration($phpcsFile, $stackPtr, $openBracket, $tok */ public function processSingleLineDeclaration($phpcsFile, $stackPtr, $tokens) { + if ($tokens[$stackPtr]['code'] === T_FN) { + return; + } + if ($tokens[$stackPtr]['code'] === T_CLOSURE) { $sniff = new OpeningFunctionBraceKernighanRitchieSniff(); } else { @@ -300,12 +311,20 @@ public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) // The opening brace needs to be one space away from the closing parenthesis. $opener = $tokens[$stackPtr]['scope_opener']; if ($tokens[$opener]['line'] !== $tokens[$closeBracket]['line']) { - $error = 'The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line'; - $fix = $phpcsFile->addFixableError($error, $opener, 'NewlineBeforeOpenBrace'); + $error = 'The closing parenthesis and the %s of a multi-line function declaration must be on the same line'; + $code = 'NewlineBeforeOpenBrace'; + $data = ['opening brace']; + + if ($tokens[$stackPtr]['code'] === T_FN) { + $code = 'NewlineBeforeArrow'; + $data = ['arrow']; + } + + $fix = $phpcsFile->addFixableError($error, $opener, $code, $data); if ($fix === true) { $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), $closeBracket, true); $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->addContent($prev, ' {'); + $phpcsFile->fixer->addContent($prev, ' '.$tokens[$opener]['content']); // If the opener is on a line by itself, removing it will create // an empty line, so just remove the entire line instead. @@ -340,8 +359,18 @@ public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) } if ($length !== 1) { - $error = 'There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration; found %s spaces'; - $fix = $phpcsFile->addFixableError($error, ($opener - 1), 'SpaceBeforeOpenBrace', [$length]); + $error = 'There must be a single space between the closing parenthesis and the %s of a multi-line function declaration; found %s spaces'; + $code = 'SpaceBeforeOpenBrace'; + $data = ['opening brace']; + + if ($tokens[$stackPtr]['code'] === T_FN) { + $code = 'SpaceBeforeArrow'; + $data = ['arrow']; + } + + $data[] = $length; + + $fix = $phpcsFile->addFixableError($error, ($opener - 1), $code, $data); if ($fix === true) { if ($length === 0) { $phpcsFile->fixer->addContentBefore($opener, ' '); diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc index 02e0a20dbf..4e3aa1ad32 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc @@ -418,3 +418,24 @@ class ConstructorPropertyPromotionMultiLineAttributesIncorrectIndent // Do something. } } + +$arrowNoArgs = fn () => $retrievedfromscope; + +$arrowSingleLineArgs = fn (Type $param1, int $param2, string $param3): \ReturnType => $retrievedfromscope; + +$arrowMultiLineArgs = fn ( + $longVar1, + $longerVar2, + &...$muchLongerVar3 +) => $longVar1; + +$arrowNoArgs = fn( ) + => $retrievedfromscope; + +$arrowSingleLineArgs = fn( Type $param1 , int $param2, string $param3 + ) : \ReturnType => $retrievedfromscope; + +$arrowMultiLineArgs = fn ( + $longVar1, $longerVar2, + + & ... $muchLongerVar3) => $longVar1; diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed index 0d67e9f758..fa9281c575 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.inc.fixed @@ -416,3 +416,24 @@ class ConstructorPropertyPromotionMultiLineAttributesIncorrectIndent // Do something. } } + +$arrowNoArgs = fn () => $retrievedfromscope; + +$arrowSingleLineArgs = fn (Type $param1, int $param2, string $param3): \ReturnType => $retrievedfromscope; + +$arrowMultiLineArgs = fn ( + $longVar1, + $longerVar2, + &...$muchLongerVar3 +) => $longVar1; + +$arrowNoArgs = fn ( ) + => $retrievedfromscope; + +$arrowSingleLineArgs = fn ( Type $param1 , int $param2, string $param3 +) : \ReturnType => $retrievedfromscope; + +$arrowMultiLineArgs = fn ( + $longVar1, $longerVar2, + & ... $muchLongerVar3 +) => $longVar1; diff --git a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php index 01ab3e8482..210c60938b 100644 --- a/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php +++ b/src/Standards/PEAR/Tests/Functions/FunctionDeclarationUnitTest.php @@ -99,6 +99,11 @@ public function getErrorList($testFile='FunctionDeclarationUnitTest.inc') 371 => 1, 402 => 1, 406 => 1, + 432 => 1, + 435 => 1, + 436 => 2, + 440 => 1, + 441 => 2, ]; } else { $errors = [