Skip to content

Commit

Permalink
feature #3739 Add MagicMethodCasingFixer (SpacePossum)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
keradus committed Aug 22, 2018
2 parents 8a36d51 + e45919f commit ed2f77b
Show file tree
Hide file tree
Showing 4 changed files with 550 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.rst
Expand Up @@ -801,6 +801,10 @@ Choose from the list of available rules:

Magic constants should be referred to using the correct casing.

* **magic_method_casing** [@Symfony]

Magic method definitions and calls must be using the correct casing.

* **mb_str_functions**

Replace non multibyte-safe functions with corresponding mb function.
Expand Down
230 changes: 230 additions & 0 deletions src/Fixer/Casing/MagicMethodCasingFixer.php
@@ -0,0 +1,230 @@
<?php

/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace PhpCsFixer\Fixer\Casing;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
* @author SpacePossum
*/
final class MagicMethodCasingFixer extends AbstractFixer
{
private static $magicNames = [
'__call' => '__call',
'__callstatic' => '__callStatic',
'__clone' => '__clone',
'__construct' => '__construct',
'__debuginfo' => '__debugInfo',
'__destruct' => '__destruct',
'__get' => '__get',
'__invoke' => '__invoke',
'__isset' => '__isset',
'__set' => '__set',
'__set_state' => '__set_state',
'__sleep' => '__sleep',
'__tostring' => '__toString',
'__unset' => '__unset',
'__wakeup' => '__wakeup',
];

/**
* {@inheritdoc}
*/
public function getDefinition()
{
return new FixerDefinition(
'Magic method definitions and calls must be using the correct casing.',
[
new CodeSample(
'<?php
class Foo
{
public function __Sleep()
{
}
}
'
),
new CodeSample(
'<?php
$foo->__INVOKE(1);
'
),
]
);
}

/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens)
{
return $tokens->isTokenKindFound(T_STRING) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_OBJECT_OPERATOR, T_DOUBLE_COLON]);
}

/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
{
$inClass = 0;
$tokenCount = \count($tokens);

for ($index = 1; $index < $tokenCount - 2; ++$index) {
if (0 === $inClass && $tokens[$index]->isClassy()) {
$inClass = 1;
$index = $tokens->getNextTokenOfKind($index, ['{']);

continue;
}

if (0 !== $inClass) {
if ($tokens[$index]->equals('{')) {
++$inClass;

continue;
}

if ($tokens[$index]->equals('}')) {
--$inClass;

continue;
}
}

if (!$tokens[$index]->isGivenKind(T_STRING)) {
continue; // wrong type
}

$content = $tokens[$index]->getContent();
if ('__' !== substr($content, 0, 2)) {
continue; // cheap look ahead
}

$name = strtolower($content);

if (!$this->isMagicMethodName($name)) {
continue; // method name is not one of the magic ones we can fix
}

$nameInCorrectCasing = $this->getMagicMethodNameInCorrectCasing($name);
if ($nameInCorrectCasing === $content) {
continue; // method name is already in the correct casing, no fix needed
}

if ($this->isFunctionSignature($tokens, $index)) {
if (0 !== $inClass) {
// this is a method definition we want to fix
$this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing);
}

continue;
}

if ($this->isMethodCall($tokens, $index)) {
$this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing);

continue;
}

if (
('__callstatic' === $name || '__set_state' === $name)
&& $this->isStaticMethodCall($tokens, $index)
) {
$this->setTokenToCorrectCasing($tokens, $index, $nameInCorrectCasing);
}
}
}

/**
* @param Tokens $tokens
* @param int $index
*
* @return bool
*/
private function isFunctionSignature(Tokens $tokens, $index)
{
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$prevIndex]->isGivenKind(T_FUNCTION)) {
return false; // not a method signature
}

return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(');
}

/**
* @param Tokens $tokens
* @param int $index
*
* @return bool
*/
private function isMethodCall(Tokens $tokens, $index)
{
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$prevIndex]->equals([T_OBJECT_OPERATOR, '->'])) {
return false; // not a "simple" method call
}

return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(');
}

/**
* @param Tokens $tokens
* @param int $index
*
* @return bool
*/
private function isStaticMethodCall(Tokens $tokens, $index)
{
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$prevIndex]->isGivenKind(T_DOUBLE_COLON)) {
return false; // not a "simple" static method call
}

return $tokens[$tokens->getNextMeaningfulToken($index)]->equals('(');
}

/**
* @param string $name
*
* @return bool
*/
private function isMagicMethodName($name)
{
return isset(self::$magicNames[$name]);
}

/**
* @param string $name name of a magic method
*
* @return string
*/
private function getMagicMethodNameInCorrectCasing($name)
{
return self::$magicNames[$name];
}

/**
* @param Tokens $tokens
* @param int $index
* @param string $nameInCorrectCasing
*/
private function setTokenToCorrectCasing(Tokens $tokens, $index, $nameInCorrectCasing)
{
$tokens[$index] = new Token([T_STRING, $nameInCorrectCasing]);
}
}
1 change: 1 addition & 0 deletions src/RuleSet.php
Expand Up @@ -77,6 +77,7 @@ final class RuleSet implements RuleSetInterface
'lowercase_cast' => true,
'lowercase_static_reference' => true,
'magic_constant_casing' => true,
'magic_method_casing' => true,
'method_argument_space' => true,
'native_function_casing' => true,
'new_with_braces' => true,
Expand Down

0 comments on commit ed2f77b

Please sign in to comment.