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
-