Skip to content

Commit 0ecb63a

Browse files
committed
Add PhpDocPropertySorterFixer
1 parent 3c2bc06 commit 0ecb63a

File tree

4 files changed

+325
-0
lines changed

4 files changed

+325
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# CHANGELOG for PHP CS Fixer: custom fixers
22

3+
## v3.34.0
4+
- Add PhpDocPropertySorterFixer
5+
36
## v3.33.0
47
- Update minimum PHP CS Fixer version to 3.86.0
58

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,19 @@ Configuration options:
461461
+echo 01234567; // octal
462462
```
463463

464+
#### PhpDocPropertySorterFixer
465+
Sorts @property annotations in PHPDoc blocks alphabetically within groups separated by empty lines.
466+
```diff
467+
<?php
468+
/**
469+
- * @property string $zzz
470+
* @property int $aaa
471+
* @property bool $mmm
472+
+ * @property string $zzz
473+
*/
474+
class Foo {}
475+
```
476+
464477
#### PhpUnitAssertArgumentsOrderFixer
465478
PHPUnit assertions must have expected argument before actual one.
466479
*Risky: when original PHPUnit methods are overwritten.*
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php declare(strict_types=1);
2+
3+
/*
4+
* This file is part of PHP CS Fixer: custom fixers.
5+
*
6+
* (c) 2018 Kuba Werłos
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace PhpCsFixerCustomFixers\Fixer;
13+
14+
use PhpCsFixer\FixerDefinition\CodeSample;
15+
use PhpCsFixer\FixerDefinition\FixerDefinition;
16+
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
17+
use PhpCsFixer\Preg;
18+
use PhpCsFixer\Tokenizer\Token;
19+
use PhpCsFixer\Tokenizer\Tokens;
20+
21+
/**
22+
* @no-named-arguments
23+
*/
24+
final class PhpDocPropertySorterFixer extends AbstractFixer
25+
{
26+
public function getDefinition(): FixerDefinitionInterface
27+
{
28+
return new FixerDefinition(
29+
'Sorts @property annotations in PHPDoc blocks alphabetically within groups separated by empty lines.',
30+
[new CodeSample('<?php
31+
/**
32+
* @property string $zzz
33+
* @property int $aaa
34+
* @property bool $mmm
35+
*/
36+
class Foo {}
37+
')],
38+
'',
39+
);
40+
}
41+
42+
public function getPriority(): int
43+
{
44+
return 0;
45+
}
46+
47+
public function isCandidate(Tokens $tokens): bool
48+
{
49+
return $tokens->isTokenKindFound(\T_DOC_COMMENT);
50+
}
51+
52+
public function isRisky(): bool
53+
{
54+
return false;
55+
}
56+
57+
public function fix(\SplFileInfo $file, Tokens $tokens): void
58+
{
59+
foreach ($tokens as $index => $token) {
60+
if (!$token->isGivenKind(\T_DOC_COMMENT)) {
61+
continue;
62+
}
63+
64+
$originalDocContent = $token->getContent();
65+
$sortedDocContent = $this->sortPropertiesInDocBlock($originalDocContent);
66+
67+
if ($originalDocContent !== $sortedDocContent) {
68+
$tokens[$index] = new Token([\T_DOC_COMMENT, $sortedDocContent]);
69+
}
70+
}
71+
}
72+
73+
private function sortPropertiesInDocBlock(string $docContent): string
74+
{
75+
$docLines = \explode("\n", $docContent);
76+
$processedLines = [];
77+
$currentPropertyGroup = [];
78+
79+
foreach ($docLines as $line) {
80+
if (self::isPropertyAnnotation($line)) {
81+
$currentPropertyGroup[] = $line;
82+
} else {
83+
$this->flushPropertyGroup($currentPropertyGroup, $processedLines);
84+
$processedLines[] = $line;
85+
}
86+
}
87+
88+
return \implode("\n", $processedLines);
89+
}
90+
91+
private static function isPropertyAnnotation(string $line): bool
92+
{
93+
return Preg::match('/@property/', $line);
94+
}
95+
96+
/**
97+
* Sorts and adds a property group to the processed lines.
98+
*
99+
* @param list<string> $propertyGroup
100+
* @param list<string> $processedLines
101+
*/
102+
private function flushPropertyGroup(array &$propertyGroup, array &$processedLines): void
103+
{
104+
if (\count($propertyGroup) === 0) {
105+
return;
106+
}
107+
108+
$this->sortPropertiesByName($propertyGroup);
109+
$processedLines = \array_merge($processedLines, $propertyGroup);
110+
$propertyGroup = [];
111+
}
112+
113+
/**
114+
* @param list<string> $properties
115+
*/
116+
private function sortPropertiesByName(array &$properties): void
117+
{
118+
\usort($properties, static function (string $propertyA, string $propertyB): int {
119+
$nameA = self::extractPropertyName($propertyA);
120+
$nameB = self::extractPropertyName($propertyB);
121+
122+
return \strcmp($nameA, $nameB);
123+
});
124+
}
125+
126+
private static function extractPropertyName(string $propertyLine): string
127+
{
128+
$matches = [];
129+
Preg::match('/@property\\s+[^\\s]+\\s+\\$(\\w+)/', $propertyLine, $matches);
130+
/** @var array<array-key, string> $matches */
131+
if (\count($matches) > 1) {
132+
return $matches[1];
133+
}
134+
135+
return $propertyLine;
136+
}
137+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php declare(strict_types=1);
2+
3+
/*
4+
* This file is part of PHP CS Fixer: custom fixers.
5+
*
6+
* (c) 2018 Kuba Werłos
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace Tests\Fixer;
13+
14+
/**
15+
* @internal
16+
*
17+
* @covers \PhpCsFixerCustomFixers\Fixer\PhpDocPropertySorterFixer
18+
*/
19+
final class PhpDocPropertySorterFixerTest extends AbstractFixerTestCase
20+
{
21+
public function testIsRisky(): void
22+
{
23+
self::assertRiskiness(false);
24+
}
25+
26+
/**
27+
* @dataProvider provideFixCases
28+
*/
29+
public function testFix(string $expected, ?string $input = null): void
30+
{
31+
$this->doTest($expected, $input);
32+
}
33+
34+
/**
35+
* @return iterable<array{0: string, 1?: string}>
36+
*/
37+
public static function provideFixCases(): iterable
38+
{
39+
yield ['<?php
40+
/**
41+
* @return void
42+
*/
43+
'];
44+
45+
yield ['<?php
46+
/**
47+
* @customAnnotation Some text
48+
*/
49+
'];
50+
51+
yield ['<?php
52+
/**
53+
* @param string $x
54+
* @return int
55+
*/
56+
'];
57+
58+
yield [
59+
'<?php
60+
/**
61+
* @property string $aaa
62+
* @property bool $bbb
63+
* @property int $zzz
64+
*/
65+
class Foo {}
66+
',
67+
'<?php
68+
/**
69+
* @property int $zzz
70+
* @property string $aaa
71+
* @property bool $bbb
72+
*/
73+
class Foo {}
74+
',
75+
];
76+
77+
yield [
78+
'<?php
79+
/**
80+
* @property string $alpha
81+
* @property int $beta
82+
* @property bool $gamma
83+
*/
84+
class Example {}
85+
',
86+
'<?php
87+
/**
88+
* @property bool $gamma
89+
* @property string $alpha
90+
* @property int $beta
91+
*/
92+
class Example {}
93+
',
94+
];
95+
96+
yield [
97+
'<?php
98+
/**
99+
* @property array $data
100+
* @property string|null $name property comment
101+
* @property int $value
102+
*/
103+
',
104+
'<?php
105+
/**
106+
* @property string|null $name property comment
107+
* @property int $value
108+
* @property array $data
109+
*/
110+
',
111+
];
112+
113+
yield [
114+
'<?php
115+
/**
116+
* @property ClassA $classA
117+
* @property ClassB $classB
118+
* @property ClassC $classC
119+
*/
120+
',
121+
'<?php
122+
/**
123+
* @property ClassC $classC
124+
* @property ClassA $classA
125+
* @property ClassB $classB
126+
*/
127+
',
128+
];
129+
130+
yield [
131+
'<?php
132+
/**
133+
* @property string $first
134+
* @property int $second
135+
*
136+
* @property array $fourth
137+
* @property bool $third
138+
*/
139+
',
140+
'<?php
141+
/**
142+
* @property int $second
143+
* @property string $first
144+
*
145+
* @property bool $third
146+
* @property array $fourth
147+
*/
148+
',
149+
];
150+
151+
yield [
152+
'<?php
153+
/**
154+
* @param string $x
155+
* @property int $count
156+
* @property string $name
157+
* @return bool
158+
*/
159+
function example($x) {}
160+
',
161+
'<?php
162+
/**
163+
* @param string $x
164+
* @property string $name
165+
* @property int $count
166+
* @return bool
167+
*/
168+
function example($x) {}
169+
',
170+
];
171+
}
172+
}

0 commit comments

Comments
 (0)