Skip to content

Commit 1a2a83a

Browse files
committed
Add SelfStaticAccessorFixer
1 parent c992b3d commit 1a2a83a

File tree

6 files changed

+629
-0
lines changed

6 files changed

+629
-0
lines changed

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,11 @@ Choose from the list of available rules:
16961696

16971697
*Risky rule: risky when using dynamic calls like get_called_class() or late static binding.*
16981698

1699+
* **self_static_accessor**
1700+
1701+
Inside a final class or anonymous class ``self`` should be preferred to
1702+
``static``.
1703+
16991704
* **semicolon_after_instruction** [@Symfony, @PhpCsFixer]
17001705

17011706
Instructions must be terminated with a semicolon.
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP CS Fixer.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
8+
*
9+
* This source file is subject to the MIT license that is bundled
10+
* with this source code in the file LICENSE.
11+
*/
12+
13+
namespace PhpCsFixer\Fixer\ClassNotation;
14+
15+
use PhpCsFixer\AbstractFixer;
16+
use PhpCsFixer\FixerDefinition\CodeSample;
17+
use PhpCsFixer\FixerDefinition\FixerDefinition;
18+
use PhpCsFixer\FixerDefinition\VersionSpecification;
19+
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
20+
use PhpCsFixer\Tokenizer\Token;
21+
use PhpCsFixer\Tokenizer\Tokens;
22+
use PhpCsFixer\Tokenizer\TokensAnalyzer;
23+
24+
final class SelfStaticAccessorFixer extends AbstractFixer
25+
{
26+
/**
27+
* @var TokensAnalyzer
28+
*/
29+
private $tokensAnalyzer;
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function getDefinition()
35+
{
36+
return new FixerDefinition(
37+
'Inside a final class or anonymous class `self` should be preferred to `static`.',
38+
[
39+
new CodeSample(
40+
'<?php
41+
final class Sample
42+
{
43+
private static $A = 1;
44+
45+
public function getBar()
46+
{
47+
return static::class.static::test().static::$A;
48+
}
49+
50+
private static function test()
51+
{
52+
return \'test\';
53+
}
54+
}
55+
'
56+
),
57+
new CodeSample(
58+
'<?php
59+
final class Foo
60+
{
61+
public function bar()
62+
{
63+
return new static();
64+
}
65+
}
66+
'
67+
),
68+
new CodeSample(
69+
'<?php
70+
final class Foo
71+
{
72+
public function isBar()
73+
{
74+
return $foo instanceof static;
75+
}
76+
}
77+
'
78+
),
79+
new VersionSpecificCodeSample(
80+
'<?php
81+
$a = new class() {
82+
public function getBar()
83+
{
84+
return static::class;
85+
}
86+
};
87+
',
88+
new VersionSpecification(70000)
89+
),
90+
]
91+
);
92+
}
93+
94+
/**
95+
* {@inheritdoc}
96+
*/
97+
public function isCandidate(Tokens $tokens)
98+
{
99+
return $tokens->isAllTokenKindsFound([T_CLASS, T_STATIC]) && $tokens->isAnyTokenKindsFound([T_DOUBLE_COLON, T_NEW, T_INSTANCEOF]);
100+
}
101+
102+
public function getPriority()
103+
{
104+
// must be run after FinalInternalClassFixer and FunctionToConstantFixer
105+
return -10;
106+
}
107+
108+
/**
109+
* {@inheritdoc}
110+
*/
111+
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
112+
{
113+
$this->tokensAnalyzer = $tokensAnalyzer = new TokensAnalyzer($tokens);
114+
115+
$classIndex = $tokens->getNextTokenOfKind(0, [[T_CLASS]]);
116+
117+
while (null !== $classIndex) {
118+
if (
119+
$tokens[$tokens->getPrevMeaningfulToken($classIndex)]->isGivenKind(T_FINAL)
120+
|| $tokensAnalyzer->isAnonymousClass($classIndex)
121+
) {
122+
$classIndex = $this->fixClass($tokens, $classIndex);
123+
}
124+
125+
$classIndex = $tokens->getNextTokenOfKind($classIndex, [[T_CLASS]]);
126+
}
127+
}
128+
129+
/**
130+
* @param Tokens $tokens
131+
* @param int $index
132+
*
133+
* @return int
134+
*/
135+
private function fixClass(Tokens $tokens, $index)
136+
{
137+
$index = $tokens->getNextTokenOfKind($index, ['{']);
138+
$classOpenCount = 1;
139+
140+
while ($classOpenCount > 0) {
141+
++$index;
142+
143+
if ($tokens[$index]->equals('{')) {
144+
++$classOpenCount;
145+
146+
continue;
147+
}
148+
149+
if ($tokens[$index]->equals('}')) {
150+
--$classOpenCount;
151+
152+
continue;
153+
}
154+
155+
if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
156+
// do not fix inside lambda
157+
if ($this->tokensAnalyzer->isLambda($index)) {
158+
// figure out where the lambda starts
159+
$index = $tokens->getNextTokenOfKind($index, ['{']);
160+
$openCount = 1;
161+
162+
do {
163+
$index = $tokens->getNextTokenOfKind($index, ['}', '{', [T_CLASS]]);
164+
if ($tokens[$index]->equals('}')) {
165+
--$openCount;
166+
} elseif ($tokens[$index]->equals('{')) {
167+
++$openCount;
168+
} else {
169+
$index = $this->fixClass($tokens, $index);
170+
}
171+
} while ($openCount > 0);
172+
}
173+
174+
continue;
175+
}
176+
177+
if ($tokens[$index]->isGivenKind([T_NEW, T_INSTANCEOF])) {
178+
$index = $tokens->getNextMeaningfulToken($index);
179+
180+
if ($tokens[$index]->isGivenKind(T_STATIC)) {
181+
$tokens[$index] = new Token([T_STRING, 'self']);
182+
}
183+
184+
continue;
185+
}
186+
187+
if (!$tokens[$index]->isGivenKind(T_STATIC)) {
188+
continue;
189+
}
190+
191+
$staticIndex = $index;
192+
$index = $tokens->getNextMeaningfulToken($index);
193+
194+
if (!$tokens[$index]->isGivenKind(T_DOUBLE_COLON)) {
195+
continue;
196+
}
197+
198+
$tokens[$staticIndex] = new Token([T_STRING, 'self']);
199+
}
200+
201+
return $index;
202+
}
203+
}

tests/AutoReview/FixerFactoryTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@ public function provideFixersPriorityCases()
8989
[$fixers['escape_implicit_backslashes'], $fixers['heredoc_to_nowdoc']],
9090
[$fixers['escape_implicit_backslashes'], $fixers['single_quote']],
9191
[$fixers['explicit_string_variable'], $fixers['simple_to_complex_string_variable']],
92+
[$fixers['final_internal_class'], $fixers['self_static_accessor']],
9293
[$fixers['fully_qualified_strict_types'], $fixers['no_superfluous_phpdoc_tags']],
9394
[$fixers['function_to_constant'], $fixers['native_function_casing']],
9495
[$fixers['function_to_constant'], $fixers['no_extra_blank_lines']],
9596
[$fixers['function_to_constant'], $fixers['no_singleline_whitespace_before_semicolons']],
9697
[$fixers['function_to_constant'], $fixers['no_trailing_whitespace']],
9798
[$fixers['function_to_constant'], $fixers['no_whitespace_in_blank_line']],
99+
[$fixers['function_to_constant'], $fixers['self_static_accessor']],
98100
[$fixers['general_phpdoc_annotation_remove'], $fixers['phpdoc_separation']],
99101
[$fixers['general_phpdoc_annotation_remove'], $fixers['phpdoc_trim']],
100102
[$fixers['general_phpdoc_annotation_remove'], $fixers['no_empty_phpdoc']],

0 commit comments

Comments
 (0)