Skip to content

Commit 2a4775d

Browse files
committed
Add ClassConstantUsageFixer
1 parent 6922672 commit 2a4775d

File tree

3 files changed

+244
-1
lines changed

3 files changed

+244
-1
lines changed

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Latest stable version](https://img.shields.io/packagist/v/kubawerlos/php-cs-fixer-custom-fixers.svg?label=current%20version)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
66
[![PHP version](https://img.shields.io/packagist/php-v/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://php.net)
77
[![License](https://img.shields.io/github/license/kubawerlos/php-cs-fixer-custom-fixers.svg)](LICENSE)
8-
![Tests](https://img.shields.io/badge/tests-3546-brightgreen.svg)
8+
![Tests](https://img.shields.io/badge/tests-3569-brightgreen.svg)
99
[![Downloads](https://img.shields.io/packagist/dt/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
1010

1111
[![CI status](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml/badge.svg)](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml)
@@ -43,6 +43,21 @@ require __DIR__ . '/vendor/kubawerlos/php-cs-fixer-custom-fixers/bootstrap.php';
4343

4444

4545
## Fixers
46+
#### ClassConstantUsageFixer
47+
Class constant must be used instead of a copy of string.
48+
```diff
49+
<?php
50+
class Foo
51+
{
52+
public const BAR = 'bar';
53+
public function bar()
54+
{
55+
- return 'bar';
56+
+ return self::BAR;
57+
}
58+
}
59+
```
60+
4661
#### CommentSurroundedBySpacesFixer
4762
Comments must be surrounded by spaces.
4863
```diff
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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\Tokenizer\Token;
18+
use PhpCsFixer\Tokenizer\Tokens;
19+
20+
final class ClassConstantUsageFixer extends AbstractFixer
21+
{
22+
public function getDefinition(): FixerDefinitionInterface
23+
{
24+
return new FixerDefinition(
25+
'Class constant must be used instead of a copy of string.',
26+
[new CodeSample(
27+
<<<'PHP'
28+
<?php
29+
class Foo
30+
{
31+
public const BAR = 'bar';
32+
public function bar()
33+
{
34+
return 'bar';
35+
}
36+
}
37+
38+
PHP,
39+
)],
40+
'',
41+
);
42+
}
43+
44+
public function getPriority(): int
45+
{
46+
return 0;
47+
}
48+
49+
public function isCandidate(Tokens $tokens): bool
50+
{
51+
return $tokens->isAllTokenKindsFound([\T_CLASS, \T_CONSTANT_ENCAPSED_STRING]);
52+
}
53+
54+
public function isRisky(): bool
55+
{
56+
return false;
57+
}
58+
59+
public function fix(\SplFileInfo $file, Tokens $tokens): void
60+
{
61+
for ($index = $tokens->count() - 1; $index > 0; $index--) {
62+
if (!$tokens[$index]->isGivenKind(\T_CLASS)) {
63+
continue;
64+
}
65+
66+
$openParenthesisIndex = $tokens->getNextTokenOfKind($index, ['{']);
67+
\assert(\is_int($openParenthesisIndex));
68+
69+
$closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openParenthesisIndex);
70+
71+
$this->fixClass($tokens, $openParenthesisIndex, $closeParenthesisIndex);
72+
}
73+
}
74+
75+
private function fixClass(Tokens $tokens, int $openParenthesisIndex, int $closeParenthesisIndex): void
76+
{
77+
[$constantsMap, $constantsIndices] = $this->getClassConstants($tokens, $openParenthesisIndex, $closeParenthesisIndex);
78+
79+
for ($index = $closeParenthesisIndex; $index > $openParenthesisIndex; $index--) {
80+
if (!$tokens[$index]->isGivenKind(\T_CONSTANT_ENCAPSED_STRING)) {
81+
continue;
82+
}
83+
84+
if (!isset($constantsMap[$tokens[$index]->getContent()])) {
85+
continue;
86+
}
87+
88+
if (isset($constantsIndices[$index])) {
89+
continue;
90+
}
91+
92+
$tokens->overrideRange(
93+
$index,
94+
$index,
95+
[
96+
new Token([\T_STRING, 'self']),
97+
new Token([\T_DOUBLE_COLON, '::']),
98+
new Token([\T_STRING, $constantsMap[$tokens[$index]->getContent()]]),
99+
],
100+
);
101+
}
102+
}
103+
104+
private function getClassConstants(Tokens $tokens, int $openParenthesisIndex, int $closeParenthesisIndex): array
105+
{
106+
$constants = [];
107+
$constantsIndices = [];
108+
for ($index = $openParenthesisIndex; $index < $closeParenthesisIndex; $index++) {
109+
if (!$tokens[$index]->isGivenKind(\T_CONST)) {
110+
continue;
111+
}
112+
113+
$assignTokenIndex = $tokens->getNextTokenOfKind($index, ['=']);
114+
115+
$constantNameIndex = $tokens->getPrevMeaningfulToken($assignTokenIndex);
116+
$constantValueIndex = $tokens->getNextMeaningfulToken($assignTokenIndex);
117+
118+
$constantsIndices[$constantValueIndex] = true;
119+
120+
if (!$tokens[$constantValueIndex]->isGivenKind(\T_CONSTANT_ENCAPSED_STRING)) {
121+
continue;
122+
}
123+
124+
$constants[$tokens[$constantNameIndex]->getContent()] = $tokens[$constantValueIndex]->getContent();
125+
}
126+
127+
return [$this->getClassConstantsMap($constants), $constantsIndices];
128+
}
129+
130+
private function getClassConstantsMap(array $constants): array
131+
{
132+
$map = [];
133+
$valuesCount = [];
134+
135+
foreach ($constants as $name => $value) {
136+
$map[$value] = $name;
137+
$valuesCount[$value] = $valuesCount[$value] ?? 0;
138+
$valuesCount[$value]++;
139+
140+
if (($valuesCount[$value] ?? 0) > 1) {
141+
unset($map[$value]);
142+
}
143+
}
144+
145+
return $map;
146+
}
147+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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\ClassConstantUsageFixer
18+
*/
19+
final class ClassConstantUsageFixerTest 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 'multiple constants with the same value' => [
40+
<<<'PHP'
41+
<?php
42+
class Foo
43+
{
44+
public const BAR = 'a';
45+
public const BAZ = 'a';
46+
public function f()
47+
{
48+
return 'a';
49+
}
50+
}
51+
PHP,
52+
];
53+
54+
yield 'constants all over the class' => [
55+
<<<'PHP'
56+
<?php
57+
class Foo
58+
{
59+
public const BAR = 'bar';
60+
public function f()
61+
{
62+
return self::BAR . self::BAZ;
63+
}
64+
public const BAZ = 'baz';
65+
}
66+
PHP,
67+
<<<'PHP'
68+
<?php
69+
class Foo
70+
{
71+
public const BAR = 'bar';
72+
public function f()
73+
{
74+
return 'bar' . 'baz';
75+
}
76+
public const BAZ = 'baz';
77+
}
78+
PHP,
79+
];
80+
}
81+
}

0 commit comments

Comments
 (0)