Просмотр исходного кода

feature #4742 FunctionToConstantFixer - get_class($this) support (SpacePossum)

This PR was merged into the 2.15 branch.

Discussion
----------

FunctionToConstantFixer - get_class($this) support

closes https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/3900

Commits
-------

1c93905a FunctionToConstantFixer - get_class($this) support
SpacePossum 5 лет назад
Родитель
Сommit
9eea6ad9a0

+ 3 - 2
README.rst

@@ -709,8 +709,9 @@ Choose from the list of available rules:
   Configuration options:
 
   - ``functions`` (a subset of ``['get_called_class', 'get_class',
-    'php_sapi_name', 'phpversion', 'pi']``): list of function names to fix;
-    defaults to ``['get_class', 'php_sapi_name', 'phpversion', 'pi']``
+    'php_sapi_name', 'phpversion', 'pi', 'get_class_this']``): list of
+    function names to fix; defaults to ``['get_class', 'php_sapi_name',
+    'phpversion', 'pi']``
 
 * **function_typehint_space** [@Symfony, @PhpCsFixer]
 

+ 106 - 21
src/Fixer/LanguageConstruct/FunctionToConstantFixer.php

@@ -19,6 +19,7 @@ use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
 use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
 use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
+use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
 use PhpCsFixer\Tokenizer\CT;
 use PhpCsFixer\Tokenizer\Token;
 use PhpCsFixer\Tokenizer\Tokens;
@@ -51,6 +52,11 @@ final class FunctionToConstantFixer extends AbstractFixer implements Configurati
                 'php_sapi_name' => [new Token([T_STRING, 'PHP_SAPI'])],
                 'phpversion' => [new Token([T_STRING, 'PHP_VERSION'])],
                 'pi' => [new Token([T_STRING, 'M_PI'])],
+                'get_class_this' => [
+                    new Token([T_STATIC, 'static']),
+                    new Token([T_DOUBLE_COLON, '::']),
+                    new Token([CT::T_CLASS_CONSTANT, 'class']),
+                ],
             ];
         }
 
@@ -78,8 +84,13 @@ final class FunctionToConstantFixer extends AbstractFixer implements Configurati
         return new FixerDefinition(
             'Replace core functions calls returning constants with the constants.',
             [
-                new CodeSample("<?php\necho phpversion();\necho pi();\necho php_sapi_name();\n"),
-                new CodeSample("<?php\necho phpversion();\necho pi();\n", ['functions' => ['phpversion']]),
+                new CodeSample(
+                    "<?php\necho phpversion();\necho pi();\necho php_sapi_name();\nclass Foo\n{\n    public function Bar()\n    {\n        echo get_class();\n        echo get_called_class();\n    }\n}\n"
+                ),
+                new CodeSample(
+                    "<?php\necho phpversion();\necho pi();\nclass Foo\n{\n    public function Bar()\n    {\n        echo get_class();\n        get_class(\$this);\n        echo get_called_class();\n    }\n}\n",
+                    ['functions' => ['phpversion', 'get_called_class', 'get_class_this']]
+                ),
             ],
             null,
             'Risky when any of the configured functions to replace are overridden.'
@@ -118,8 +129,10 @@ final class FunctionToConstantFixer extends AbstractFixer implements Configurati
      */
     protected function applyFix(\SplFileInfo $file, Tokens $tokens)
     {
+        $functionAnalyzer = new FunctionsAnalyzer();
+
         for ($index = $tokens->count() - 4; $index > 0; --$index) {
-            $candidate = $this->getReplaceCandidate($tokens, $index);
+            $candidate = $this->getReplaceCandidate($tokens, $functionAnalyzer, $index);
             if (null === $candidate) {
                 continue;
             }
@@ -164,8 +177,13 @@ final class FunctionToConstantFixer extends AbstractFixer implements Configurati
      */
     private function fixFunctionCallToConstant(Tokens $tokens, $index, $braceOpenIndex, $braceCloseIndex, array $replacements)
     {
-        $tokens->clearTokenAndMergeSurroundingWhitespace($braceCloseIndex);
-        $tokens->clearTokenAndMergeSurroundingWhitespace($braceOpenIndex);
+        for ($i = $braceCloseIndex; $i >= $braceOpenIndex; --$i) {
+            if ($tokens[$i]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT]])) {
+                continue;
+            }
+
+            $tokens->clearTokenAndMergeSurroundingWhitespace($i);
+        }
 
         if ($replacements[0]->isGivenKind([T_CLASS_C, T_STATIC])) {
             $prevIndex = $tokens->getPrevMeaningfulToken($index);
@@ -184,43 +202,110 @@ final class FunctionToConstantFixer extends AbstractFixer implements Configurati
      *
      * @return null|array
      */
-    private function getReplaceCandidate(Tokens $tokens, $index)
-    {
-        // test if we are at a function all
+    private function getReplaceCandidate(
+        Tokens $tokens,
+        FunctionsAnalyzer $functionAnalyzer,
+        $index
+    ) {
         if (!$tokens[$index]->isGivenKind(T_STRING)) {
             return null;
         }
 
+        $lowerContent = strtolower($tokens[$index]->getContent());
+
+        if ('get_class' === $lowerContent) {
+            return $this->fixGetClassCall($tokens, $functionAnalyzer, $index);
+        }
+
+        if (!isset($this->functionsFixMap[$lowerContent])) {
+            return null;
+        }
+
+        if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
+            return null;
+        }
+
+        // test if function call without parameters
         $braceOpenIndex = $tokens->getNextMeaningfulToken($index);
         if (!$tokens[$braceOpenIndex]->equals('(')) {
             return null;
         }
 
-        // test if function call without parameters
         $braceCloseIndex = $tokens->getNextMeaningfulToken($braceOpenIndex);
         if (!$tokens[$braceCloseIndex]->equals(')')) {
             return null;
         }
 
-        $functionNamePrefix = $tokens->getPrevMeaningfulToken($index);
-        if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, CT::T_RETURN_REF])) {
+        return $this->getReplacementTokenClones($lowerContent, $braceOpenIndex, $braceCloseIndex);
+    }
+
+    /**
+     * @param int $index
+     *
+     * @return null|array
+     */
+    private function fixGetClassCall(
+        Tokens $tokens,
+        FunctionsAnalyzer $functionAnalyzer,
+        $index
+    ) {
+        if (!isset($this->functionsFixMap['get_class']) && !isset($this->functionsFixMap['get_class_this'])) {
+            return null;
+        }
+
+        if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
             return null;
         }
 
-        if ($tokens[$functionNamePrefix]->isGivenKind(T_NS_SEPARATOR)) {
-            // skip if the call is to a constructor or to a function in a namespace other than the default
-            $prevIndex = $tokens->getPrevMeaningfulToken($functionNamePrefix);
-            if ($tokens[$prevIndex]->isGivenKind([T_STRING, T_NEW])) {
-                return null;
+        $braceOpenIndex = $tokens->getNextMeaningfulToken($index);
+        $braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex);
+
+        if ($braceCloseIndex === $tokens->getNextMeaningfulToken($braceOpenIndex)) { // no arguments passed
+            if (isset($this->functionsFixMap['get_class'])) {
+                return $this->getReplacementTokenClones('get_class', $braceOpenIndex, $braceCloseIndex);
             }
-        }
+        } else {
+            if (isset($this->functionsFixMap['get_class_this'])) {
+                $isThis = false;
 
-        // test if the function call is to a native PHP function
-        $lowerContent = strtolower($tokens[$index]->getContent());
-        if (!\array_key_exists($lowerContent, $this->functionsFixMap)) {
-            return null;
+                for ($i = $braceOpenIndex + 1; $i < $braceCloseIndex; ++$i) {
+                    if ($tokens[$i]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], ')'])) {
+                        continue;
+                    }
+
+                    if ($tokens[$i]->isGivenKind(T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) {
+                        $isThis = true;
+
+                        continue;
+                    }
+
+                    if (false === $isThis && $tokens[$i]->equals('(')) {
+                        continue;
+                    }
+
+                    $isThis = false;
+
+                    break;
+                }
+
+                if ($isThis) {
+                    return $this->getReplacementTokenClones('get_class_this', $braceOpenIndex, $braceCloseIndex);
+                }
+            }
         }
 
+        return null;
+    }
+
+    /**
+     * @param string $lowerContent
+     * @param int    $braceOpenIndex
+     * @param int    $braceCloseIndex
+     *
+     * @return array
+     */
+    private function getReplacementTokenClones($lowerContent, $braceOpenIndex, $braceCloseIndex)
+    {
         $clones = [];
         foreach ($this->functionsFixMap[$lowerContent] as $token) {
             $clones[] = clone $token;

+ 10 - 1
src/RuleSet.php

@@ -177,7 +177,16 @@ final class RuleSet implements RuleSetInterface
             'error_suppression' => true,
             'fopen_flag_order' => true,
             'fopen_flags' => ['b_mode' => false],
-            'function_to_constant' => true,
+            'function_to_constant' => [
+                'functions' => [
+                    'get_called_class',
+                    'get_class',
+                    'get_class_this',
+                    'php_sapi_name',
+                    'phpversion',
+                    'pi',
+                ],
+            ],
             'implode_call' => true,
             'is_null' => true,
             'modernize_types_casting' => true,

+ 28 - 0
tests/Fixer/LanguageConstruct/FunctionToConstantFixerTest.php

@@ -226,6 +226,34 @@ get_called_class#1
                 null,
                 ['functions' => ['get_called_class']],
             ],
+            [
+                '<?php class Foo{ public function Bar(){ echo static::class  ; }}',
+                '<?php class Foo{ public function Bar(){ echo get_class( $This ); }}',
+                ['functions' => ['get_class_this']],
+            ],
+            [
+                '<?php class Foo{ public function Bar(){ echo static::class; get_class(1, 2); get_class($a); get_class($a, $b);}}',
+                '<?php class Foo{ public function Bar(){ echo get_class($this); get_class(1, 2); get_class($a); get_class($a, $b);}}',
+                ['functions' => ['get_class_this']],
+            ],
+            [
+                '<?php class Foo{ public function Bar(){ echo static::class /* 0 */  /* 1 */ ;}}',
+                '<?php class Foo{ public function Bar(){ echo \get_class( /* 0 */ $this /* 1 */ );}}',
+                ['functions' => ['get_class_this']],
+            ],
+            [
+                '<?php class Foo{ public function Bar(){ echo static::class; echo __CLASS__; }}',
+                '<?php class Foo{ public function Bar(){ echo \get_class((($this))); echo get_class(); }}',
+                ['functions' => ['get_class_this', 'get_class']],
+            ],
+            [
+                '<?php
+                    class Foo{ public function Bar(){ echo $reflection = new \ReflectionClass(get_class($this->extension)); }}
+                    class Foo{ public function Bar(){ echo $reflection = new \ReflectionClass(get_class($this() )); }}
+                ',
+                null,
+                ['functions' => ['get_class_this']],
+            ],
         ];
     }