Skip to content

Commit ed2f77b

Browse files
committed
feature #3739 Add MagicMethodCasingFixer (SpacePossum)
This PR was merged into the 2.13-dev branch. Discussion ---------- Add MagicMethodCasingFixer ``` $ php php-cs-fixer describe magic_method_casing Description of magic_method_casing rule. Magic method definitions and calls must be using the correct casing. Fixing examples: * Example #1. ---------- begin diff ---------- --- Original +++ New @@ -1,7 +1,7 @@ <?php class Foo { - public function __Sleep() + public function __sleep() { } } ----------- end diff ----------- * Example #2. ---------- begin diff ---------- --- Original +++ New @@ -1,2 +1,2 @@ <?php -$foo->__INVOKE(1); +$foo->__invoke(1); ----------- end diff ----------- ``` Commits ------- e45919f Add MagicMethodCasingFixer
2 parents 8a36d51 + e45919f commit ed2f77b

File tree

4 files changed

+550
-0
lines changed

4 files changed

+550
-0
lines changed

README.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,10 @@ Choose from the list of available rules:
801801

802802
Magic constants should be referred to using the correct casing.
803803

804+
* **magic_method_casing** [@Symfony]
805+
806+
Magic method definitions and calls must be using the correct casing.
807+
804808
* **mb_str_functions**
805809

806810
Replace non multibyte-safe functions with corresponding mb function.
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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\Casing;
14+
15+
use PhpCsFixer\AbstractFixer;
16+
use PhpCsFixer\FixerDefinition\CodeSample;
17+
use PhpCsFixer\FixerDefinition\FixerDefinition;
18+
use PhpCsFixer\Tokenizer\Token;
19+
use PhpCsFixer\Tokenizer\Tokens;
20+
21+
/**
22+
* @author SpacePossum
23+
*/
24+
final class MagicMethodCasingFixer extends AbstractFixer
25+
{
26+
private static $magicNames = [
27+
'__call' => '__call',
28+
'__callstatic' => '__callStatic',
29+
'__clone' => '__clone',
30+
'__construct' => '__construct',
31+
'__debuginfo' => '__debugInfo',
32+
'__destruct' => '__destruct',
33+
'__get' => '__get',
34+
'__invoke' => '__invoke',
35+
'__isset' => '__isset',
36+
'__set' => '__set',
37+
'__set_state' => '__set_state',
38+
'__sleep' => '__sleep',
39+
'__tostring' => '__toString',
40+
'__unset' => '__unset',
41+
'__wakeup' => '__wakeup',
42+
];
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function getDefinition()
48+
{
49+
return new FixerDefinition(
50+
'Magic method definitions and calls must be using the correct casing.',
51+
[
52+
new CodeSample(
53+
'<?php
54+
class Foo
55+
{
56+
public function __Sleep()
57+
{
58+
}
59+
}
60+
'
61+
),
62+
new CodeSample(
63+
'<?php
64+
$foo->__INVOKE(1);
65+
'
66+
),
67+
]
68+
);
69+
}
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function isCandidate(Tokens $tokens)
75+
{
76+
return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_OBJECT_OPERATOR, T_DOUBLE_COLON]);
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
83+
{
84+
$inClass = 0;
85+
$tokenCount = \count($tokens);
86+
87+
for ($index = 1; $index < $tokenCount - 2; ++$index) {
88+
if (0 === $inClass && $tokens[$index]->isClassy()) {
89+
$inClass = 1;
90+
$index = $tokens->getNextTokenOfKind($index, ['{']);
91+
92+
continue;
93+
}
94+
95+
if (0 !== $inClass) {
96+
if ($tokens[$index]->equals('{')) {
97+
++$inClass;
98+
99+
continue;
100+
}
101+
102+
if ($tokens[$index]->equals('}')) {
103+
--$inClass;
104+
105+
continue;
106+
}
107+
}
108+
109+
if (!$tokens[$index]->isGivenKind(T_STRING)) {
110+
continue; // wrong type
111+
}
112+
113+
$content = $tokens[$index]->getContent();
114+
if ('__' !== substr($content, 0, 2)) {
115+
continue; // cheap look ahead
116+
}
117+
118+
$name = strtolower($content);
119+
120+
if (!$this->isMagicMethodName($name)) {
121+
continue; // method name is not one of the magic ones we can fix
122+
}
123+
124+
$nameInCorrectCasing = $this->getMagicMethodNameInCorrectCasing($name);
125+
if ($nameInCorrectCasing === $content) {
126+
continue; // method name is already in the correct casing, no fix needed
127+
}
128+
129+
if ($this->isFunctionSignature($tokens, $index)) {
130+
if (0 !== $inClass) {
131+
// this is a method definition we want to fix
132+
$this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing);
133+
}
134+
135+
continue;
136+
}
137+
138+
if ($this->isMethodCall($tokens, $index)) {
139+
$this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing);
140+
141+
continue;
142+
}
143+
144+
if (
145+
('__callstatic' === $name || '__set_state' === $name)
146+
&& $this->isStaticMethodCall($tokens, $index)
147+
) {
148+
$this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing);
149+
}
150+
}
151+
}
152+
153+
/**
154+
* @param Tokens $tokens
155+
* @param int $index
156+
*
157+
* @return bool
158+
*/
159+
private function isFunctionSignature(Tokens $tokens, $index)
160+
{
161+
$prevIndex = $tokens->getPrevMeaningfulToken($index);
162+
if (!$tokens[$prevIndex]->isGivenKind(T_FUNCTION)) {
163+
return false; // not a method signature
164+
}
165+
166+
return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(');
167+
}
168+
169+
/**
170+
* @param Tokens $tokens
171+
* @param int $index
172+
*
173+
* @return bool
174+
*/
175+
private function isMethodCall(Tokens $tokens, $index)
176+
{
177+
$prevIndex = $tokens->getPrevMeaningfulToken($index);
178+
if (!$tokens[$prevIndex]->equals([T_OBJECT_OPERATOR, '->'])) {
179+
return false; // not a "simple" method call
180+
}
181+
182+
return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(');
183+
}
184+
185+
/**
186+
* @param Tokens $tokens
187+
* @param int $index
188+
*
189+
* @return bool
190+
*/
191+
private function isStaticMethodCall(Tokens $tokens, $index)
192+
{
193+
$prevIndex = $tokens->getPrevMeaningfulToken($index);
194+
if (!$tokens[$prevIndex]->isGivenKind(T_DOUBLE_COLON)) {
195+
return false; // not a "simple" static method call
196+
}
197+
198+
return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(');
199+
}
200+
201+
/**
202+
* @param string $name
203+
*
204+
* @return bool
205+
*/
206+
private function isMagicMethodName($name)
207+
{
208+
return isset(self::$magicNames[$name]);
209+
}
210+
211+
/**
212+
* @param string $name name of a magic method
213+
*
214+
* @return string
215+
*/
216+
private function getMagicMethodNameInCorrectCasing($name)
217+
{
218+
return self::$magicNames[$name];
219+
}
220+
221+
/**
222+
* @param Tokens $tokens
223+
* @param int $index
224+
* @param string $nameInCorrectCasing
225+
*/
226+
private function setTokenToCorrectCasing(Tokens $tokens, $index, $nameInCorrectCasing)
227+
{
228+
$tokens[$index] = new Token([T_STRING, $nameInCorrectCasing]);
229+
}
230+
}

src/RuleSet.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ final class RuleSet implements RuleSetInterface
7777
'lowercase_cast' => true,
7878
'lowercase_static_reference' => true,
7979
'magic_constant_casing' => true,
80+
'magic_method_casing' => true,
8081
'method_argument_space' => true,
8182
'native_function_casing' => true,
8283
'new_with_braces' => true,

0 commit comments

Comments
 (0)