Skip to content

Commit cdcc622

Browse files
committed
Added PSR12.ControlStructures.ControlStructureSpacing to enforce that spacing and indents are correct inside control structure parenthesis (ref #750)
1 parent d1d8811 commit cdcc622

File tree

6 files changed

+379
-2
lines changed

6 files changed

+379
-2
lines changed

package.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
4848
-- 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
4949
-- If a default is specified, the position of the first token in the default value will be set in a "default_token" array index
5050
-- If a default is specified, the position of the equals sign will be set in a "default_equal_token" array index
51-
-- If the paramater is not the last, the position of the comma will be set in a "comma_token" array index
51+
-- If the param is not the last, the position of the comma will be set in a "comma_token" array index
5252
-- If the param is passed by reference, the position of the reference operator will be set in a "reference_token" array index
5353
-- If the param is variable length, the position of the variadic operator will be set in a "variadic_token" array index
5454
- 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">
6969
-- Enforce the use of a strict types declaration in PHP files
7070
- Added PSR12.ControlStructures.BooleanOperatorPlacement sniff
7171
-- Enforces that boolean operators between conditions are consistently at the start or end of the line
72+
- Added PSR12.ControlStructures.ControlStructureSpacing sniff
73+
-- Enforces that spacing and indents are correct inside control structure parenthesis
7274
- Added PSR12.Files.DeclareStatement sniff
7375
-- Enforces the formatting of declare statements within a file
7476
- Added PSR12.Files.FileHeader sniff
@@ -1120,6 +1122,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
11201122
</dir>
11211123
<dir name="ControlStructures">
11221124
<file baseinstalldir="PHP/CodeSniffer" name="BooleanOperatorPlacementSniff.php" role="php" />
1125+
<file baseinstalldir="PHP/CodeSniffer" name="ControlStructureSpacingSniff.php" role="php" />
11231126
</dir>
11241127
<dir name="Files">
11251128
<file baseinstalldir="PHP/CodeSniffer" name="DeclareStatementSniff.php" role="php" />
@@ -1157,6 +1160,9 @@ http://pear.php.net/dtd/package-2.0.xsd">
11571160
<file baseinstalldir="PHP/CodeSniffer" name="BooleanOperatorPlacementUnitTest.inc" role="test" />
11581161
<file baseinstalldir="PHP/CodeSniffer" name="BooleanOperatorPlacementUnitTest.inc.fixed" role="test" />
11591162
<file baseinstalldir="PHP/CodeSniffer" name="BooleanOperatorPlacementUnitTest.php" role="test" />
1163+
<file baseinstalldir="PHP/CodeSniffer" name="ControlStructureSpacingUnitTest.inc" role="test" />
1164+
<file baseinstalldir="PHP/CodeSniffer" name="ControlStructureSpacingUnitTest.inc.fixed" role="test" />
1165+
<file baseinstalldir="PHP/CodeSniffer" name="ControlStructureSpacingUnitTest.php" role="test" />
11601166
</dir>
11611167
<dir name="Files">
11621168
<file baseinstalldir="PHP/CodeSniffer" name="DeclareStatementUnitTest.inc" role="test" />
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?php
2+
/**
3+
* Checks that control structures have the correct spacing.
4+
*
5+
* @author Greg Sherwood <gsherwood@squiz.net>
6+
* @copyright 2006-2019 Squiz Pty Ltd (ABN 77 084 670 600)
7+
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8+
*/
9+
10+
namespace PHP_CodeSniffer\Standards\PSR12\Sniffs\ControlStructures;
11+
12+
use PHP_CodeSniffer\Sniffs\Sniff;
13+
use PHP_CodeSniffer\Files\File;
14+
use PHP_CodeSniffer\Util\Tokens;
15+
use PHP_CodeSniffer\Standards\PSR2\Sniffs\ControlStructures\ControlStructureSpacingSniff as PSR2Spacing;
16+
17+
class ControlStructureSpacingSniff implements Sniff
18+
{
19+
20+
/**
21+
* The number of spaces code should be indented.
22+
*
23+
* @var integer
24+
*/
25+
public $indent = 4;
26+
27+
28+
/**
29+
* Returns an array of tokens this test wants to listen for.
30+
*
31+
* @return array
32+
*/
33+
public function register()
34+
{
35+
return [
36+
T_IF,
37+
T_WHILE,
38+
T_FOREACH,
39+
T_FOR,
40+
T_SWITCH,
41+
T_ELSE,
42+
T_ELSEIF,
43+
T_CATCH,
44+
];
45+
46+
}//end register()
47+
48+
49+
/**
50+
* Processes this test, when one of its tokens is encountered.
51+
*
52+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
53+
* @param int $stackPtr The position of the current token
54+
* in the stack passed in $tokens.
55+
*
56+
* @return void
57+
*/
58+
public function process(File $phpcsFile, $stackPtr)
59+
{
60+
$tokens = $phpcsFile->getTokens();
61+
62+
if (isset($tokens[$stackPtr]['parenthesis_opener']) === false
63+
|| isset($tokens[$stackPtr]['parenthesis_closer']) === false
64+
) {
65+
return;
66+
}
67+
68+
$parenOpener = $tokens[$stackPtr]['parenthesis_opener'];
69+
$parenCloser = $tokens[$stackPtr]['parenthesis_closer'];
70+
71+
if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']) {
72+
// Conditions are all on the same line, so follow PSR2.
73+
$sniff = new PSR2Spacing();
74+
return $sniff->process($phpcsFile, $stackPtr);
75+
}
76+
77+
$next = $phpcsFile->findNext(T_WHITESPACE, ($parenOpener + 1), $parenCloser, true);
78+
if ($next === false) {
79+
// No conditions; parse error.
80+
return;
81+
}
82+
83+
// Check the first expression.
84+
if ($tokens[$next]['line'] !== ($tokens[$parenOpener]['line'] + 1)) {
85+
$error = 'The first expression of a multi-line control structure must be on the line after the opening parenthesis';
86+
$fix = $phpcsFile->addFixableError($error, $next, 'FirstExpressionLine');
87+
if ($fix === true) {
88+
$phpcsFile->fixer->addNewline($parenOpener);
89+
}
90+
}
91+
92+
// Check the indent of each line.
93+
$first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $stackPtr, true);
94+
$requiredIndent = ($tokens[$first]['column'] + $this->indent - 1);
95+
for ($i = $parenOpener; $i < $parenCloser; $i++) {
96+
if ($tokens[$i]['column'] !== 1
97+
|| $tokens[($i + 1)]['line'] > $tokens[$i]['line']
98+
) {
99+
continue;
100+
}
101+
102+
if (($i + 1) === $parenCloser) {
103+
break;
104+
}
105+
106+
if ($tokens[$i]['code'] !== T_WHITESPACE) {
107+
$foundIndent = 0;
108+
} else {
109+
$foundIndent = $tokens[$i]['length'];
110+
}
111+
112+
if ($foundIndent < $requiredIndent) {
113+
$error = 'Each line in a multi-line control structure must be indented at least once; expected at least %s spaces, but found %s';
114+
$data = [
115+
$requiredIndent,
116+
$foundIndent,
117+
];
118+
$fix = $phpcsFile->addFixableError($error, $i, 'LineIndent', $data);
119+
if ($fix === true) {
120+
$padding = str_repeat(' ', $requiredIndent);
121+
if ($foundIndent === 0) {
122+
$phpcsFile->fixer->addContentBefore($i, $padding);
123+
} else {
124+
$phpcsFile->fixer->replaceToken($i, $padding);
125+
}
126+
}
127+
}
128+
}//end for
129+
130+
// Check the closing parenthesis.
131+
$prev = $phpcsFile->findPrevious(T_WHITESPACE, ($parenCloser - 1), $parenOpener, true);
132+
if ($tokens[$parenCloser]['line'] !== ($tokens[$prev]['line'] + 1)) {
133+
$error = 'The closing parenthesis of a multi-line control structure must be on the line after the last expression';
134+
$fix = $phpcsFile->addFixableError($error, $parenCloser, 'CloseParenthesisLine');
135+
if ($fix === true) {
136+
if ($tokens[$parenCloser]['line'] === $tokens[$prev]['line']) {
137+
$phpcsFile->fixer->addNewlineBefore($parenCloser);
138+
} else {
139+
$phpcsFile->fixer->beginChangeset();
140+
for ($i = ($prev + 1); $i < $parenCloser; $i++) {
141+
// Maintian existing newline.
142+
if ($tokens[$i]['line'] === $tokens[$prev]['line']) {
143+
continue;
144+
}
145+
146+
// Maintain existing indent.
147+
if ($tokens[$i]['line'] === $tokens[$parenCloser]['line']) {
148+
break;
149+
}
150+
151+
$phpcsFile->fixer->replaceToken($i, '');
152+
}
153+
154+
$phpcsFile->fixer->endChangeset();
155+
}
156+
}//end if
157+
}//end if
158+
159+
if ($tokens[$parenCloser]['line'] !== $tokens[$prev]['line']) {
160+
$requiredIndent = ($tokens[$first]['column'] - 1);
161+
$foundIndent = ($tokens[$parenCloser]['column'] - 1);
162+
if ($foundIndent !== $requiredIndent) {
163+
$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';
164+
$data = [
165+
$requiredIndent,
166+
$foundIndent,
167+
];
168+
$fix = $phpcsFile->addFixableError($error, $parenCloser, 'CloseParenthesisIndent', $data);
169+
if ($fix === true) {
170+
$padding = str_repeat(' ', $requiredIndent);
171+
if ($foundIndent === 0) {
172+
$phpcsFile->fixer->addContentBefore($parenCloser, $padding);
173+
} else {
174+
$phpcsFile->fixer->replaceToken(($parenCloser - 1), $padding);
175+
}
176+
}
177+
}
178+
}
179+
180+
}//end process()
181+
182+
183+
}//end class
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
while ( $expr ) {
3+
}
4+
5+
if (
6+
) {
7+
}
8+
9+
while (
10+
$expr1
11+
&& $expr2
12+
) {
13+
}
14+
15+
while (
16+
$expr1
17+
&& $expr2
18+
) {
19+
}
20+
21+
do {
22+
} while ($expr1
23+
&& $expr2) {
24+
}
25+
26+
if (
27+
$expr1
28+
&& $expr2
29+
&& $expr3
30+
) {
31+
while (
32+
$expr1
33+
&& $expr2
34+
) {
35+
}
36+
while (
37+
$expr1
38+
&& $expr2
39+
) {
40+
}
41+
}
42+
43+
while (
44+
$expr1
45+
&& $expr2
46+
47+
48+
) {
49+
}
50+
51+
while (
52+
$expr1
53+
&& $expr2
54+
// comment here
55+
) {
56+
}
57+
58+
for ($i = 0;
59+
$i < 10;
60+
$i++;
61+
) {
62+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
while ($expr) {
3+
}
4+
5+
if (
6+
) {
7+
}
8+
9+
while (
10+
$expr1
11+
&& $expr2
12+
) {
13+
}
14+
15+
while (
16+
$expr1
17+
&& $expr2
18+
) {
19+
}
20+
21+
do {
22+
} while (
23+
$expr1
24+
&& $expr2
25+
) {
26+
}
27+
28+
if (
29+
$expr1
30+
&& $expr2
31+
&& $expr3
32+
) {
33+
while (
34+
$expr1
35+
&& $expr2
36+
) {
37+
}
38+
while (
39+
$expr1
40+
&& $expr2
41+
) {
42+
}
43+
}
44+
45+
while (
46+
$expr1
47+
&& $expr2
48+
) {
49+
}
50+
51+
while (
52+
$expr1
53+
&& $expr2
54+
// comment here
55+
) {
56+
}
57+
58+
for (
59+
$i = 0;
60+
$i < 10;
61+
$i++;
62+
) {
63+
}

0 commit comments

Comments
 (0)