From 7d132c29095c640b259f34bc549552c0cdedac18 Mon Sep 17 00:00:00 2001 From: Martin Price Date: Thu, 27 Jul 2023 10:53:42 +0100 Subject: [PATCH 1/2] Whitespace around control structures --- .../ControlSignatureSniff.php | 295 ++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 Backdrop/Sniffs/ControlStructures/ControlSignatureSniff.php diff --git a/Backdrop/Sniffs/ControlStructures/ControlSignatureSniff.php b/Backdrop/Sniffs/ControlStructures/ControlSignatureSniff.php new file mode 100644 index 0000000..5dfdbbd --- /dev/null +++ b/Backdrop/Sniffs/ControlStructures/ControlSignatureSniff.php @@ -0,0 +1,295 @@ + + * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace Backdrop\Sniffs\ControlStructures; + +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Util\Tokens; + +class ControlSignatureSniff implements Sniff +{ + + /** + * How many spaces should precede the colon if using alternative syntax. + * + * @var integer + */ + public $requiredSpacesBeforeColon = 1; + + /** + * A list of tokenizers this sniff supports. + * + * @var array + */ + public $supportedTokenizers = [ + 'PHP', + 'JS', + ]; + + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return int[] + */ + public function register() + { + return [ + T_TRY, + T_CATCH, + T_FINALLY, + T_DO, + T_WHILE, + T_FOR, + T_IF, + T_FOREACH, + T_ELSE, + T_ELSEIF, + T_SWITCH, + T_MATCH, + ]; + + }//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(); + + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); + if ($nextNonEmpty === false) { + return; + } + + $isAlternative = false; + if (isset($tokens[$stackPtr]['scope_opener']) === true + && $tokens[$tokens[$stackPtr]['scope_opener']]['code'] === T_COLON + ) { + $isAlternative = true; + } + + // Single space after the keyword. + $expected = 1; + if (isset($tokens[$stackPtr]['parenthesis_closer']) === false && $isAlternative === true) { + // Catching cases like: + // if (condition) : ... else: ... endif + // where there is no condition. + $expected = (int) $this->requiredSpacesBeforeColon; + } + + $found = 1; + if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { + $found = 0; + } else if ($tokens[($stackPtr + 1)]['content'] !== ' ') { + if (strpos($tokens[($stackPtr + 1)]['content'], $phpcsFile->eolChar) !== false) { + $found = 'newline'; + } else { + $found = $tokens[($stackPtr + 1)]['length']; + } + } + + if ($found !== $expected) { + $error = 'Expected %s space(s) after %s keyword; %s found'; + $data = [ + $expected, + strtoupper($tokens[$stackPtr]['content']), + $found, + ]; + + $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword', $data); + if ($fix === true) { + if ($found === 0) { + $phpcsFile->fixer->addContent($stackPtr, str_repeat(' ', $expected)); + } else { + $phpcsFile->fixer->replaceToken(($stackPtr + 1), str_repeat(' ', $expected)); + } + } + } + + // Single space after closing parenthesis. + if (isset($tokens[$stackPtr]['parenthesis_closer']) === true + && isset($tokens[$stackPtr]['scope_opener']) === true + ) { + $expected = 1; + if ($isAlternative === true) { + $expected = (int) $this->requiredSpacesBeforeColon; + } + + $closer = $tokens[$stackPtr]['parenthesis_closer']; + $opener = $tokens[$stackPtr]['scope_opener']; + $content = $phpcsFile->getTokensAsString(($closer + 1), ($opener - $closer - 1)); + + if (trim($content) === '') { + if (strpos($content, $phpcsFile->eolChar) !== false) { + $found = 'newline'; + } else { + $found = strlen($content); + } + } else { + $found = '"'.str_replace($phpcsFile->eolChar, '\n', $content).'"'; + } + + if ($found !== $expected) { + $error = 'Expected %s space(s) after closing parenthesis; found %s'; + $data = [ + $expected, + $found, + ]; + + $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceAfterCloseParenthesis', $data); + if ($fix === true) { + $padding = str_repeat(' ', $expected); + if ($closer === ($opener - 1)) { + $phpcsFile->fixer->addContent($closer, $padding); + } else { + $phpcsFile->fixer->beginChangeset(); + if (trim($content) === '') { + $phpcsFile->fixer->addContent($closer, $padding); + if ($found !== 0) { + for ($i = ($closer + 1); $i < $opener; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + } + } else { + $phpcsFile->fixer->addContent($closer, $padding.$tokens[$opener]['content']); + $phpcsFile->fixer->replaceToken($opener, ''); + + if ($tokens[$opener]['line'] !== $tokens[$closer]['line']) { + $next = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), null, true); + if ($tokens[$next]['line'] !== $tokens[$opener]['line']) { + for ($i = ($opener + 1); $i < $next; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + } + } + } + + $phpcsFile->fixer->endChangeset(); + }//end if + }//end if + }//end if + }//end if + + // Single newline after opening brace. + if (isset($tokens[$stackPtr]['scope_opener']) === true) { + $opener = $tokens[$stackPtr]['scope_opener']; + for ($next = ($opener + 1); $next < $phpcsFile->numTokens; $next++) { + $code = $tokens[$next]['code']; + + if ($code === T_WHITESPACE + || ($code === T_INLINE_HTML + && trim($tokens[$next]['content']) === '') + ) { + continue; + } + + // Skip all empty tokens on the same line as the opener. + if ($tokens[$next]['line'] === $tokens[$opener]['line'] + && (isset(Tokens::$emptyTokens[$code]) === true + || $code === T_CLOSE_TAG) + ) { + continue; + } + + // We found the first bit of a code, or a comment on the + // following line. + break; + }//end for + + if ($tokens[$next]['line'] === $tokens[$opener]['line']) { + $error = 'Newline required after opening brace'; + $fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterOpenBrace'); + if ($fix === true) { + $phpcsFile->fixer->beginChangeset(); + for ($i = ($opener + 1); $i < $next; $i++) { + if (trim($tokens[$i]['content']) !== '') { + break; + } + + // Remove whitespace. + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->addContent($opener, $phpcsFile->eolChar); + $phpcsFile->fixer->endChangeset(); + } + }//end if + } else if ($tokens[$stackPtr]['code'] === T_WHILE) { + // Zero spaces after parenthesis closer, but only if followed by a semicolon. + $closer = $tokens[$stackPtr]['parenthesis_closer']; + $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($closer + 1), null, true); + if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === T_SEMICOLON) { + $found = 0; + if ($tokens[($closer + 1)]['code'] === T_WHITESPACE) { + if (strpos($tokens[($closer + 1)]['content'], $phpcsFile->eolChar) !== false) { + $found = 'newline'; + } else { + $found = $tokens[($closer + 1)]['length']; + } + } + + if ($found !== 0) { + $error = 'Expected 0 spaces before semicolon; %s found'; + $data = [$found]; + $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceBeforeSemicolon', $data); + if ($fix === true) { + $phpcsFile->fixer->replaceToken(($closer + 1), ''); + } + } + } + }//end if + + // Only want to check multi-keyword structures from here on. + if ($tokens[$stackPtr]['code'] === T_WHILE) { + if (isset($tokens[$stackPtr]['scope_closer']) !== false) { + return; + } + + $closer = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($closer === false + || $tokens[$closer]['code'] !== T_CLOSE_CURLY_BRACKET + || $tokens[$tokens[$closer]['scope_condition']]['code'] !== T_DO + ) { + return; + } + } else if ($tokens[$stackPtr]['code'] === T_ELSE + || $tokens[$stackPtr]['code'] === T_ELSEIF + || $tokens[$stackPtr]['code'] === T_CATCH + || $tokens[$stackPtr]['code'] === T_FINALLY + ) { + if (isset($tokens[$stackPtr]['scope_opener']) === true + && $tokens[$tokens[$stackPtr]['scope_opener']]['code'] === T_COLON + ) { + // Special case for alternate syntax, where this token is actually + // the closer for the previous block, so there is no spacing to check. + return; + } + + $closer = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); + if ($closer === false || $tokens[$closer]['code'] !== T_CLOSE_CURLY_BRACKET) { + return; + } + } else { + return; + }//end if + + }//end process() + + +}//end class \ No newline at end of file From 3ed0c0be657b13cde04bf5ca89c9212a4ed5abd5 Mon Sep 17 00:00:00 2001 From: Martin Price Date: Thu, 27 Jul 2023 12:17:52 +0100 Subject: [PATCH 2/2] Add forked comment --- Backdrop/Sniffs/ControlStructures/ControlSignatureSniff.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Backdrop/Sniffs/ControlStructures/ControlSignatureSniff.php b/Backdrop/Sniffs/ControlStructures/ControlSignatureSniff.php index 5dfdbbd..a94b84e 100644 --- a/Backdrop/Sniffs/ControlStructures/ControlSignatureSniff.php +++ b/Backdrop/Sniffs/ControlStructures/ControlSignatureSniff.php @@ -1,6 +1,8 @@ * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)