From b770ed3ed36b751890e4b872e315771b7cee98ca Mon Sep 17 00:00:00 2001 From: Greg Sherwood Date: Tue, 17 Sep 2019 09:26:11 +1000 Subject: [PATCH] Added PSR12.Classes.AnonClassDeclaration sniff to enforce formatting of anon classes Includes refactoring and some message wording changes to other sniffs as the anon class sniff makes use of several others --- package.xml | 6 + .../FunctionCallArgumentSpacingSniff.php | 28 ++- .../Functions/FunctionCallSignatureSniff.php | 2 +- .../Functions/FunctionDeclarationSniff.php | 188 ++++++++------ .../Classes/AnonClassDeclarationSniff.php | 232 ++++++++++++++++++ .../Classes/AnonClassDeclarationUnitTest.inc | 75 ++++++ .../AnonClassDeclarationUnitTest.inc.fixed | 77 ++++++ .../Classes/AnonClassDeclarationUnitTest.php | 71 ++++++ src/Standards/PSR12/ruleset.xml | 2 +- .../Sniffs/Classes/ClassDeclarationSniff.php | 92 +++++-- 10 files changed, 665 insertions(+), 108 deletions(-) create mode 100644 src/Standards/PSR12/Sniffs/Classes/AnonClassDeclarationSniff.php create mode 100644 src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc create mode 100644 src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc.fixed create mode 100644 src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.php diff --git a/package.xml b/package.xml index 23c3416604..6bb521564a 100644 --- a/package.xml +++ b/package.xml @@ -74,6 +74,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Thanks to Mponos George for the contribution - Added Generic.PHP.RequireStrictTypes sniff -- Enforce the use of a strict types declaration in PHP files + - Added PSR12.Classes.AnonClassDeclaration sniff + -- Enforces the formatting of anonymous classes - Added PSR12.Classes.ClosingBrace sniff -- Enforces that closing braces of classes/interfaces/traits/functions are not followed by a comment or statement - Added PSR12.ControlStructures.BooleanOperatorPlacement sniff @@ -1133,6 +1135,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + @@ -1168,6 +1171,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + diff --git a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php index ba4ad3e777..880aab85f0 100644 --- a/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php +++ b/src/Standards/Generic/Sniffs/Functions/FunctionCallArgumentSpacingSniff.php @@ -82,6 +82,26 @@ public function process(File $phpcsFile, $stackPtr) return; } + $this->checkSpacing($phpcsFile, $stackPtr, $openBracket); + + }//end process() + + + /** + * Checks the spacing around commas. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the + * stack passed in $tokens. + * @param int $openBracket The position of the opening bracket + * in the stack passed in $tokens. + * + * @return void + */ + public function checkSpacing(File $phpcsFile, $stackPtr, $openBracket) + { + $tokens = $phpcsFile->getTokens(); + $closeBracket = $tokens[$openBracket]['parenthesis_closer']; $nextSeparator = $openBracket; @@ -117,7 +137,7 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[($nextSeparator - 1)]['code'] === T_WHITESPACE) { $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($nextSeparator - 2), null, true); if (isset(Tokens::$heredocTokens[$tokens[$prev]['code']]) === false) { - $error = 'Space found before comma in function call'; + $error = 'Space found before comma in argument list'; $fix = $phpcsFile->addFixableError($error, $nextSeparator, 'SpaceBeforeComma'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); @@ -135,7 +155,7 @@ public function process(File $phpcsFile, $stackPtr) }//end if if ($tokens[($nextSeparator + 1)]['code'] !== T_WHITESPACE) { - $error = 'No space found after comma in function call'; + $error = 'No space found after comma in argument list'; $fix = $phpcsFile->addFixableError($error, $nextSeparator, 'NoSpaceAfterComma'); if ($fix === true) { $phpcsFile->fixer->addContent($nextSeparator, ' '); @@ -147,7 +167,7 @@ public function process(File $phpcsFile, $stackPtr) if ($tokens[$next]['line'] === $tokens[$nextSeparator]['line']) { $space = $tokens[($nextSeparator + 1)]['length']; if ($space > 1) { - $error = 'Expected 1 space after comma in function call; %s found'; + $error = 'Expected 1 space after comma in argument list; %s found'; $data = [$space]; $fix = $phpcsFile->addFixableError($error, $nextSeparator, 'TooMuchSpaceAfterComma', $data); if ($fix === true) { @@ -159,7 +179,7 @@ public function process(File $phpcsFile, $stackPtr) }//end if }//end while - }//end process() + }//end checkSpacing() }//end class diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php index 5e9c29205d..04ddd538d2 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php @@ -205,12 +205,12 @@ public function isMultiLineCall(File $phpcsFile, $stackPtr, $openBracket, $token */ public function processSingleLineCall(File $phpcsFile, $stackPtr, $openBracket, $tokens) { - // If the function call has no arguments or comments, enforce 0 spaces. $closer = $tokens[$openBracket]['parenthesis_closer']; if ($openBracket === ($closer - 1)) { return; } + // If the function call has no arguments or comments, enforce 0 spaces. $next = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), $closer, true); if ($next === false) { $requiredSpacesAfterOpen = 0; diff --git a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php index 0fa9cf4c30..2b13089592 100644 --- a/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php +++ b/src/Standards/PEAR/Sniffs/Functions/FunctionDeclarationSniff.php @@ -282,6 +282,97 @@ public function processSingleLineDeclaration($phpcsFile, $stackPtr, $tokens) */ public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) { + $this->processArgumentList($phpcsFile, $stackPtr, $this->indent); + + $closeBracket = $tokens[$stackPtr]['parenthesis_closer']; + if ($tokens[$stackPtr]['code'] === T_CLOSURE) { + $use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']); + if ($use !== false) { + $open = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1)); + $closeBracket = $tokens[$open]['parenthesis_closer']; + } + } + + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + return; + } + + // 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'); + if ($fix === true) { + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), $closeBracket, true); + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->addContent($prev, ' {'); + + // If the opener is on a line by itself, removing it will create + // an empty line, so just remove the entire line instead. + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($opener - 1), $closeBracket, true); + $next = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), null, true); + + if ($tokens[$prev]['line'] < $tokens[$opener]['line'] + && $tokens[$next]['line'] > $tokens[$opener]['line'] + ) { + // Clear the whole line. + for ($i = ($prev + 1); $i < $next; $i++) { + if ($tokens[$i]['line'] === $tokens[$opener]['line']) { + $phpcsFile->fixer->replaceToken($i, ''); + } + } + } else { + // Just remove the opener. + $phpcsFile->fixer->replaceToken($opener, ''); + if ($tokens[$next]['line'] === $tokens[$opener]['line']) { + $phpcsFile->fixer->replaceToken(($opener + 1), ''); + } + } + + $phpcsFile->fixer->endChangeset(); + }//end if + } else { + $prev = $tokens[($opener - 1)]; + if ($prev['code'] !== T_WHITESPACE) { + $length = 0; + } else { + $length = strlen($prev['content']); + } + + 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]); + if ($fix === true) { + if ($length === 0) { + $phpcsFile->fixer->addContentBefore($opener, ' '); + } else { + $phpcsFile->fixer->replaceToken(($opener - 1), ' '); + } + } + + return; + }//end if + }//end if + + }//end processMultiLineDeclaration() + + + /** + * Processes multi-line argument list declarations. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * @param int $indent The number of spaces code should be indented. + * @param string $type The type of the token the brackets + * belong to. + * + * @return void + */ + public function processArgumentList($phpcsFile, $stackPtr, $indent, $type='function') + { + $tokens = $phpcsFile->getTokens(); + // We need to work out how far indented the function // declaration itself is, so we can work out how far to // indent parameters. @@ -309,13 +400,13 @@ public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) true ); - if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) { - if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) { - $error = 'The closing parenthesis of a multi-line function declaration must be on a new line'; - $fix = $phpcsFile->addFixableError($error, $closeBracket, 'CloseBracketLine'); - if ($fix === true) { - $phpcsFile->fixer->addNewlineBefore($closeBracket); - } + if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line'] + && $tokens[$prev]['line'] === $tokens[$closeBracket]['line'] + ) { + $error = 'The closing parenthesis of a multi-line '.$type.' declaration must be on a new line'; + $fix = $phpcsFile->addFixableError($error, $closeBracket, 'CloseBracketLine'); + if ($fix === true) { + $phpcsFile->fixer->addNewlineBefore($closeBracket); } } @@ -335,13 +426,13 @@ public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) true ); - if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) { - if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) { - $error = 'The closing parenthesis of a multi-line use declaration must be on a new line'; - $fix = $phpcsFile->addFixableError($error, $closeBracket, 'UseCloseBracketLine'); - if ($fix === true) { - $phpcsFile->fixer->addNewlineBefore($closeBracket); - } + if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line'] + && $tokens[$prev]['line'] === $tokens[$closeBracket]['line'] + ) { + $error = 'The closing parenthesis of a multi-line use declaration must be on a new line'; + $fix = $phpcsFile->addFixableError($error, $closeBracket, 'UseCloseBracketLine'); + if ($fix === true) { + $phpcsFile->fixer->addNewlineBefore($closeBracket); } } }//end if @@ -361,7 +452,7 @@ public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) // as the function. $expectedIndent = $functionIndent; } else { - $expectedIndent = ($functionIndent + $this->indent); + $expectedIndent = ($functionIndent + $indent); } // We changed lines, so this should be a whitespace indent token. @@ -371,7 +462,7 @@ public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) // This is an empty line, so don't check the indent. $foundIndent = $expectedIndent; - $error = 'Blank lines are not allowed in a multi-line function declaration'; + $error = 'Blank lines are not allowed in a multi-line '.$type.' declaration'; $fix = $phpcsFile->addFixableError($error, $i, 'EmptyLine'); if ($fix === true) { $phpcsFile->fixer->replaceToken($i, ''); @@ -381,7 +472,7 @@ public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) } if ($expectedIndent !== $foundIndent) { - $error = 'Multi-line function declaration not indented correctly; expected %s spaces but found %s'; + $error = 'Multi-line '.$type.' declaration not indented correctly; expected %s spaces but found %s'; $data = [ $expectedIndent, $foundIndent, @@ -414,68 +505,7 @@ public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens) } }//end for - if (isset($tokens[$stackPtr]['scope_opener']) === false) { - return; - } - - // 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'); - if ($fix === true) { - $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), $closeBracket, true); - $phpcsFile->fixer->beginChangeset(); - $phpcsFile->fixer->addContent($prev, ' {'); - - // If the opener is on a line by itself, removing it will create - // an empty line, so just remove the entire line instead. - $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($opener - 1), $closeBracket, true); - $next = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), null, true); - - if ($tokens[$prev]['line'] < $tokens[$opener]['line'] - && $tokens[$next]['line'] > $tokens[$opener]['line'] - ) { - // Clear the whole line. - for ($i = ($prev + 1); $i < $next; $i++) { - if ($tokens[$i]['line'] === $tokens[$opener]['line']) { - $phpcsFile->fixer->replaceToken($i, ''); - } - } - } else { - // Just remove the opener. - $phpcsFile->fixer->replaceToken($opener, ''); - if ($tokens[$next]['line'] === $tokens[$opener]['line']) { - $phpcsFile->fixer->replaceToken(($opener + 1), ''); - } - } - - $phpcsFile->fixer->endChangeset(); - }//end if - } else { - $prev = $tokens[($opener - 1)]; - if ($prev['code'] !== T_WHITESPACE) { - $length = 0; - } else { - $length = strlen($prev['content']); - } - - 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]); - if ($fix === true) { - if ($length === 0) { - $phpcsFile->fixer->addContentBefore($opener, ' '); - } else { - $phpcsFile->fixer->replaceToken(($opener - 1), ' '); - } - } - - return; - }//end if - }//end if - - }//end processMultiLineDeclaration() + }//end processArgumentList() }//end class diff --git a/src/Standards/PSR12/Sniffs/Classes/AnonClassDeclarationSniff.php b/src/Standards/PSR12/Sniffs/Classes/AnonClassDeclarationSniff.php new file mode 100644 index 0000000000..6a6941e7c6 --- /dev/null +++ b/src/Standards/PSR12/Sniffs/Classes/AnonClassDeclarationSniff.php @@ -0,0 +1,232 @@ + + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\PSR12\Sniffs\Classes; + +use PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\FunctionCallArgumentSpacingSniff; +use PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes\ClassDeclarationSniff; +use PHP_CodeSniffer\Standards\Squiz\Sniffs\Functions\MultiLineFunctionDeclarationSniff; +use PHP_CodeSniffer\Util\Tokens; +use PHP_CodeSniffer\Files\File; + +class AnonClassDeclarationSniff extends ClassDeclarationSniff +{ + + /** + * The number of spaces code should be indented. + * + * @var integer + */ + public $indent = 4; + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() + { + return [T_ANON_CLASS]; + + }//end register() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + if (isset($tokens[$stackPtr]['scope_opener']) === false) { + return; + } + + $this->processOpen($phpcsFile, $stackPtr); + $this->processClose($phpcsFile, $stackPtr); + + if (isset($tokens[$stackPtr]['parenthesis_opener']) === true) { + $openBracket = $tokens[$stackPtr]['parenthesis_opener']; + if (MultiLineFunctionDeclarationSniff::isMultiLineDeclaration($phpcsFile, $stackPtr, $openBracket, $tokens) === true) { + $this->processMultiLineArgumentList($phpcsFile, $stackPtr); + } else { + $this->processSingleLineArgumentList($phpcsFile, $stackPtr); + } + + FunctionCallArgumentSpacingSniff::checkSpacing($phpcsFile, $stackPtr, $openBracket); + } + + $opener = $tokens[$stackPtr]['scope_opener']; + if ($tokens[$opener]['line'] === $tokens[$stackPtr]['line']) { + return; + } + + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($opener - 1), $stackPtr, true); + + $implements = $phpcsFile->findPrevious(T_IMPLEMENTS, ($opener - 1), $stackPtr); + if ($implements !== false + && $tokens[$opener]['line'] !== $tokens[$implements]['line'] + && $tokens[$opener]['line'] === $tokens[$prev]['line'] + ) { + // Opening brace must be on a new line as implements list wraps. + $error = 'Opening brace must be on the line after the last implemented interface'; + $fix = $phpcsFile->addFixableError($error, $opener, 'OpenBraceSameLine'); + if ($fix === true) { + $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr, true); + $indent = str_repeat(' ', ($tokens[$first]['column'] - 1)); + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken(($prev + 1), ''); + $phpcsFile->fixer->addNewline($prev); + $phpcsFile->fixer->addContentBefore($opener, $indent); + $phpcsFile->fixer->endChangeset(); + } + } + + if ($tokens[$opener]['line'] > ($tokens[$prev]['line'] + 1)) { + // Opening brace is on a new line, so there must be no blank line before it. + $error = 'Opening brace must not be preceded by a blank line'; + $fix = $phpcsFile->addFixableError($error, $opener, 'OpenBraceLine'); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + for ($x = ($prev + 1); $x < $opener; $x++) { + if ($tokens[$x]['line'] === $tokens[$prev]['line']) { + // Maintain existing newline. + continue; + } + + if ($tokens[$x]['line'] === $tokens[$opener]['line']) { + // Maintain existing indent. + break; + } + + $phpcsFile->fixer->replaceToken($x, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + }//end if + + }//end process() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function processSingleLineArgumentList(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $openBracket = $tokens[$stackPtr]['parenthesis_opener']; + $closeBracket = $tokens[$openBracket]['parenthesis_closer']; + if ($openBracket === ($closeBracket - 1)) { + return; + } + + if ($tokens[($openBracket + 1)]['code'] === T_WHITESPACE) { + $error = 'Space after opening parenthesis of single-line argument list prohibited'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterOpenBracket'); + if ($fix === true) { + $phpcsFile->fixer->replaceToken(($openBracket + 1), ''); + } + } + + $spaceBeforeClose = 0; + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBracket - 1), $openBracket, true); + if ($tokens[$prev]['code'] === T_END_HEREDOC || $tokens[$prev]['code'] === T_END_NOWDOC) { + // Need a newline after these tokens, so ignore this rule. + return; + } + + if ($tokens[$prev]['line'] !== $tokens[$closeBracket]['line']) { + $spaceBeforeClose = 'newline'; + } else if ($tokens[($closeBracket - 1)]['code'] === T_WHITESPACE) { + $spaceBeforeClose = $tokens[($closeBracket - 1)]['length']; + } + + if ($spaceBeforeClose !== 0) { + $error = 'Space before closing parenthesis of single-line argument list prohibited'; + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceBeforeCloseBracket'); + if ($fix === true) { + if ($spaceBeforeClose === 'newline') { + $phpcsFile->fixer->beginChangeset(); + + $closingContent = ')'; + + $next = $phpcsFile->findNext(T_WHITESPACE, ($closeBracket + 1), null, true); + if ($tokens[$next]['code'] === T_SEMICOLON) { + $closingContent .= ';'; + for ($i = ($closeBracket + 1); $i <= $next; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + } + + // We want to jump over any whitespace or inline comment and + // move the closing parenthesis after any other token. + $prev = ($closeBracket - 1); + while (isset(Tokens::$emptyTokens[$tokens[$prev]['code']]) === true) { + if (($tokens[$prev]['code'] === T_COMMENT) + && (strpos($tokens[$prev]['content'], '*/') !== false) + ) { + break; + } + + $prev--; + } + + $phpcsFile->fixer->addContent($prev, $closingContent); + + $prevNonWhitespace = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBracket - 1), null, true); + for ($i = ($prevNonWhitespace + 1); $i <= $closeBracket; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); + } else { + $phpcsFile->fixer->replaceToken(($closeBracket - 1), ''); + }//end if + }//end if + }//end if + + }//end processSingleLineArgumentList() + + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token + * in the stack passed in $tokens. + * + * @return void + */ + public function processMultiLineArgumentList(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $openBracket = $tokens[$stackPtr]['parenthesis_opener']; + + MultiLineFunctionDeclarationSniff::processBracket($phpcsFile, $openBracket, $tokens, 'argument'); + MultiLineFunctionDeclarationSniff::processArgumentList($phpcsFile, $stackPtr, $this->indent, 'argument'); + + }//end processMultiLineArgumentList() + + +}//end class diff --git a/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc new file mode 100644 index 0000000000..7b8499b93a --- /dev/null +++ b/src/Standards/PSR12/Tests/Classes/AnonClassDeclarationUnitTest.inc @@ -0,0 +1,75 @@ + + * @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Standards\PSR12\Tests\Classes; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class AnonClassDeclarationUnitTest extends AbstractSniffUnitTest +{ + + + /** + * Returns the lines where errors should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of errors that should occur on that line. + * + * @return array + */ + public function getErrorList() + { + return [ + 28 => 3, + 30 => 1, + 31 => 4, + 32 => 1, + 33 => 1, + 34 => 1, + 35 => 2, + 36 => 1, + 37 => 3, + 39 => 1, + 40 => 1, + 43 => 3, + 44 => 4, + 45 => 1, + 48 => 1, + 52 => 3, + 53 => 1, + 54 => 1, + 55 => 1, + 56 => 2, + 63 => 1, + 75 => 1, + ]; + + }//end getErrorList() + + + /** + * Returns the lines where warnings should occur. + * + * The key of the array should represent the line number and the value + * should represent the number of warnings that should occur on that line. + * + * @return array + */ + public function getWarningList() + { + return []; + + }//end getWarningList() + + +}//end class diff --git a/src/Standards/PSR12/ruleset.xml b/src/Standards/PSR12/ruleset.xml index 5eb9a7563c..da6950c820 100644 --- a/src/Standards/PSR12/ruleset.xml +++ b/src/Standards/PSR12/ruleset.xml @@ -340,7 +340,7 @@ - + diff --git a/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php b/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php index dcb705becc..6da768b092 100644 --- a/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php +++ b/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php @@ -119,27 +119,53 @@ public function processOpen(File $phpcsFile, $stackPtr) break; } - $className = $phpcsFile->findNext(T_STRING, $stackPtr); - - // Spacing of the keyword. - $gap = $tokens[($stackPtr + 1)]['content']; - if (strlen($gap) !== 1) { - $found = strlen($gap); - $error = 'Expected 1 space between %s keyword and %s name; %s found'; - $data = [ - $stackPtrType, - $stackPtrType, - $found, - ]; - - $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword', $data); - if ($fix === true) { - $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); + $className = null; + $checkSpacing = true; + + if ($tokens[$stackPtr]['code'] !== T_ANON_CLASS) { + $className = $phpcsFile->findNext(T_STRING, $stackPtr); + } else { + // Ignore the spacing check if this is a simple anon class. + $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); + if ($next === $tokens[$stackPtr]['scope_opener'] + && $tokens[$next]['line'] > $tokens[$stackPtr]['line'] + ) { + $checkSpacing = false; } } + if ($checkSpacing === true) { + // Spacing of the keyword. + if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { + $gap = 0; + } else if ($tokens[($stackPtr + 2)]['line'] !== $tokens[$stackPtr]['line']) { + $gap = 'newline'; + } else { + $gap = $tokens[($stackPtr + 1)]['length']; + } + + if ($gap !== 1) { + $error = 'Expected 1 space after %s keyword; %s found'; + $data = [ + $stackPtrType, + $gap, + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword', $data); + if ($fix === true) { + if ($gap === 0) { + $phpcsFile->fixer->addContent($stackPtr, ' '); + } else { + $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); + } + } + } + }//end if + // Check after the class/interface name. - if ($tokens[($className + 2)]['line'] === $tokens[$className]['line']) { + if ($className !== null + && $tokens[($className + 2)]['line'] === $tokens[$className]['line'] + ) { $gap = $tokens[($className + 1)]['content']; if (strlen($gap) !== 1) { $found = strlen($gap); @@ -159,18 +185,29 @@ public function processOpen(File $phpcsFile, $stackPtr) $openingBrace = $tokens[$stackPtr]['scope_opener']; // Check positions of the extends and implements keywords. + $compareToken = $stackPtr; + $compareType = 'name'; + if ($tokens[$stackPtr]['code'] === T_ANON_CLASS) { + if (isset($tokens[$stackPtr]['parenthesis_opener']) === true) { + $compareToken = $tokens[$stackPtr]['parenthesis_closer']; + $compareType = 'closing parenthesis'; + } else { + $compareType = 'keyword'; + } + } + foreach (['extends', 'implements'] as $keywordType) { - $keyword = $phpcsFile->findNext(constant('T_'.strtoupper($keywordType)), ($stackPtr + 1), $openingBrace); + $keyword = $phpcsFile->findNext(constant('T_'.strtoupper($keywordType)), ($compareToken + 1), $openingBrace); if ($keyword !== false) { - if ($tokens[$keyword]['line'] !== $tokens[$stackPtr]['line']) { - $error = 'The '.$keywordType.' keyword must be on the same line as the %s name'; + if ($tokens[$keyword]['line'] !== $tokens[$compareToken]['line']) { + $error = 'The '.$keywordType.' keyword must be on the same line as the %s '.$compareType; $data = [$stackPtrType]; $fix = $phpcsFile->addFixableError($error, $keyword, ucfirst($keywordType).'Line', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $comments = []; - for ($i = ($stackPtr + 1); $i < $keyword; ++$i) { + for ($i = ($compareToken + 1); $i < $keyword; ++$i) { if ($tokens[$i]['code'] === T_COMMENT) { $comments[] = trim($tokens[$i]['content']); } @@ -182,7 +219,7 @@ public function processOpen(File $phpcsFile, $stackPtr) } } - $phpcsFile->fixer->addContent($stackPtr, ' '); + $phpcsFile->fixer->addContent($compareToken, ' '); if (empty($comments) === false) { $i = $keyword; while ($tokens[($i + 1)]['line'] === $tokens[$keyword]['line']) { @@ -238,8 +275,16 @@ public function processOpen(File $phpcsFile, $stackPtr) $keywordTokenType, ]; + if ($className !== null) { + $start = $className; + } else if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) { + $start = $tokens[$stackPtr]['parenthesis_closer']; + } else { + $start = $stackPtr; + } + $classNames = []; - $nextClass = $phpcsFile->findNext($find, ($className + 2), ($openingBrace - 1)); + $nextClass = $phpcsFile->findNext($find, ($start + 2), ($openingBrace - 1)); while ($nextClass !== false) { $classNames[] = $nextClass; $nextClass = $phpcsFile->findNext($find, ($nextClass + 1), ($openingBrace - 1)); @@ -463,6 +508,7 @@ public function processClose(File $phpcsFile, $stackPtr) $ignoreTokens = Tokens::$phpcsCommentTokens; $ignoreTokens[] = T_WHITESPACE; $ignoreTokens[] = T_COMMENT; + $ignoreTokens[] = T_SEMICOLON; $nextContent = $phpcsFile->findNext($ignoreTokens, ($closeBrace + 1), null, true); if ($tokens[$nextContent]['content'] !== $phpcsFile->eolChar && $tokens[$nextContent]['line'] === $tokens[$closeBrace]['line']