Skip to content

Commit a223f2f

Browse files
author
Thibaud Fabre
committed
Add scope formatting sniff (proper blank line wrapping of blocks)
1 parent 5d50e4c commit a223f2f

4 files changed

Lines changed: 116 additions & 6 deletions

File tree

src/Aztech/Sniffs/Formatting/ReturnFormattingSniff.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class ReturnFormattingSniff implements \PHP_CodeSniffer_Sniff
1717

1818
public function register()
1919
{
20-
return array(T_RETURN);
20+
return array(T_RETURN, T_BREAK, T_CONTINUE);
2121
}
2222

2323
public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
@@ -43,16 +43,16 @@ private function processTokensBeforeReturn(\PHP_CodeSniffer_File $phpcsFile, $st
4343
}
4444

4545
if ($startToken['code'] == T_OPEN_CURLY_BRACKET && $newLineCount > 1) {
46-
$error = 'Additional blank lines found before return statement';
46+
$error = 'Additional blank lines found before this statement';
4747
$phpcsFile->addError($error, $stackPtr, 'ExtraBlankLines');
4848
}
4949
elseif ($startToken['code'] == T_SEMICOLON || $startToken['code'] == T_CLOSE_CURLY_BRACKET) {
5050
if ($newLineCount > 2) {
51-
$error = 'Additional blank lines found before return statement';
51+
$error = 'Additional blank lines found before this statement';
5252
$phpcsFile->addError($error, $stackPtr, 'ExtraBlankLines');
5353
}
5454
elseif ($newLineCount < 2) {
55-
$error = 'There must be exactly one blank line before this return statement.';
55+
$error = 'There must be exactly one blank line before this statement.';
5656
$phpcsFile->addError($error, $stackPtr, 'MissingBlankLines');
5757
}
5858
}
@@ -70,7 +70,7 @@ private function processTokensAfterReturn(\PHP_CodeSniffer_File $phpcsFile, $sta
7070

7171
foreach ($it as $ptr => $token) {
7272
if (! in_array($token['code'], $this->allowedTypes)) {
73-
$error = 'Return statement should be the last statement in scope';
73+
$error = 'Statement should be the last statement in scope';
7474
$phpcsFile->addError($error, $ptr, 'NotLastInScope');
7575

7676
break;
@@ -82,7 +82,7 @@ private function processTokensAfterReturn(\PHP_CodeSniffer_File $phpcsFile, $sta
8282
}
8383

8484
if ($newLineCount > 1) {
85-
$error = 'Additional blank lines found after return statement';
85+
$error = 'Additional blank lines found after statement';
8686
$phpcsFile->addError($error, $stackPtr, 'ExtraBlankLines');
8787
}
8888
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace Aztech\Sniffs\Formatting;
4+
5+
use Aztech\Sniffs\TokenIterator;
6+
7+
/**
8+
* Sniff to detect that there are no consecutive blank lines in code.
9+
*
10+
* @author thibaud
11+
*/
12+
class ScopeFormattingSniff implements \PHP_CodeSniffer_Sniff
13+
{
14+
15+
public function register()
16+
{
17+
return [
18+
T_FUNCTION
19+
];
20+
}
21+
22+
public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
23+
{
24+
$scope = $phpcsFile->getDeclarationName($stackPtr);
25+
$class = $phpcsFile->getDeclarationName($phpcsFile->findPrevious([ T_CLASS, T_INTERFACE], $stackPtr));
26+
27+
$tokens = $phpcsFile->getTokens();
28+
$function = $tokens[$stackPtr];
29+
30+
$scopeTypes = [
31+
T_IF,
32+
T_FOR,
33+
T_FOREACH,
34+
T_SWITCH
35+
];
36+
37+
$functionStartPtr = $function['scope_opener'];
38+
$functionEndPtr = $function['scope_closer'];
39+
$ptr = $functionStartPtr;
40+
41+
while ($ptr = $phpcsFile->findNext($scopeTypes, $ptr + 1, $functionEndPtr)) {
42+
$this->getBlankLineCountBefore($phpcsFile, $tokens, $functionStartPtr, $ptr);
43+
$this->getBlankLineCountAfter($phpcsFile, $tokens, $tokens[$ptr]['scope_closer'], $functionEndPtr);
44+
}
45+
}
46+
47+
private function getBlankLineCountBefore(\PHP_CodeSniffer_File $phpcsFile, $tokens, $startPtr, $endPtr)
48+
{
49+
$it = new TokenIterator($tokens, $startPtr, $endPtr);
50+
$brokenBy = null;
51+
$newLines = 0;
52+
53+
foreach ($it->reverse() as $ptr => $token) {
54+
if ($token['code'] == T_SEMICOLON || $token['code'] == T_OPEN_CURLY_BRACKET || $token['code'] == T_CLOSE_CURLY_BRACKET) {
55+
$brokenBy = $token;
56+
57+
break;
58+
}
59+
60+
if ($token['code'] == T_WHITESPACE) {
61+
$content = str_replace(' ', '', $token['content']);
62+
$newLines += substr_count($content, PHP_EOL);
63+
}
64+
}
65+
66+
if ($newLines != 2 && $brokenBy['code'] != T_OPEN_CURLY_BRACKET) {
67+
$error = 'Control blocks should be preceded by exactly one blank line (' . max(0, $newLines - 1) . ' found)';
68+
$phpcsFile->addError($error, $endPtr, 'NotExactlyOneBlankLine');
69+
}
70+
71+
elseif ($newLines > 1 && $brokenBy['code'] == T_OPEN_CURLY_BRACKET) {
72+
$error = 'Immediately nested control blocks should not be preceded by blank lines (' . max(0, $newLines - 1) . ' found)';
73+
$phpcsFile->addError($error, $ptr + 2, 'ExtraBlankLines');
74+
}
75+
}
76+
77+
private function getBlankLineCountAfter(\PHP_CodeSniffer_File $phpcsFile, $tokens, $startPtr, $endPtr)
78+
{
79+
$it = new TokenIterator($tokens, $startPtr, $endPtr);
80+
$brokenBy = null;
81+
$newLines = 0;
82+
83+
foreach ($it as $ptr => $token) {
84+
if ($token['code'] == T_SEMICOLON || $token['code'] == T_OPEN_CURLY_BRACKET || $token['code'] == T_CLOSE_CURLY_BRACKET) {
85+
$brokenBy = $token;
86+
87+
break;
88+
}
89+
90+
if ($token['code'] == T_WHITESPACE) {
91+
$content = str_replace(' ', '', $token['content']);
92+
$newLines += substr_count($content, PHP_EOL);
93+
}
94+
95+
}
96+
97+
if ($newLines != 2 && $brokenBy['code'] != T_CLOSE_CURLY_BRACKET) {
98+
$error = 'Control blocks should be followed by exactly one blank line (' . max(0, $newLines - 1) . ' found)';
99+
$phpcsFile->addError($error, $endPtr, 'NotExactlyOneBlankLine');
100+
}
101+
}
102+
}

src/Aztech/Sniffs/TokenIterator.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,11 @@ public function __construct($tokens, $start, $stop)
88
{
99
parent::__construct(array_slice($tokens, $start, $stop - $start, true));
1010
}
11+
12+
public function reverse()
13+
{
14+
$items = array_reverse($this->items, true);
15+
16+
return new StandardIterator($items);
17+
}
1118
}

src/Aztech/ruleset.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<rule ref="Aztech.Commenting.FunctionComment" />
55
<rule ref="Aztech.Formatting.NoConsecutiveBlankLines" />
66
<rule ref="Aztech.Formatting.ReturnFormatting" />
7+
<rule ref="Aztech.Formatting.ScopeFormatting" />
78
<rule ref="Generic.Classes.DuplicateClassName" />
89
<rule ref="Generic.CodeAnalysis.EmptyStatement" />
910
<rule ref="Generic.CodeAnalysis.ForLoopShouldBeWhileLoop" />

0 commit comments

Comments
 (0)