Skip to content

Commit 7c60c78

Browse files
authored
Revert "fix: do not enable AlphabeticallyOrderedConstantsSniff by default (tmp sniff removal) (#124)" (#130)
This reverts commit 74ad7ea.
1 parent 381abd0 commit 7c60c78

File tree

4 files changed

+351
-0
lines changed

4 files changed

+351
-0
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cdn77\Sniffs\Ordering;
6+
7+
use PHP_CodeSniffer\Files\File;
8+
use PHP_CodeSniffer\Sniffs\Sniff;
9+
use PHP_CodeSniffer\Util\Tokens;
10+
use SlevomatCodingStandard\Helpers\FixerHelper;
11+
12+
use function array_key_first;
13+
use function array_map;
14+
use function implode;
15+
use function in_array;
16+
use function sort;
17+
use function sprintf;
18+
use function strtolower;
19+
use function ucfirst;
20+
use function usort;
21+
22+
use const T_CONST;
23+
use const T_EQUAL;
24+
use const T_OPEN_TAG;
25+
use const T_PRIVATE;
26+
use const T_PROTECTED;
27+
use const T_PUBLIC;
28+
use const T_SEMICOLON;
29+
use const T_STRING;
30+
use const T_WHITESPACE;
31+
32+
/**
33+
* @phpstan-type NameWithValueShape array{
34+
* name: NameShape,
35+
* value: ValueShape
36+
* }
37+
* @phpstan-type NameShape array{
38+
* content: string,
39+
* lowercaseContent: string,
40+
* ptr: int
41+
* }
42+
* @phpstan-type ValueShape array{
43+
* content: string,
44+
* startPtr: int,
45+
* endPtr: int
46+
* }
47+
*/
48+
final class AlphabeticallyOrderedConstantsSniff implements Sniff
49+
{
50+
public const CodeIncorrectConstantOrder = 'IncorrectConstantOrder';
51+
52+
public function register(): array
53+
{
54+
return [T_OPEN_TAG];
55+
}
56+
57+
public function process(File $phpcsFile, mixed $stackPtr): void
58+
{
59+
$namesWithValuesByVisibility = $this->findConstantNamesWithValuesByVisibility($phpcsFile);
60+
61+
if ($namesWithValuesByVisibility === []) {
62+
return;
63+
}
64+
65+
foreach ($namesWithValuesByVisibility as $visibility => $namesWithValues) {
66+
$constantNames = array_map(
67+
static fn (array $nameWithValue): string => $nameWithValue['name']['lowercaseContent'],
68+
$namesWithValues,
69+
);
70+
$sortedConstantNames = $constantNames;
71+
sort($sortedConstantNames);
72+
73+
if ($sortedConstantNames === $constantNames) {
74+
continue;
75+
}
76+
77+
$firstNameWithValue = $namesWithValues[array_key_first($namesWithValues)];
78+
$fix = $phpcsFile->addFixableError(
79+
sprintf('%s constant names are not alphabetically ordered.', ucfirst($visibility)),
80+
$firstNameWithValue['name']['ptr'],
81+
self::CodeIncorrectConstantOrder,
82+
);
83+
84+
if (! $fix) {
85+
continue;
86+
}
87+
88+
$this->fix($phpcsFile, $namesWithValues);
89+
}
90+
}
91+
92+
/** @param list<NameWithValueShape> $namesWithValues */
93+
private function fix(File $file, array $namesWithValues): void
94+
{
95+
$fixer = $file->fixer;
96+
$sortedNameAndValueTokens = $namesWithValues;
97+
usort(
98+
$sortedNameAndValueTokens,
99+
static fn (array $a, array $b): int => $a['name']['lowercaseContent'] <=> $b['name']['lowercaseContent'],
100+
);
101+
102+
$fixer->beginChangeset();
103+
104+
foreach ($namesWithValues as $key => $nameWithValue) {
105+
$sortedNameAndValueToken = $sortedNameAndValueTokens[$key];
106+
107+
$namePointer = $nameWithValue['name']['ptr'];
108+
FixerHelper::removeBetweenIncluding($file, $namePointer, $namePointer);
109+
$fixer->addContent($namePointer, $sortedNameAndValueToken['name']['content']);
110+
111+
$value = $nameWithValue['value'];
112+
FixerHelper::removeBetweenIncluding($file, $value['startPtr'], $value['endPtr']);
113+
$fixer->addContent($value['startPtr'], $sortedNameAndValueToken['value']['content']);
114+
}
115+
116+
$fixer->endChangeset();
117+
}
118+
119+
/** @return array<string, list<NameWithValueShape>> */
120+
private function findConstantNamesWithValuesByVisibility(File $phpcsFile): array
121+
{
122+
$constantNamesWithValues = [];
123+
$tokens = $phpcsFile->getTokens();
124+
125+
foreach ($tokens as $stackPtr => $token) {
126+
if ($token['code'] !== T_CONST) {
127+
continue;
128+
}
129+
130+
$visibility = $this->getVisibility($phpcsFile, $stackPtr);
131+
$constantName = $this->findConstantName($phpcsFile, $stackPtr);
132+
133+
if ($constantName === null) {
134+
continue;
135+
}
136+
137+
$equalsTokenPointer = $this->findEqualsPointer($phpcsFile, $constantName['ptr']);
138+
139+
if ($equalsTokenPointer === null) {
140+
continue;
141+
}
142+
143+
$value = $this->findValue($phpcsFile, $equalsTokenPointer);
144+
145+
if ($value === null) {
146+
continue;
147+
}
148+
149+
$constantNamesWithValues[$visibility][] = [
150+
'name' => $constantName,
151+
'value' => $value,
152+
];
153+
}
154+
155+
return $constantNamesWithValues;
156+
}
157+
158+
private function getVisibility(File $phpcsFile, int $constStackPtr): string
159+
{
160+
$tokens = $phpcsFile->getTokens();
161+
$visibilityTokenPointer = $phpcsFile->findPrevious(
162+
types: Tokens::$emptyTokens,
163+
start: $constStackPtr - 1,
164+
exclude: true,
165+
local: true,
166+
);
167+
168+
return in_array($tokens[$visibilityTokenPointer]['code'], [T_PUBLIC, T_PROTECTED, T_PRIVATE], true)
169+
? (string) $tokens[$visibilityTokenPointer]['content']
170+
: 'public';
171+
}
172+
173+
/** @phpstan-return NameShape|null */
174+
private function findConstantName(File $phpcsFile, int $constStackPtr): array|null
175+
{
176+
$tokens = $phpcsFile->getTokens();
177+
$constantNameTokenPointer = $phpcsFile->findNext(
178+
types: Tokens::$emptyTokens,
179+
start: $constStackPtr + 1,
180+
exclude: true,
181+
local: true,
182+
);
183+
184+
if ($constantNameTokenPointer === false || $tokens[$constantNameTokenPointer]['code'] !== T_STRING) {
185+
return null;
186+
}
187+
188+
return [
189+
'content' => $tokens[$constantNameTokenPointer]['content'],
190+
'lowercaseContent' => strtolower($tokens[$constantNameTokenPointer]['content']),
191+
'ptr' => $constantNameTokenPointer,
192+
];
193+
}
194+
195+
private function findEqualsPointer(File $phpcsFile, int $constNameStackPtr): int|null
196+
{
197+
$tokens = $phpcsFile->getTokens();
198+
$equalsTokenPointer = $phpcsFile->findNext(
199+
types: Tokens::$emptyTokens,
200+
start: $constNameStackPtr + 1,
201+
exclude: true,
202+
local: true,
203+
);
204+
205+
if ($equalsTokenPointer === false || $tokens[$equalsTokenPointer]['code'] !== T_EQUAL) {
206+
return null;
207+
}
208+
209+
return $equalsTokenPointer;
210+
}
211+
212+
/** @phpstan-return ValueShape|null */
213+
private function findValue(File $phpcsFile, int $equalsTokenPointer): array|null
214+
{
215+
$tokens = $phpcsFile->getTokens();
216+
$startValueTokenPointer = $phpcsFile->findNext(
217+
types: Tokens::$emptyTokens,
218+
start: $equalsTokenPointer + 1,
219+
exclude: true,
220+
local: true,
221+
);
222+
223+
if ($startValueTokenPointer === false) {
224+
return null;
225+
}
226+
227+
$endValueTokenPointer = $startValueTokenPointer;
228+
$valueToken = $tokens[$endValueTokenPointer];
229+
$values = [];
230+
231+
while ($valueToken['code'] !== T_SEMICOLON) {
232+
if (
233+
$valueToken['code'] === T_WHITESPACE
234+
&& in_array($valueToken['content'], ["\n", "\r\n", "\r"], true)
235+
) {
236+
return null;
237+
}
238+
239+
$values[] = $valueToken['content'];
240+
$valueToken = $tokens[++$endValueTokenPointer];
241+
}
242+
243+
return [
244+
'content' => implode('', $values),
245+
'startPtr' => $startValueTokenPointer,
246+
'endPtr' => $endValueTokenPointer - 1,
247+
];
248+
}
249+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cdn77\Sniffs\Ordering;
6+
7+
use Cdn77\TestCase;
8+
9+
use function array_keys;
10+
use function json_encode;
11+
12+
use const JSON_THROW_ON_ERROR;
13+
14+
final class AlphabeticallyOrderedConstantsSniffTest extends TestCase
15+
{
16+
public function testErrors(): void
17+
{
18+
$file = self::checkFile(__DIR__ . '/data/AlphabeticallyOrderedConstantsSniffTest.inc');
19+
$expectedErrors = [
20+
9 => AlphabeticallyOrderedConstantsSniff::CodeIncorrectConstantOrder,
21+
19 => AlphabeticallyOrderedConstantsSniff::CodeIncorrectConstantOrder,
22+
25 => AlphabeticallyOrderedConstantsSniff::CodeIncorrectConstantOrder,
23+
];
24+
$possibleLines = array_keys($expectedErrors);
25+
$errors = $file->getErrors();
26+
27+
foreach ($errors as $line => $error) {
28+
self::assertContains($line, $possibleLines, json_encode($error, JSON_THROW_ON_ERROR));
29+
30+
$expectedError = $expectedErrors[$line];
31+
self::assertSniffError($file, $line, $expectedError);
32+
}
33+
34+
self::assertSame(3, $file->getErrorCount());
35+
36+
$file->disableCaching();
37+
$file->fixer->fixFile();
38+
39+
self::assertStringEqualsFile(
40+
__DIR__ . '/data/AlphabeticallyOrderedConstantsSniffTest.fixed.inc',
41+
$file->fixer->getContents(),
42+
);
43+
}
44+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cdn77\Sniffs\Ordering\data;
6+
7+
final class TestClass
8+
{
9+
public const A = 'a';
10+
public const B = 'b';
11+
12+
public const
13+
IgnoredMultiline1 = 1,
14+
IgnoredMultiline2 = 2;
15+
16+
public const C = 'c' . PHP_EOL;
17+
public const D = [123, 'test'];
18+
19+
protected const E = 'e';
20+
protected const F = 'f';
21+
protected const G = 'g';
22+
protected const Ha = 'h';
23+
protected const HB = 'h';
24+
25+
private const I = 'i';
26+
private const J = 'j';
27+
private const K = 'k';
28+
private const L = 'l';
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cdn77\Sniffs\Ordering\data;
6+
7+
final class TestClass
8+
{
9+
public const C = 'c' . PHP_EOL;
10+
public const A = 'a';
11+
12+
public const
13+
IgnoredMultiline1 = 1,
14+
IgnoredMultiline2 = 2;
15+
16+
public const D = [123, 'test'];
17+
public const B = 'b';
18+
19+
protected const E = 'e';
20+
protected const G = 'g';
21+
protected const F = 'f';
22+
protected const HB = 'h';
23+
protected const Ha = 'h';
24+
25+
private const K = 'k';
26+
private const J = 'j';
27+
private const I = 'i';
28+
private const L = 'l';
29+
}

0 commit comments

Comments
 (0)