Browse Source

ExplicitStringVariableFixer: handle parsed array and object

Filippo Tessarotto 7 years ago
parent
commit
73036dd504

+ 53 - 7
src/Fixer/StringNotation/ExplicitStringVariableFixer.php

@@ -31,7 +31,15 @@ final class ExplicitStringVariableFixer extends AbstractFixer
     {
         return new FixerDefinition(
             'Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax.',
-            [new CodeSample('<?php $a = "My name is $name!";'."\n")],
+            [new CodeSample(
+                <<<'EOT'
+<?php
+$a = "My name is $name !";
+$b = "I live in $state->country !";
+$c = "I have $farm[0] chickens !";
+
+EOT
+            )],
             'The reasoning behind this rule is the following:'
                 ."\n".'- When there are two valid ways of doing the same thing, using both is confusing, there should be a coding standard to follow'
                 ."\n".'- PHP manual marks `"$var"` syntax as implicit and `"${var}"` syntax as explicit: explicit code should always be preferred'
@@ -53,23 +61,61 @@ final class ExplicitStringVariableFixer extends AbstractFixer
      */
     protected function applyFix(\SplFileInfo $file, Tokens $tokens)
     {
-        foreach ($tokens as $index => $token) {
+        for ($index = count($tokens) - 1; $index > 0; --$index) {
+            $token = $tokens[$index];
             if (!$token->isGivenKind(T_VARIABLE)) {
                 continue;
             }
 
             $prevToken = $tokens[$index - 1];
-            if (
-                $prevToken->isGivenKind(T_ENCAPSED_AND_WHITESPACE)
-                || $prevToken->isGivenKind(T_START_HEREDOC)
-                || '"' === $prevToken->getContent()
-            ) {
+            if (!$this->isStringPartToken($prevToken)) {
+                continue;
+            }
+
+            $variableTokens = [
+                $index => $token,
+            ];
+            $firstVariableTokenIndex = $index;
+            $lastVariableTokenIndex = $index;
+
+            $nextIndex = $index + 1;
+            while (!$this->isStringPartToken($tokens[$nextIndex])) {
+                $variableTokens[$nextIndex] = $tokens[$nextIndex];
+                $lastVariableTokenIndex = $nextIndex;
+                ++$nextIndex;
+            }
+
+            if (1 === count($variableTokens)) {
                 $tokens->overrideRange($index, $index, [
                     new Token([T_DOLLAR_OPEN_CURLY_BRACES, '${']),
                     new Token([T_STRING_VARNAME, substr($token->getContent(), 1)]),
                     new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']),
                 ]);
+            } else {
+                foreach ($variableTokens as $variablePartIndex => $variablePartToken) {
+                    if ($variablePartToken->isGivenKind(T_NUM_STRING)) {
+                        $tokens[$variablePartIndex] = new Token([T_LNUMBER, $variablePartToken->getContent()]);
+                    }
+                }
+
+                $tokens->insertAt($lastVariableTokenIndex + 1, new Token([CT::T_CURLY_CLOSE, '}']));
+                $tokens->insertAt($firstVariableTokenIndex, new Token([T_CURLY_OPEN, '{']));
             }
         }
     }
+
+    /**
+     * Check if token is a part of a string.
+     *
+     * @param Token $token The token to check
+     *
+     * @return bool
+     */
+    private function isStringPartToken(Token $token)
+    {
+        return $token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)
+            || $token->isGivenKind(T_START_HEREDOC)
+            || '"' === $token->getContent()
+        ;
+    }
 }

+ 78 - 0
tests/Fixer/StringNotation/ExplicitStringVariableFixerTest.php

@@ -66,6 +66,14 @@ EOF;
                 '<?php $a = "$b";',
             ],
             [
+                '<?php $a = "${b} start";',
+                '<?php $a = "$b start";',
+            ],
+            [
+                '<?php $a = "end ${b}";',
+                '<?php $a = "end $b";',
+            ],
+            [
 '<?php $a = <<<EOF
 ${b}
 EOF;
@@ -97,6 +105,76 @@ $b
 EOF;
 ',
             ],
+            [
+                '<?php $a = "My name is {$object->property} !";',
+                '<?php $a = "My name is $object->property !";',
+            ],
+            [
+                '<?php $a = "My name is {$array[1]} !";',
+                '<?php $a = "My name is $array[1] !";',
+            ],
+            [
+                '<?php $a = "My name is {$array[MY_CONSTANT]} !";',
+                '<?php $a = "My name is $array[MY_CONSTANT] !";',
+            ],
+            [
+                '<?php $a = "Closure not allowed ${closure}() text";',
+                '<?php $a = "Closure not allowed $closure() text";',
+            ],
+            [
+                '<?php $a = "Complex object chaining not allowed {$object->property}->method()->array[1] text";',
+                '<?php $a = "Complex object chaining not allowed $object->property->method()->array[1] text";',
+            ],
+            [
+                '<?php $a = "Complex array chaining not allowed {$array[1]}[2][MY_CONSTANT] text";',
+                '<?php $a = "Complex array chaining not allowed $array[1][2][MY_CONSTANT] text";',
+            ],
+            [
+                '<?php $a = "{$a->b} start";',
+                '<?php $a = "$a->b start";',
+            ],
+            [
+                '<?php $a = "end {$a->b}";',
+                '<?php $a = "end $a->b";',
+            ],
+            [
+                '<?php $a = "{$a[1]} start";',
+                '<?php $a = "$a[1] start";',
+            ],
+            [
+                '<?php $a = "end {$a[1]}";',
+                '<?php $a = "end $a[1]";',
+            ],
+        ];
+    }
+
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideTestFix71Cases
+     * @requires PHP 7.1
+     */
+    public function testFix71($expected, $input = null)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideTestFix71Cases()
+    {
+        return [
+            [
+                '<?php $a = "My name is {$array[-1]} !";',
+                '<?php $a = "My name is $array[-1] !";',
+            ],
+            [
+                '<?php $a = "{$a[-1]} start";',
+                '<?php $a = "$a[-1] start";',
+            ],
+            [
+                '<?php $a = "end {$a[-1]}";',
+                '<?php $a = "end $a[-1]";',
+            ],
         ];
     }
 }