diff --git a/package.xml b/package.xml index 1a2f26e003..d2dcfc2910 100644 --- a/package.xml +++ b/package.xml @@ -48,7 +48,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- If a type hint is specified, the position of the last token in the hint will be set in a "type_hint_end_token" array index -- If a default is specified, the position of the first token in the default value will be set in a "default_token" array index -- If a default is specified, the position of the equals sign will be set in a "default_equal_token" array index - -- If the paramater is not the last, the position of the comma will be set in a "comma_token" array index + -- If the param is not the last, the position of the comma will be set in a "comma_token" array index -- If the param is passed by reference, the position of the reference operator will be set in a "reference_token" array index -- If the param is variable length, the position of the variadic operator will be set in a "variadic_token" array index - The T_LIST token and it's opening and closing parentheses now contain references to each other in the tokens array @@ -69,6 +69,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> -- Enforce the use of a strict types declaration in PHP files - Added PSR12.ControlStructures.BooleanOperatorPlacement sniff -- Enforces that boolean operators between conditions are consistently at the start or end of the line + - Added PSR12.ControlStructures.ControlStructureSpacing sniff + -- Enforces that spacing and indents are correct inside control structure parenthesis - Added PSR12.Files.DeclareStatement sniff -- Enforces the formatting of declare statements within a file - Added PSR12.Files.FileHeader sniff @@ -1120,6 +1122,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + @@ -1157,6 +1160,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + diff --git a/src/Standards/PSR12/Sniffs/ControlStructures/ControlStructureSpacingSniff.php b/src/Standards/PSR12/Sniffs/ControlStructures/ControlStructureSpacingSniff.php new file mode 100644 index 0000000000..d7fa033b29 --- /dev/null +++ b/src/Standards/PSR12/Sniffs/ControlStructures/ControlStructureSpacingSniff.php @@ -0,0 +1,183 @@ + + * @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\ControlStructures; + +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Util\Tokens; +use PHP_CodeSniffer\Standards\PSR2\Sniffs\ControlStructures\ControlStructureSpacingSniff as PSR2Spacing; + +class ControlStructureSpacingSniff implements Sniff +{ + + /** + * 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_IF, + T_WHILE, + T_FOREACH, + T_FOR, + T_SWITCH, + T_ELSE, + T_ELSEIF, + T_CATCH, + ]; + + }//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]['parenthesis_opener']) === false + || isset($tokens[$stackPtr]['parenthesis_closer']) === false + ) { + return; + } + + $parenOpener = $tokens[$stackPtr]['parenthesis_opener']; + $parenCloser = $tokens[$stackPtr]['parenthesis_closer']; + + if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']) { + // Conditions are all on the same line, so follow PSR2. + $sniff = new PSR2Spacing(); + return $sniff->process($phpcsFile, $stackPtr); + } + + $next = $phpcsFile->findNext(T_WHITESPACE, ($parenOpener + 1), $parenCloser, true); + if ($next === false) { + // No conditions; parse error. + return; + } + + // Check the first expression. + if ($tokens[$next]['line'] !== ($tokens[$parenOpener]['line'] + 1)) { + $error = 'The first expression of a multi-line control structure must be on the line after the opening parenthesis'; + $fix = $phpcsFile->addFixableError($error, $next, 'FirstExpressionLine'); + if ($fix === true) { + $phpcsFile->fixer->addNewline($parenOpener); + } + } + + // Check the indent of each line. + $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr, true); + $requiredIndent = ($tokens[$first]['column'] + $this->indent - 1); + for ($i = $parenOpener; $i < $parenCloser; $i++) { + if ($tokens[$i]['column'] !== 1 + || $tokens[($i + 1)]['line'] > $tokens[$i]['line'] + ) { + continue; + } + + if (($i + 1) === $parenCloser) { + break; + } + + if ($tokens[$i]['code'] !== T_WHITESPACE) { + $foundIndent = 0; + } else { + $foundIndent = $tokens[$i]['length']; + } + + if ($foundIndent < $requiredIndent) { + $error = 'Each line in a multi-line control structure must be indented at least once; expected at least %s spaces, but found %s'; + $data = [ + $requiredIndent, + $foundIndent, + ]; + $fix = $phpcsFile->addFixableError($error, $i, 'LineIndent', $data); + if ($fix === true) { + $padding = str_repeat(' ', $requiredIndent); + if ($foundIndent === 0) { + $phpcsFile->fixer->addContentBefore($i, $padding); + } else { + $phpcsFile->fixer->replaceToken($i, $padding); + } + } + } + }//end for + + // Check the closing parenthesis. + $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($parenCloser - 1), $parenOpener, true); + if ($tokens[$parenCloser]['line'] !== ($tokens[$prev]['line'] + 1)) { + $error = 'The closing parenthesis of a multi-line control structure must be on the line after the last expression'; + $fix = $phpcsFile->addFixableError($error, $parenCloser, 'CloseParenthesisLine'); + if ($fix === true) { + if ($tokens[$parenCloser]['line'] === $tokens[$prev]['line']) { + $phpcsFile->fixer->addNewlineBefore($parenCloser); + } else { + $phpcsFile->fixer->beginChangeset(); + for ($i = ($prev + 1); $i < $parenCloser; $i++) { + // Maintian existing newline. + if ($tokens[$i]['line'] === $tokens[$prev]['line']) { + continue; + } + + // Maintain existing indent. + if ($tokens[$i]['line'] === $tokens[$parenCloser]['line']) { + break; + } + + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->endChangeset(); + } + }//end if + }//end if + + if ($tokens[$parenCloser]['line'] !== $tokens[$prev]['line']) { + $requiredIndent = ($tokens[$first]['column'] - 1); + $foundIndent = ($tokens[$parenCloser]['column'] - 1); + if ($foundIndent !== $requiredIndent) { + $error = 'The closing parenthesis of a multi-line control structure must be indented to the same level as start of the control structure; expected %s spaces but found %s'; + $data = [ + $requiredIndent, + $foundIndent, + ]; + $fix = $phpcsFile->addFixableError($error, $parenCloser, 'CloseParenthesisIndent', $data); + if ($fix === true) { + $padding = str_repeat(' ', $requiredIndent); + if ($foundIndent === 0) { + $phpcsFile->fixer->addContentBefore($parenCloser, $padding); + } else { + $phpcsFile->fixer->replaceToken(($parenCloser - 1), $padding); + } + } + } + } + + }//end process() + + +}//end class diff --git a/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc b/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc new file mode 100644 index 0000000000..7d6f8841e8 --- /dev/null +++ b/src/Standards/PSR12/Tests/ControlStructures/ControlStructureSpacingUnitTest.inc @@ -0,0 +1,62 @@ + + * @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\ControlStructures; + +use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest; + +class ControlStructureSpacingUnitTest 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 [ + 2 => 2, + 16 => 1, + 17 => 1, + 18 => 1, + 22 => 1, + 23 => 1, + 32 => 1, + 33 => 1, + 34 => 1, + 37 => 1, + 38 => 1, + 39 => 1, + 48 => 2, + 58 => 1, + 59 => 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 3426f880e6..f4c9714d6d 100644 --- a/src/Standards/PSR12/ruleset.xml +++ b/src/Standards/PSR12/ruleset.xml @@ -235,7 +235,6 @@ 0 -