Skip to content

Commit 4fca9a9

Browse files
committed
PSR2R.WhiteSpace.ConsistentIndent
1 parent d48aec1 commit 4fca9a9

File tree

7 files changed

+378
-3
lines changed

7 files changed

+378
-3
lines changed

PSR2R/Sniffs/WhiteSpace/ConsistentIndentSniff.php

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,26 @@ public function process(File $phpcsFile, int $stackPtr): void {
6262
return;
6363
}
6464

65-
// Skip control flow keywords that often have blank lines before them
66-
$controlFlowTokens = [T_BREAK, T_CONTINUE, T_RETURN, T_THROW, T_CASE, T_DEFAULT];
67-
if (in_array($tokens[$nextToken]['code'], $controlFlowTokens, true)) {
65+
// Skip case/default - they have special indentation rules in switch statements
66+
if (in_array($tokens[$nextToken]['code'], [T_CASE, T_DEFAULT], true)) {
6867
return;
6968
}
7069

7170
// Get the expected indentation based on scope
7271
$expectedIndent = $this->getExpectedIndent($phpcsFile, $nextToken, $tokens);
7372

73+
// Special handling for break/continue in switch statements
74+
// They can be indented one level deeper (at case body level)
75+
if (in_array($tokens[$nextToken]['code'], [T_BREAK, T_CONTINUE], true)) {
76+
if ($this->isInSwitch($tokens, $nextToken)) {
77+
// Allow one extra level of indentation for break/continue in switch
78+
// (they're typically at the same level as case body code)
79+
if ($currentIndent === $expectedIndent + 1) {
80+
return;
81+
}
82+
}
83+
}
84+
7485
// Check if line is over-indented (more than expected for its scope)
7586
if ($currentIndent > $expectedIndent) {
7687
// Check if this line starts with a continuation operator
@@ -151,6 +162,58 @@ protected function findPreviousContentLine(File $phpcsFile, int $stackPtr, array
151162
return null;
152163
}
153164

165+
/**
166+
* Check if there's a blank line before the current line.
167+
*
168+
* @param \PHP_CodeSniffer\Files\File $phpcsFile
169+
* @param int $stackPtr
170+
* @param array $tokens
171+
*
172+
* @return bool
173+
*/
174+
protected function hasBlankLineBefore(File $phpcsFile, int $stackPtr, array $tokens): bool {
175+
$currentLine = $tokens[$stackPtr]['line'];
176+
177+
// Find the previous line
178+
$prevLine = $currentLine - 1;
179+
if ($prevLine < 1) {
180+
return false;
181+
}
182+
183+
// Check if the previous line is empty (only whitespace or completely blank)
184+
for ($i = $stackPtr - 1; $i >= 0; $i--) {
185+
if ($tokens[$i]['line'] < $prevLine) {
186+
// We've gone past the previous line
187+
break;
188+
}
189+
190+
if ($tokens[$i]['line'] === $prevLine) {
191+
// Found a token on the previous line
192+
if ($tokens[$i]['code'] !== T_WHITESPACE) {
193+
// Previous line has content
194+
return false;
195+
}
196+
}
197+
}
198+
199+
// Previous line was empty (only whitespace or no tokens)
200+
return true;
201+
}
202+
203+
/**
204+
* Check if the token is inside a switch statement.
205+
*
206+
* @param array $tokens
207+
* @param int $stackPtr
208+
*
209+
* @return bool
210+
*/
211+
protected function isInSwitch(array $tokens, int $stackPtr): bool {
212+
$conditions = $tokens[$stackPtr]['conditions'];
213+
214+
return in_array(T_SWITCH, $conditions, true);
215+
}
216+
154217
/**
155218
* Check if this line starts with a continuation operator.
156219
*
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace App\Test;
4+
5+
class ConsistentIndentControlFlowTest {
6+
7+
// Test 1: Orphaned return without blank line (WRONG)
8+
public function testOrphanedReturn() {
9+
if ($condition) {
10+
$x = 1;
11+
}
12+
return $x; // WRONG - orphaned after }
13+
}
14+
15+
// Test 2: Orphaned return with blank line (WRONG)
16+
public function testOrphanedReturnWithBlank() {
17+
if ($condition) {
18+
$y = 2;
19+
}
20+
21+
return $y; // WRONG - orphaned even with blank line
22+
}
23+
24+
// Test 3: Orphaned throw (WRONG)
25+
public function testOrphanedThrow() {
26+
if (!$valid) {
27+
$error = 'Invalid';
28+
}
29+
throw new Exception($error); // WRONG - orphaned
30+
}
31+
32+
// Test 4: Orphaned break outside switch (WRONG)
33+
public function testOrphanedBreakInLoop() {
34+
foreach ($items as $item) {
35+
if ($item === 'stop') {
36+
break; // WRONG - over-indented break in loop
37+
}
38+
}
39+
}
40+
41+
// Test 5: Orphaned continue (WRONG)
42+
public function testOrphanedContinue() {
43+
foreach ($items as $item) {
44+
if ($item < 0) {
45+
continue; // WRONG - over-indented
46+
}
47+
$result[] = $item;
48+
}
49+
}
50+
51+
// Test 6: Multiple orphaned lines (WRONG)
52+
public function testMultipleOrphaned() {
53+
if ($condition) {
54+
$a = 1;
55+
}
56+
$b = 2; // WRONG
57+
$c = 3; // WRONG
58+
return [$a, $b, $c]; // WRONG
59+
}
60+
61+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace App\Test;
4+
5+
class ConsistentIndentControlFlowTest {
6+
7+
// Test 1: Orphaned return without blank line (WRONG)
8+
public function testOrphanedReturn() {
9+
if ($condition) {
10+
$x = 1;
11+
}
12+
return $x; // WRONG - orphaned after }
13+
}
14+
15+
// Test 2: Orphaned return with blank line (WRONG)
16+
public function testOrphanedReturnWithBlank() {
17+
if ($condition) {
18+
$y = 2;
19+
}
20+
21+
return $y; // WRONG - orphaned even with blank line
22+
}
23+
24+
// Test 3: Orphaned throw (WRONG)
25+
public function testOrphanedThrow() {
26+
if (!$valid) {
27+
$error = 'Invalid';
28+
}
29+
throw new Exception($error); // WRONG - orphaned
30+
}
31+
32+
// Test 4: Orphaned break outside switch (WRONG)
33+
public function testOrphanedBreakInLoop() {
34+
foreach ($items as $item) {
35+
if ($item === 'stop') {
36+
break; // WRONG - over-indented break in loop
37+
}
38+
}
39+
}
40+
41+
// Test 5: Orphaned continue (WRONG)
42+
public function testOrphanedContinue() {
43+
foreach ($items as $item) {
44+
if ($item < 0) {
45+
continue; // WRONG - over-indented
46+
}
47+
$result[] = $item;
48+
}
49+
}
50+
51+
// Test 6: Multiple orphaned lines (WRONG)
52+
public function testMultipleOrphaned() {
53+
if ($condition) {
54+
$a = 1;
55+
}
56+
$b = 2; // WRONG
57+
$c = 3; // WRONG
58+
return [$a, $b, $c]; // WRONG
59+
}
60+
61+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace App\Test;
4+
5+
class ConsistentIndentControlFlowNoErrorsTest {
6+
7+
// Test 1: Valid return with correct indentation
8+
public function testValidReturn() {
9+
if ($condition) {
10+
$x = 1;
11+
}
12+
return $x; // CORRECT
13+
}
14+
15+
// Test 2: Valid return with blank line and correct indentation
16+
public function testValidReturnWithBlank() {
17+
$y = 2;
18+
19+
return $y; // CORRECT
20+
}
21+
22+
// Test 3: Valid throw with correct indentation
23+
public function testValidThrow() {
24+
if (!$valid) {
25+
$error = 'Invalid';
26+
}
27+
throw new Exception($error); // CORRECT
28+
}
29+
30+
// Test 4: Valid break in loop
31+
public function testValidBreakInLoop() {
32+
foreach ($items as $item) {
33+
if ($item === 'stop') {
34+
break; // CORRECT
35+
}
36+
}
37+
}
38+
39+
// Test 5: Valid continue in loop
40+
public function testValidContinue() {
41+
foreach ($items as $item) {
42+
if ($item < 0) {
43+
continue; // CORRECT
44+
}
45+
$result[] = $item;
46+
}
47+
}
48+
49+
// Test 6: Break in switch (allowed extra indent at case body level)
50+
public function testBreakInSwitch() {
51+
switch ($value) {
52+
case 1:
53+
$x = 1;
54+
break; // CORRECT
55+
case 2:
56+
$y = 2;
57+
58+
break; // CORRECT - with blank line
59+
}
60+
}
61+
62+
// Test 7: Nested returns
63+
public function testNestedReturns() {
64+
if ($condition1) {
65+
if ($condition2) {
66+
return true;
67+
}
68+
return false;
69+
}
70+
return null;
71+
}
72+
73+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace App\Test;
4+
5+
class ConsistentIndentControlFlowNoErrorsTest {
6+
7+
// Test 1: Valid return with correct indentation
8+
public function testValidReturn() {
9+
if ($condition) {
10+
$x = 1;
11+
}
12+
return $x; // CORRECT
13+
}
14+
15+
// Test 2: Valid return with blank line and correct indentation
16+
public function testValidReturnWithBlank() {
17+
$y = 2;
18+
19+
return $y; // CORRECT
20+
}
21+
22+
// Test 3: Valid throw with correct indentation
23+
public function testValidThrow() {
24+
if (!$valid) {
25+
$error = 'Invalid';
26+
}
27+
throw new Exception($error); // CORRECT
28+
}
29+
30+
// Test 4: Valid break in loop
31+
public function testValidBreakInLoop() {
32+
foreach ($items as $item) {
33+
if ($item === 'stop') {
34+
break; // CORRECT
35+
}
36+
}
37+
}
38+
39+
// Test 5: Valid continue in loop
40+
public function testValidContinue() {
41+
foreach ($items as $item) {
42+
if ($item < 0) {
43+
continue; // CORRECT
44+
}
45+
$result[] = $item;
46+
}
47+
}
48+
49+
// Test 6: Break in switch (allowed extra indent at case body level)
50+
public function testBreakInSwitch() {
51+
switch ($value) {
52+
case 1:
53+
$x = 1;
54+
break; // CORRECT
55+
case 2:
56+
$y = 2;
57+
58+
break; // CORRECT - with blank line
59+
}
60+
}
61+
62+
// Test 7: Nested returns
63+
public function testNestedReturns() {
64+
if ($condition1) {
65+
if ($condition2) {
66+
return true;
67+
}
68+
return false;
69+
}
70+
return null;
71+
}
72+
73+
}

tests/_data/ConsistentIndentNoErrors/after.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,26 @@ public function testContinue($items) {
135135
return true;
136136
}
137137

138+
// Test 12: Array with docblock between elements (should NOT flag)
139+
public function testArrayWithDocblock() {
140+
$config = [
141+
'Datasources' => [
142+
'default' => [
143+
'password' => env('DB_PASSWORD', ''),
144+
'database' => env('DB_DATABASE', 'sandbox_local'), // Set in your app_local.php
145+
],
146+
147+
/**
148+
* The test connection is used during the test suite.
149+
*/
150+
'test' => [
151+
'password' => env('DB_PASSWORD', ''),
152+
'database' => 'cakephp_test',
153+
],
154+
],
155+
];
156+
157+
return $config;
158+
}
159+
138160
}

0 commit comments

Comments
 (0)