Skip to content

Commit b5d47e0

Browse files
committed
Added ParameterTypeHintSpacingSniff
1 parent 52c9746 commit b5d47e0

12 files changed

+274
-3
lines changed

SlevomatCodingStandard/Helpers/TokenHelper.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ class TokenHelper
3131
T_DOC_COMMENT_WHITESPACE,
3232
];
3333

34+
/** @var mixed[] */
35+
public static $typeHintTokenCodes = [
36+
T_NS_SEPARATOR,
37+
T_STRING,
38+
T_SELF,
39+
T_PARENT,
40+
T_ARRAY_HINT,
41+
T_CALLABLE,
42+
];
43+
3444
/**
3545
* @param \PHP_CodeSniffer_File $phpcsFile
3646
* @param int $startPointer search starts at this token, inclusive

SlevomatCodingStandard/Sniffs/TypeHints/NullableTypeForNullDefaultValueSniff.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,14 @@ public function process(\PHP_CodeSniffer_File $phpcsFile, $functionPointer)
4848
continue;
4949
}
5050

51-
$typeHintTokens = [T_NS_SEPARATOR, T_STRING, T_SELF, T_PARENT, T_ARRAY_HINT, T_CALLABLE];
5251
$ignoreTokensToFindTypeHint = array_merge(TokenHelper::$ineffectiveTokenCodes, [T_BITWISE_AND, T_ELLIPSIS]);
5352
$typeHintPointer = TokenHelper::findPreviousExcluding($phpcsFile, $ignoreTokensToFindTypeHint, $i - 1, $startPointer);
5453

55-
if ($typeHintPointer === null || !in_array($tokens[$typeHintPointer]['code'], $typeHintTokens, true)) {
54+
if ($typeHintPointer === null || !in_array($tokens[$typeHintPointer]['code'], TokenHelper::$typeHintTokenCodes, true)) {
5655
continue;
5756
};
5857

59-
$ignoreTokensToSkipTypeHint = array_merge(TokenHelper::$ineffectiveTokenCodes, $typeHintTokens);
58+
$ignoreTokensToSkipTypeHint = array_merge(TokenHelper::$ineffectiveTokenCodes, TokenHelper::$typeHintTokenCodes);
6059
$beforeTypeHintPointer = TokenHelper::findPreviousExcluding($phpcsFile, $ignoreTokensToSkipTypeHint, $typeHintPointer - 1, $startPointer);
6160

6261
if ($beforeTypeHintPointer !== null && $tokens[$beforeTypeHintPointer]['code'] === T_NULLABLE) {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\TypeHints;
4+
5+
use SlevomatCodingStandard\Helpers\TokenHelper;
6+
7+
class ParameterTypeHintSpacingSniff implements \PHP_CodeSniffer_Sniff
8+
{
9+
10+
const CODE_NO_SPACE_BETWEEN_TYPE_HINT_AND_PARAMETER = 'NoSpaceBetweenTypeHintAndParameter';
11+
12+
const CODE_MULTIPLE_SPACES_BETWEEN_TYPE_HINT_AND_PARAMETER = 'MultipleSpacesBetweenTypeHintAndParameter';
13+
14+
const CODE_WHITESPACE_AFTER_NULLABILITY_SYMBOL = 'WhitespaceAfterNullabilitySymbol';
15+
16+
/**
17+
* @return int[]
18+
*/
19+
public function register(): array
20+
{
21+
return [
22+
T_FUNCTION,
23+
T_CLOSURE,
24+
];
25+
}
26+
27+
/**
28+
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
29+
* @param \PHP_CodeSniffer_File $phpcsFile
30+
* @param int $functionPointer
31+
*/
32+
public function process(\PHP_CodeSniffer_File $phpcsFile, $functionPointer)
33+
{
34+
$tokens = $phpcsFile->getTokens();
35+
36+
$parametersStartPointer = $tokens[$functionPointer]['parenthesis_opener'] + 1;
37+
$parametersEndPointer = $tokens[$functionPointer]['parenthesis_closer'] - 1;
38+
39+
for ($i = $parametersStartPointer; $i <= $parametersEndPointer; $i++) {
40+
if ($tokens[$i]['code'] !== T_VARIABLE) {
41+
continue;
42+
}
43+
44+
$parameterPointer = $i;
45+
$parameterName = $tokens[$parameterPointer]['content'];
46+
47+
$parameterStartPointer = $phpcsFile->findPrevious(T_COMMA, $parameterPointer - 1, $parametersStartPointer);
48+
if ($parameterStartPointer === false) {
49+
$parameterStartPointer = $parametersStartPointer;
50+
}
51+
52+
$parameterEndPointer = $phpcsFile->findNext(T_COMMA, $parameterPointer + 1, $parametersEndPointer + 1);
53+
if ($parameterEndPointer === false) {
54+
$parameterEndPointer = $parametersEndPointer;
55+
}
56+
57+
$typeHintEndPointer = $phpcsFile->findPrevious(TokenHelper::$typeHintTokenCodes, $parameterPointer - 1, $parameterStartPointer);
58+
if ($typeHintEndPointer === false) {
59+
continue;
60+
}
61+
62+
$nextTokenNames = [
63+
T_VARIABLE => sprintf('parameter %s', $parameterName),
64+
T_BITWISE_AND => sprintf('reference sign of parameter %s', $parameterName),
65+
T_ELLIPSIS => sprintf('varadic parameter %s', $parameterName),
66+
];
67+
$nextTokenPointer = $phpcsFile->findNext(array_keys($nextTokenNames), $typeHintEndPointer + 1, $parameterEndPointer + 1);
68+
69+
if ($tokens[$typeHintEndPointer + 1]['code'] !== T_WHITESPACE) {
70+
$fix = $phpcsFile->addFixableError(sprintf('There must be exactly one space between parameter type hint and %s.', $nextTokenNames[$tokens[$nextTokenPointer]['code']]), $typeHintEndPointer, self::CODE_NO_SPACE_BETWEEN_TYPE_HINT_AND_PARAMETER);
71+
if ($fix) {
72+
$phpcsFile->fixer->beginChangeset();
73+
$phpcsFile->fixer->addContent($typeHintEndPointer, ' ');
74+
$phpcsFile->fixer->endChangeset();
75+
}
76+
} elseif ($tokens[$typeHintEndPointer + 1]['content'] !== ' ') {
77+
$fix = $phpcsFile->addFixableError(sprintf('There must be exactly one space between parameter type hint and %s.', $nextTokenNames[$tokens[$nextTokenPointer]['code']]), $typeHintEndPointer, self::CODE_MULTIPLE_SPACES_BETWEEN_TYPE_HINT_AND_PARAMETER);
78+
if ($fix) {
79+
$phpcsFile->fixer->beginChangeset();
80+
$phpcsFile->fixer->replaceToken($typeHintEndPointer + 1, ' ');
81+
$phpcsFile->fixer->endChangeset();
82+
}
83+
}
84+
85+
$typeHintStartPointer = TokenHelper::findPreviousExcluding($phpcsFile, TokenHelper::$typeHintTokenCodes, $typeHintEndPointer, $parameterStartPointer) + 1;
86+
87+
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $typeHintStartPointer - 1, $parameterStartPointer);
88+
$nullabilitySymbolPointer = $previousPointer !== null && $tokens[$previousPointer]['code'] === T_NULLABLE ? $previousPointer : null;
89+
90+
if ($nullabilitySymbolPointer === null) {
91+
continue;
92+
}
93+
94+
if ($nullabilitySymbolPointer + 1 !== $typeHintStartPointer) {
95+
$fix = $phpcsFile->addFixableError(sprintf('There must be no whitespace between parameter type hint nullability symbol and parameter type hint of parameter %s.', $parameterName), $typeHintStartPointer, self::CODE_WHITESPACE_AFTER_NULLABILITY_SYMBOL);
96+
if ($fix) {
97+
$phpcsFile->fixer->beginChangeset();
98+
$phpcsFile->fixer->replaceToken($nullabilitySymbolPointer + 1, '');
99+
$phpcsFile->fixer->endChangeset();
100+
}
101+
}
102+
}
103+
}
104+
105+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SlevomatCodingStandard\Sniffs\TypeHints;
4+
5+
class ParameterTypeHintSpacingSniffTest extends \SlevomatCodingStandard\Sniffs\TestCase
6+
{
7+
8+
public function testNoErrors()
9+
{
10+
$this->assertNoSniffErrorInFile($this->checkFile(__DIR__ . '/data/parameterTypeHintSpacingNoErrors.php'));
11+
}
12+
13+
public function testErrors()
14+
{
15+
$report = $this->checkFile(__DIR__ . '/data/parameterTypeHintSpacingErrors.php');
16+
17+
$this->assertSame(6, $report->getErrorCount());
18+
19+
$this->assertSniffError($report, 3, ParameterTypeHintSpacingSniff::CODE_WHITESPACE_AFTER_NULLABILITY_SYMBOL, 'There must be no whitespace between parameter type hint nullability symbol and parameter type hint of parameter $a.');
20+
$this->assertSniffError($report, 3, ParameterTypeHintSpacingSniff::CODE_NO_SPACE_BETWEEN_TYPE_HINT_AND_PARAMETER, 'There must be exactly one space between parameter type hint and parameter $a.');
21+
$this->assertSniffError($report, 3, ParameterTypeHintSpacingSniff::CODE_WHITESPACE_AFTER_NULLABILITY_SYMBOL, 'There must be no whitespace between parameter type hint nullability symbol and parameter type hint of parameter $b.');
22+
$this->assertSniffError($report, 3, ParameterTypeHintSpacingSniff::CODE_MULTIPLE_SPACES_BETWEEN_TYPE_HINT_AND_PARAMETER, 'There must be exactly one space between parameter type hint and reference sign of parameter $b.');
23+
$this->assertSniffError($report, 3, ParameterTypeHintSpacingSniff::CODE_WHITESPACE_AFTER_NULLABILITY_SYMBOL, 'There must be no whitespace between parameter type hint nullability symbol and parameter type hint of parameter $c.');
24+
$this->assertSniffError($report, 3, ParameterTypeHintSpacingSniff::CODE_MULTIPLE_SPACES_BETWEEN_TYPE_HINT_AND_PARAMETER, 'There must be exactly one space between parameter type hint and varadic parameter $c.');
25+
}
26+
27+
public function testFixableParameterTypeHintSpacingNoSpaceBetweenTypeHintAndParameter()
28+
{
29+
$report = $this->checkFile(__DIR__ . '/data/fixableParameterTypeHintSpacingNoSpaceBetweenTypeHintAndParameter.php', [], [ParameterTypeHintSpacingSniff::CODE_NO_SPACE_BETWEEN_TYPE_HINT_AND_PARAMETER]);
30+
$this->assertAllFixedInFile($report);
31+
}
32+
33+
public function testFixableParameterTypeHintSpacingMultipleSpacesBetweenTypeHintAndParameter()
34+
{
35+
$report = $this->checkFile(__DIR__ . '/data/fixableParameterTypeHintSpacingMultipleSpacesBetweenTypeHintAndParameter.php', [], [ParameterTypeHintSpacingSniff::CODE_MULTIPLE_SPACES_BETWEEN_TYPE_HINT_AND_PARAMETER]);
36+
$this->assertAllFixedInFile($report);
37+
}
38+
39+
public function testFixableParameterTypeHintSpacingWhitespaceAfterNullabilitySymbol()
40+
{
41+
$report = $this->checkFile(__DIR__ . '/data/fixableParameterTypeHintSpacingWhitespaceAfterNullabilitySymbol.php', [], [ParameterTypeHintSpacingSniff::CODE_WHITESPACE_AFTER_NULLABILITY_SYMBOL]);
42+
$this->assertAllFixedInFile($report);
43+
}
44+
45+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // lint >= 7.1
2+
3+
$a = function (?bool $a, ?string &$b, ?int ...$c)
4+
{
5+
6+
};
7+
8+
function b(
9+
?bool $a,
10+
array $b,
11+
$c
12+
) {
13+
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // lint >= 7.1
2+
3+
$a = function (?bool $a, ?string &$b, ?int ...$c)
4+
{
5+
6+
};
7+
8+
function b(
9+
?bool $a,
10+
array $b,
11+
$c
12+
) {
13+
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // lint >= 7.1
2+
3+
$a = function (?bool $a, ?string &$b, ?int ...$c)
4+
{
5+
6+
};
7+
8+
function b(
9+
?bool $a,
10+
array $b,
11+
$c
12+
) {
13+
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // lint >= 7.1
2+
3+
$a = function (?bool$a, ?string&$b, ?int...$c)
4+
{
5+
6+
};
7+
8+
function b(
9+
?bool$a,
10+
array$b,
11+
$c
12+
) {
13+
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // lint >= 7.1
2+
3+
$a = function (?bool $a, ?string &$b, ?int ...$c)
4+
{
5+
6+
};
7+
8+
function b(
9+
?bool $a,
10+
array $b,
11+
$c
12+
) {
13+
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php // lint >= 7.1
2+
3+
$a = function (? bool $a, ? string &$b, ? int ...$c)
4+
{
5+
6+
};
7+
8+
function b(
9+
? bool $a,
10+
array $b,
11+
$c
12+
) {
13+
14+
}

0 commit comments

Comments
 (0)