Skip to content

Commit 79c154b

Browse files
committed
Added RequireMultiLineNullCoalesceSniff
1 parent a4ab61a commit 79c154b

File tree

4 files changed

+124
-1
lines changed

4 files changed

+124
-1
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace InfinityloopCodingStandard\Sniffs\ControlStructures;
4+
5+
use PHP_CodeSniffer\Files\File;
6+
use PHP_CodeSniffer\Sniffs\Sniff;
7+
use SlevomatCodingStandard\Helpers\TokenHelper;
8+
use function array_merge;
9+
use function in_array;
10+
use function strlen;
11+
use function substr;
12+
use const T_COALESCE;
13+
use const T_OPEN_TAG;
14+
use const T_OPEN_TAG_WITH_ECHO;
15+
use const T_WHITESPACE;
16+
17+
class RequireMultiLineNullCoalesceSniff implements Sniff
18+
{
19+
20+
public const CODE_MULTI_LINE_NULL_COALESCE_OPERATOR_NOT_USED = 'MultiLineNullCoalesceOperatorNotUsed';
21+
22+
private const TAB_INDENT = "\t";
23+
private const SPACES_INDENT = ' ';
24+
25+
/**
26+
* @return array<int, (int|string)>
27+
*/
28+
public function register(): array
29+
{
30+
return [
31+
T_COALESCE,
32+
];
33+
}
34+
35+
/**
36+
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
37+
* @param File $phpcsFile
38+
* @param int $coalescePointer
39+
*/
40+
public function process(File $phpcsFile, $coalescePointer): void
41+
{
42+
$tokens = $phpcsFile->getTokens();
43+
44+
/** @var int $variablePointer */
45+
$variablePointer = TokenHelper::findPrevious($phpcsFile, T_VARIABLE, $coalescePointer + 1);
46+
47+
if ($tokens[$coalescePointer]['line'] !== $tokens[$variablePointer]['line']) {
48+
return;
49+
}
50+
51+
$fix = $phpcsFile->addFixableError('Null coalesce operator should be reformatted to next line.', $coalescePointer, self::CODE_MULTI_LINE_NULL_COALESCE_OPERATOR_NOT_USED);
52+
53+
if (!$fix) {
54+
return;
55+
}
56+
57+
$endOfLineBeforeCoalescePointer = $this->getEndOfLineBefore($phpcsFile, $coalescePointer);
58+
59+
$indentation = $this->getIndentation($phpcsFile, $endOfLineBeforeCoalescePointer);
60+
61+
$phpcsFile->fixer->beginChangeset();
62+
$phpcsFile->fixer->addContentBefore($coalescePointer, $phpcsFile->eolChar . $indentation);
63+
$phpcsFile->fixer->endChangeset();
64+
}
65+
66+
private function getEndOfLineBefore(File $phpcsFile, int $pointer): int
67+
{
68+
$tokens = $phpcsFile->getTokens();
69+
70+
$endOfLineBefore = null;
71+
72+
$startPointer = $pointer - 1;
73+
while (true) {
74+
$possibleEndOfLinePointer = TokenHelper::findPrevious(
75+
$phpcsFile,
76+
array_merge([T_WHITESPACE, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO], TokenHelper::$inlineCommentTokenCodes),
77+
$startPointer
78+
);
79+
if ($tokens[$possibleEndOfLinePointer]['code'] === T_WHITESPACE && $tokens[$possibleEndOfLinePointer]['content'] === $phpcsFile->eolChar) {
80+
$endOfLineBefore = $possibleEndOfLinePointer;
81+
break;
82+
}
83+
84+
if ($tokens[$possibleEndOfLinePointer]['code'] === T_OPEN_TAG || $tokens[$possibleEndOfLinePointer]['code'] === T_OPEN_TAG_WITH_ECHO) {
85+
$endOfLineBefore = $possibleEndOfLinePointer;
86+
break;
87+
}
88+
89+
if (
90+
in_array($tokens[$possibleEndOfLinePointer]['code'], TokenHelper::$inlineCommentTokenCodes, true)
91+
&& substr($tokens[$possibleEndOfLinePointer]['content'], -1) === $phpcsFile->eolChar
92+
) {
93+
$endOfLineBefore = $possibleEndOfLinePointer;
94+
break;
95+
}
96+
97+
$startPointer = $possibleEndOfLinePointer - 1;
98+
}
99+
100+
/** @var int $endOfLineBefore */
101+
$endOfLineBefore = $endOfLineBefore;
102+
return $endOfLineBefore;
103+
}
104+
105+
private function getIndentation(File $phpcsFile, int $endOfLinePointer): string
106+
{
107+
$pointerAfterWhitespace = TokenHelper::findNextExcluding($phpcsFile, T_WHITESPACE, $endOfLinePointer + 1);
108+
$actualIndentation = TokenHelper::getContent($phpcsFile, $endOfLinePointer + 1, $pointerAfterWhitespace - 1);
109+
110+
if (strlen($actualIndentation) !== 0) {
111+
return $actualIndentation . (substr($actualIndentation, -1) === self::TAB_INDENT ? self::TAB_INDENT : self::SPACES_INDENT);
112+
}
113+
114+
$tabPointer = TokenHelper::findPreviousContent($phpcsFile, T_WHITESPACE, self::TAB_INDENT, $endOfLinePointer - 1);
115+
return $tabPointer !== null ? self::TAB_INDENT : self::SPACES_INDENT;
116+
}
117+
}

InfinityloopCodingStandard/ruleset.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<rule ref="Generic.Commenting.Todo"/>
4545
<rule ref="Generic.ControlStructures.InlineControlStructure"/>
4646
<rule ref="Generic.Files.ByteOrderMark"/>
47+
<rule ref="Generic.Files.EndFileNewline"/>
4748
<rule ref="Generic.Files.InlineHTML"/>
4849
<rule ref="Generic.Files.LineEndings">
4950
<properties>
@@ -517,4 +518,5 @@
517518
</rule>
518519
<rule ref="InfinityloopCodingStandard.Classes.FinalClassVisibility"/>
519520
<rule ref="InfinityloopCodingStandard.Namespaces.UseDoesNotStartWithBackslash"/>
521+
<rule ref="InfinityloopCodingStandard.ControlStructures.RequireMultiLineNullCoalesce"/>
520522
</ruleset>

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ When class is final and doesnt extend any other class, it's safe to change visib
7272

7373
Inverted `SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash` sniff - require imports to start with backslash.
7474

75+
#### InfinityloopCodingStandard.ControlStructures.RequireMultiLineNullCoalesce :wrench:
76+
77+
Enforces null coalesce operator to be reformatted to new line
78+
7579
### Slevomat sniffs
7680

7781
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).

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"require": {
99
"php": "^7.4",
1010
"squizlabs/php_codesniffer": "~3.5.3",
11-
"slevomat/coding-standard": "~6.1.1"
11+
"slevomat/coding-standard": "~6.3.8"
1212
},
1313
"autoload": {
1414
"psr-4": {

0 commit comments

Comments
 (0)