Skip to content

Commit 4cd153d

Browse files
committed
Added SwitchCommentSpacingSniff
1 parent 22b455b commit 4cd153d

File tree

4 files changed

+217
-1
lines changed

4 files changed

+217
-1
lines changed

InfinityloopCodingStandard/Sniffs/ControlStructures/RequireMultiLineNullCoalesceSniff.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
<?php declare(strict_types = 1);
1+
<?php
2+
3+
declare(strict_types = 1);
24

35
namespace InfinityloopCodingStandard\Sniffs\ControlStructures;
46

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace InfinityloopCodingStandard\Sniffs\ControlStructures;
6+
7+
use PHP_CodeSniffer\Files\File;
8+
use PHP_CodeSniffer\Sniffs\Sniff;
9+
use SlevomatCodingStandard\Helpers\TokenHelper;
10+
use function array_merge;
11+
use function in_array;
12+
use function strlen;
13+
use function substr;
14+
use const T_OPEN_TAG;
15+
use const T_OPEN_TAG_WITH_ECHO;
16+
use const T_WHITESPACE;
17+
use const T_CASE;
18+
use const T_DEFAULT;
19+
use const T_SWITCH;
20+
21+
class SwitchCommentSpacingSniff implements Sniff
22+
{
23+
public const CODE_SWITCH_COMMENT_INVALID_FORMAT = 'SwitchCommentInvalidSpacing';
24+
25+
private const TAB_INDENT = "\t";
26+
private const SPACES_INDENT = ' ';
27+
28+
/**
29+
* @return array<int, (int|string)>
30+
*/
31+
public function register(): array
32+
{
33+
return [
34+
T_SWITCH,
35+
];
36+
}
37+
38+
/**
39+
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
40+
* @param File $phpcsFile
41+
* @param int $stackPtr
42+
*/
43+
public function process(File $phpcsFile, $stackPtr): void
44+
{
45+
$tokens = $phpcsFile->getTokens();
46+
47+
if (isset($tokens[$stackPtr]['scope_opener']) === false
48+
|| isset($tokens[$stackPtr]['scope_closer']) === false
49+
) {
50+
return;
51+
}
52+
53+
$switch = $tokens[$stackPtr];
54+
$nextCase = $stackPtr;
55+
56+
while (($nextCase = $this->findNextCase($phpcsFile, ($nextCase + 1), $switch['scope_closer'])) !== false) {
57+
$opener = $tokens[$nextCase]['scope_opener'];
58+
$nextCloser = $tokens[$nextCase]['scope_closer'];
59+
60+
$nextCode = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), $nextCloser, true);
61+
62+
if ($tokens[$nextCode]['code'] !== T_CASE && $tokens[$nextCode]['code'] !== T_DEFAULT) {
63+
if ($tokens[$nextCode]['code'] === T_COMMENT && $tokens[$nextCode]['line'] === $tokens[$nextCase]['line']) {
64+
$fix = $phpcsFile->addFixableError(
65+
'Fallthrough comment has to be aligned to next line in case without body',
66+
$nextCase,
67+
self::CODE_SWITCH_COMMENT_INVALID_FORMAT,
68+
);
69+
70+
if ($fix) {
71+
$indentation = $this->getIndentation($phpcsFile, $this->getEndOfLineBefore($phpcsFile, $nextCase));
72+
73+
$phpcsFile->fixer->beginChangeset();
74+
$phpcsFile->fixer->addContentBefore($nextCode,$phpcsFile->eolChar . $indentation);
75+
$phpcsFile->fixer->endChangeset();
76+
}
77+
}
78+
79+
if ($tokens[$nextCode]['code'] === T_COMMENT) {
80+
continue;
81+
}
82+
83+
$nextCode = $this->findNextCase($phpcsFile, ($opener + 1), $nextCloser);
84+
85+
$beforeEndOfCase = $phpcsFile->findPrevious(T_WHITESPACE, (($nextCode === false ? $nextCloser : $nextCode) - 1), $nextCase, true);
86+
87+
if ($tokens[$beforeEndOfCase]['code'] === T_COMMENT &&
88+
($nextCode !== false || $tokens[$nextCloser]['code'] === T_CLOSE_CURLY_BRACKET)) {
89+
$beforeEndOfCase2 = $phpcsFile->findPrevious(T_WHITESPACE, ($beforeEndOfCase - 1), $nextCase, true);
90+
91+
$linesBetween = $tokens[$beforeEndOfCase]['line'] - $tokens[$beforeEndOfCase2]['line'];
92+
93+
if($linesBetween === 0 || $linesBetween === 1) {
94+
$fix = $phpcsFile->addFixableError(
95+
'Expected one space between comment and last line of case\'s body',
96+
$beforeEndOfCase,
97+
self::CODE_SWITCH_COMMENT_INVALID_FORMAT,
98+
);
99+
100+
if ($fix) {
101+
$indentation = $this->getIndentation($phpcsFile, $this->getEndOfLineBefore($phpcsFile, $nextCase));
102+
103+
$phpcsFile->fixer->addContentBefore(
104+
$beforeEndOfCase,
105+
$linesBetween === 0 ? $phpcsFile->eolChar . $phpcsFile->eolChar . $indentation : $phpcsFile->eolChar . $indentation,
106+
);
107+
108+
$phpcsFile->fixer->endChangeset();
109+
}
110+
}
111+
}
112+
113+
if ($nextCode === false && \in_array($tokens[$nextCloser]['code'], [T_BREAK, T_CONTINUE, T_RETURN], true)) {
114+
$nextComment = $phpcsFile->findNext(T_COMMENT, $nextCloser, null);
115+
116+
$linesBetween = $tokens[$nextCloser]['line'] - $tokens[$nextComment]['line'];
117+
118+
if($linesBetween === 0 || $linesBetween === 1) {
119+
$fix = $phpcsFile->addFixableError(
120+
'Expected one space between comment and last line of case\'s body',
121+
$nextComment,
122+
self::CODE_SWITCH_COMMENT_INVALID_FORMAT,
123+
);
124+
125+
if ($fix) {
126+
$indentation = $this->getIndentation($phpcsFile, $this->getEndOfLineBefore($phpcsFile, $opener));
127+
128+
$phpcsFile->fixer->beginChangeset();
129+
$phpcsFile->fixer->addContentBefore(
130+
$nextComment,
131+
$linesBetween === 0 ? $phpcsFile->eolChar . $phpcsFile->eolChar . $indentation : $phpcsFile->eolChar . $indentation,
132+
);
133+
$phpcsFile->fixer->endChangeset();
134+
}
135+
}
136+
}
137+
}
138+
}
139+
}
140+
141+
private function findNextCase($phpcsFile, $stackPtr, $end)
142+
{
143+
$tokens = $phpcsFile->getTokens();
144+
while (($stackPtr = $phpcsFile->findNext([T_CASE, T_DEFAULT, T_SWITCH], $stackPtr, $end)) !== false) {
145+
// Skip nested SWITCH statements; they are handled on their own.
146+
if ($tokens[$stackPtr]['code'] === T_SWITCH) {
147+
$stackPtr = $tokens[$stackPtr]['scope_closer'];
148+
continue;
149+
}
150+
151+
break;
152+
}
153+
154+
return $stackPtr;
155+
156+
}
157+
158+
private function getEndOfLineBefore(File $phpcsFile, int $pointer): int
159+
{
160+
$tokens = $phpcsFile->getTokens();
161+
162+
$endOfLineBefore = null;
163+
164+
$startPointer = $pointer - 1;
165+
while (true) {
166+
$possibleEndOfLinePointer = TokenHelper::findPrevious(
167+
$phpcsFile,
168+
array_merge([T_WHITESPACE, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO], TokenHelper::$inlineCommentTokenCodes),
169+
$startPointer
170+
);
171+
if ($tokens[$possibleEndOfLinePointer]['code'] === T_WHITESPACE && $tokens[$possibleEndOfLinePointer]['content'] === $phpcsFile->eolChar) {
172+
$endOfLineBefore = $possibleEndOfLinePointer;
173+
break;
174+
}
175+
176+
if ($tokens[$possibleEndOfLinePointer]['code'] === T_OPEN_TAG || $tokens[$possibleEndOfLinePointer]['code'] === T_OPEN_TAG_WITH_ECHO) {
177+
$endOfLineBefore = $possibleEndOfLinePointer;
178+
break;
179+
}
180+
181+
if (
182+
in_array($tokens[$possibleEndOfLinePointer]['code'], TokenHelper::$inlineCommentTokenCodes, true)
183+
&& substr($tokens[$possibleEndOfLinePointer]['content'], -1) === $phpcsFile->eolChar
184+
) {
185+
$endOfLineBefore = $possibleEndOfLinePointer;
186+
break;
187+
}
188+
189+
$startPointer = $possibleEndOfLinePointer - 1;
190+
}
191+
192+
/** @var int $endOfLineBefore */
193+
$endOfLineBefore = $endOfLineBefore;
194+
return $endOfLineBefore;
195+
}
196+
197+
private function getIndentation(File $phpcsFile, int $endOfLinePointer): string
198+
{
199+
$pointerAfterWhitespace = TokenHelper::findNextExcluding($phpcsFile, T_WHITESPACE, $endOfLinePointer + 1);
200+
$actualIndentation = TokenHelper::getContent($phpcsFile, $endOfLinePointer + 1, $pointerAfterWhitespace - 1);
201+
202+
if (strlen($actualIndentation) !== 0) {
203+
return $actualIndentation . (substr($actualIndentation, -1) === self::TAB_INDENT ? self::TAB_INDENT : self::SPACES_INDENT);
204+
}
205+
206+
$tabPointer = TokenHelper::findPreviousContent($phpcsFile, T_WHITESPACE, self::TAB_INDENT, $endOfLinePointer - 1);
207+
return $tabPointer !== null ? self::TAB_INDENT : self::SPACES_INDENT;
208+
}
209+
}

InfinityloopCodingStandard/ruleset.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,4 +510,5 @@
510510
<rule ref="InfinityloopCodingStandard.Classes.FinalClassVisibility"/>
511511
<rule ref="InfinityloopCodingStandard.Namespaces.UseDoesNotStartWithBackslash"/>
512512
<rule ref="InfinityloopCodingStandard.ControlStructures.RequireMultiLineNullCoalesce"/>
513+
<rule ref="InfinityloopCodingStandard.ControlStructures.SwitchCommentSpacing"/>
513514
</ruleset>

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ Inverted `SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash` sniff
7676

7777
Enforces null coalesce operator to be reformatted to new line
7878

79+
#### InfinityloopCodingStandard.ControlStructures.SwitchCommentSpacing :wrench:
80+
81+
Checks that there is a certain number of blank lines between code and comment
82+
7983
### Slevomat sniffs
8084

8185
Detailed list of Slevomat sniffs with configured settings. Some sniffs are not included, either because we dont find them helpful, the are too strict, or collide with their counter-sniff (require/disallow pairs).

0 commit comments

Comments
 (0)