Skip to content

Commit 10b01f8

Browse files
committed
SlevomatCodingStandard.Classes.PropertyDeclaration: New option "enableMultipleSpacesBetweenModifiersCheck" to enable check of spaces between property modifiers
1 parent d109591 commit 10b01f8

7 files changed

+201
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ Sniff provides the following settings:
548548

549549
* `modifiersOrder`: allows to configurure order of modifiers.
550550
* `checkPromoted`: will check promoted properties too.
551+
* `enableMultipleSpacesBetweenModifiersCheck`: checks multiple spaces between modifiers.
551552

552553
#### SlevomatCodingStandard.Classes.PropertySpacing 🔧
553554

SlevomatCodingStandard/Sniffs/Classes/PropertyDeclarationSniff.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,17 @@ class PropertyDeclarationSniff implements Sniff
4949

5050
public const CODE_INCORRECT_ORDER_OF_MODIFIERS = 'IncorrectOrderOfModifiers';
5151

52+
public const CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS = 'MultipleSpacesBetweenModifiers';
53+
5254
/** @var string[]|null */
5355
public $modifiersOrder = [];
5456

5557
/** @var bool */
5658
public $checkPromoted = false;
5759

60+
/** @var bool */
61+
public $enableMultipleSpacesBetweenModifiersCheck = false;
62+
5863
/** @var array<int, array<int, (int|string)>>|null */
5964
private $normalizedModifiersOrder = null;
6065

@@ -106,6 +111,7 @@ public function process(File $phpcsFile, $modifierPointer): void
106111
} while (true);
107112

108113
$this->checkModifiersOrder($phpcsFile, $propertyPointer, $firstModifierPointer, $modifierPointer);
114+
$this->checkSpacesBetweenModifiers($phpcsFile, $propertyPointer, $firstModifierPointer, $modifierPointer);
109115
$this->checkTypeHintSpacing($phpcsFile, $propertyPointer, $modifierPointer);
110116
}
111117

@@ -189,6 +195,68 @@ private function checkModifiersOrder(File $phpcsFile, int $propertyPointer, int
189195
$phpcsFile->fixer->endChangeset();
190196
}
191197

198+
private function checkSpacesBetweenModifiers(
199+
File $phpcsFile,
200+
int $propertyPointer,
201+
int $firstModifierPointer,
202+
int $lastModifierPointer
203+
): void
204+
{
205+
if (!$this->enableMultipleSpacesBetweenModifiersCheck) {
206+
return;
207+
}
208+
209+
$modifiersPointers = TokenHelper::findNextAll(
210+
$phpcsFile,
211+
TokenHelper::$propertyModifiersTokenCodes,
212+
$firstModifierPointer,
213+
$lastModifierPointer + 1
214+
);
215+
216+
if (count($modifiersPointers) < 2) {
217+
return;
218+
}
219+
220+
$tokens = $phpcsFile->getTokens();
221+
222+
$error = false;
223+
for ($i = 0; $i < count($modifiersPointers) - 1; $i++) {
224+
$whitespace = TokenHelper::getContent($phpcsFile, $modifiersPointers[$i] + 1, $modifiersPointers[$i + 1] - 1);
225+
if ($whitespace !== ' ') {
226+
$error = true;
227+
break;
228+
}
229+
}
230+
231+
if (!$error) {
232+
return;
233+
}
234+
235+
$fix = $phpcsFile->addFixableError(
236+
sprintf('There must be exactly one space between modifiers of property %s.', $tokens[$propertyPointer]['content']),
237+
$firstModifierPointer,
238+
self::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS
239+
);
240+
if (!$fix) {
241+
return;
242+
}
243+
244+
$expectedModifiers = array_map(static function (int $modifierPointer) use ($tokens): string {
245+
return $tokens[$modifierPointer]['content'];
246+
}, $modifiersPointers);
247+
$expectedModifiersFormatted = implode(' ', $expectedModifiers);
248+
249+
$phpcsFile->fixer->beginChangeset();
250+
251+
$phpcsFile->fixer->replaceToken($firstModifierPointer, $expectedModifiersFormatted);
252+
253+
for ($i = $firstModifierPointer + 1; $i <= $lastModifierPointer; $i++) {
254+
$phpcsFile->fixer->replaceToken($i, '');
255+
}
256+
257+
$phpcsFile->fixer->endChangeset();
258+
}
259+
192260
private function checkTypeHintSpacing(File $phpcsFile, int $propertyPointer, int $lastModifierPointer): void
193261
{
194262
$typeHintEndPointer = TokenHelper::findPrevious(

tests/Sniffs/Classes/PropertyDeclarationSniffTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,55 @@ public function testPromotedErrors(): void
7878
self::assertAllFixedInFile($report);
7979
}
8080

81+
public function testDisabledMultipleSpacesBetweenModifiersNoErrors(): void
82+
{
83+
$report = self::checkFile(__DIR__ . '/data/propertyDeclarationDisabledMultipleSpacesBetweenModifiersNoErrors.php', [
84+
'checkPromoted' => true,
85+
'enableMultipleSpacesBetweenModifiersCheck' => false,
86+
], [PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS]);
87+
88+
self::assertNoSniffErrorInFile($report);
89+
}
90+
91+
public function testEnabledMultipleSpacesBetweenModifiersNoErrors(): void
92+
{
93+
$report = self::checkFile(__DIR__ . '/data/propertyDeclarationEnabledMultipleSpacesBetweenModifiersNoErrors.php', [
94+
'checkPromoted' => true,
95+
'enableMultipleSpacesBetweenModifiersCheck' => true,
96+
], [PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS]);
97+
98+
self::assertNoSniffErrorInFile($report);
99+
}
100+
101+
public function testEnabledMultipleSpacesBetweenModifiersErrors(): void
102+
{
103+
$report = self::checkFile(__DIR__ . '/data/propertyDeclarationEnabledMultipleSpacesBetweenModifiersErrors.php', [
104+
'checkPromoted' => true,
105+
'enableMultipleSpacesBetweenModifiersCheck' => true,
106+
], [PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS]);
107+
108+
self::assertSame(6, $report->getErrorCount());
109+
110+
self::assertSniffError($report, 6, PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS);
111+
self::assertSniffError($report, 8, PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS);
112+
self::assertSniffError(
113+
$report,
114+
10,
115+
PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS,
116+
'There must be exactly one space between modifiers of property $three.'
117+
);
118+
self::assertSniffError(
119+
$report,
120+
10,
121+
PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS,
122+
'There must be exactly one space between modifiers of property $four.'
123+
);
124+
self::assertSniffError($report, 20, PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS);
125+
self::assertSniffError($report, 21, PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS);
126+
127+
self::assertAllFixedInFile($report);
128+
}
129+
81130
public function testModifiedModifiersOrderNoErrors(): void
82131
{
83132
$report = self::checkFile(__DIR__ . '/data/propertyDeclarationModifiedModifiersOrderNoErrors.php', [
@@ -86,6 +135,7 @@ public function testModifiedModifiersOrderNoErrors(): void
86135
'var, public, protected, private',
87136
],
88137
]);
138+
89139
self::assertNoSniffErrorInFile($report);
90140
}
91141

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // lint >= 8.1
2+
3+
class Whatever
4+
{
5+
6+
public readonly int $one;
7+
8+
static public string|bool $two = false;
9+
10+
public function __construct(public readonly Foo|Bar $three)
11+
{
12+
}
13+
14+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php // lint >= 8.1
2+
3+
class Whatever
4+
{
5+
6+
public readonly int $one;
7+
8+
static public string|bool $two = false;
9+
10+
public function __construct(public readonly ?Foo $three, readonly public Foo|Bar|null $four)
11+
{
12+
}
13+
14+
}
15+
16+
class Anything
17+
{
18+
19+
public function __construct(
20+
public readonly ?Foo $five,
21+
readonly protected Foo|Bar|null $six,
22+
)
23+
{
24+
}
25+
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php // lint >= 8.1
2+
3+
class Whatever
4+
{
5+
6+
public readonly int $one;
7+
8+
static public string|bool $two = false;
9+
10+
public function __construct(public readonly ?Foo $three, readonly public Foo|Bar|null $four)
11+
{
12+
}
13+
14+
}
15+
16+
class Anything
17+
{
18+
19+
public function __construct(
20+
public readonly ?Foo $five,
21+
readonly protected Foo|Bar|null $six,
22+
)
23+
{
24+
}
25+
26+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php // lint >= 8.1
2+
3+
class Whatever
4+
{
5+
6+
public int $zero;
7+
8+
public readonly int $one;
9+
10+
static public string|bool $two = false;
11+
12+
public function __construct(public readonly Foo|Bar $three)
13+
{
14+
}
15+
16+
}

0 commit comments

Comments
 (0)