Skip to content

Commit

Permalink
Added PSR12.ControlStructures.ControlStructureSpacing to enforce that…
Browse files Browse the repository at this point in the history
… spacing and indents are correct inside control structure parenthesis (ref #750)
  • Loading branch information
gsherwood committed Sep 5, 2019
1 parent d1d8811 commit cdcc622
Show file tree
Hide file tree
Showing 6 changed files with 379 additions and 2 deletions.
8 changes: 7 additions & 1 deletion package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -1120,6 +1122,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
</dir>
<dir name="ControlStructures">
<file baseinstalldir="PHP/CodeSniffer" name="BooleanOperatorPlacementSniff.php" role="php" />
<file baseinstalldir="PHP/CodeSniffer" name="ControlStructureSpacingSniff.php" role="php" />
</dir>
<dir name="Files">
<file baseinstalldir="PHP/CodeSniffer" name="DeclareStatementSniff.php" role="php" />
Expand Down Expand Up @@ -1157,6 +1160,9 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="PHP/CodeSniffer" name="BooleanOperatorPlacementUnitTest.inc" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="BooleanOperatorPlacementUnitTest.inc.fixed" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="BooleanOperatorPlacementUnitTest.php" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="ControlStructureSpacingUnitTest.inc" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="ControlStructureSpacingUnitTest.inc.fixed" role="test" />
<file baseinstalldir="PHP/CodeSniffer" name="ControlStructureSpacingUnitTest.php" role="test" />
</dir>
<dir name="Files">
<file baseinstalldir="PHP/CodeSniffer" name="DeclareStatementUnitTest.inc" role="test" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php
/**
* Checks that control structures have the correct spacing.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @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
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
while ( $expr ) {
}

if (
) {
}

while (
$expr1
&& $expr2
) {
}

while (
$expr1
&& $expr2
) {
}

do {
} while ($expr1
&& $expr2) {
}

if (
$expr1
&& $expr2
&& $expr3
) {
while (
$expr1
&& $expr2
) {
}
while (
$expr1
&& $expr2
) {
}
}

while (
$expr1
&& $expr2


) {
}

while (
$expr1
&& $expr2
// comment here
) {
}

for ($i = 0;
$i < 10;
$i++;
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
while ($expr) {
}

if (
) {
}

while (
$expr1
&& $expr2
) {
}

while (
$expr1
&& $expr2
) {
}

do {
} while (
$expr1
&& $expr2
) {
}

if (
$expr1
&& $expr2
&& $expr3
) {
while (
$expr1
&& $expr2
) {
}
while (
$expr1
&& $expr2
) {
}
}

while (
$expr1
&& $expr2
) {
}

while (
$expr1
&& $expr2
// comment here
) {
}

for (
$i = 0;
$i < 10;
$i++;
) {
}
Loading

0 comments on commit cdcc622

Please sign in to comment.