Browse Source

NoTrailingWhitespaceInStringFixer - introduction

Gregor Harlan 5 years ago
parent
commit
591b7fa990

+ 6 - 0
README.rst

@@ -1218,6 +1218,12 @@ Choose from the list of available rules:
 
   There MUST be no trailing spaces inside comment or PHPDoc.
 
+* **no_trailing_whitespace_in_string** [@PhpCsFixer:risky]
+
+  There must be no trailing whitespace in strings.
+
+  *Risky rule: changing the whitespaces in strings might affect string comparisons and outputs.*
+
 * **no_unneeded_control_parentheses** [@Symfony, @PhpCsFixer]
 
   Removes unneeded parentheses around control statements.

+ 112 - 0
src/Fixer/StringNotation/NoTrailingWhitespaceInStringFixer.php

@@ -0,0 +1,112 @@
+<?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\StringNotation;
+
+use PhpCsFixer\AbstractFixer;
+use PhpCsFixer\FixerDefinition\CodeSample;
+use PhpCsFixer\FixerDefinition\FixerDefinition;
+use PhpCsFixer\Preg;
+use PhpCsFixer\Tokenizer\Token;
+use PhpCsFixer\Tokenizer\Tokens;
+
+/**
+ * @author Gregor Harlan
+ */
+final class NoTrailingWhitespaceInStringFixer extends AbstractFixer
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function isCandidate(Tokens $tokens)
+    {
+        return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isRisky()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDefinition()
+    {
+        return new FixerDefinition(
+            'There must be no trailing whitespace in strings.',
+            [
+                new CodeSample(
+                    "<?php \$a = '  \n    foo \n';\n"
+                ),
+            ],
+            null,
+            'Changing the whitespaces in strings might affect string comparisons and outputs.'
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
+    {
+        for ($index = $tokens->count() - 1, $last = true; $index >= 0; --$index, $last = false) {
+            $token = $tokens[$index];
+
+            if (!$token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML])) {
+                continue;
+            }
+
+            $isInlineHtml = $token->isGivenKind(T_INLINE_HTML);
+            $regex = $isInlineHtml && $last ? '/\h+(?=\R|$)/' : '/\h+(?=\R)/';
+            $content = Preg::replace($regex, '', $token->getContent());
+
+            if ($token->getContent() === $content) {
+                continue;
+            }
+
+            if (!$isInlineHtml || 0 === $index) {
+                $this->updateContent($tokens, $index, $content);
+
+                continue;
+            }
+
+            $prev = $index - 1;
+
+            if ($tokens[$prev]->equals([T_CLOSE_TAG, '?>']) && Preg::match('/^\R/', $content, $match)) {
+                $tokens[$prev] = new Token([T_CLOSE_TAG, $tokens[$prev]->getContent().$match[0]]);
+                $content = substr($content, \strlen($match[0]));
+                $content = false === $content ? '' : $content;
+            }
+
+            $this->updateContent($tokens, $index, $content);
+        }
+    }
+
+    /**
+     * @param int    $index
+     * @param string $content
+     */
+    private function updateContent(Tokens $tokens, $index, $content)
+    {
+        if ('' === $content) {
+            $tokens->clearAt($index);
+
+            return;
+        }
+
+        $tokens[$index] = new Token([$tokens[$index]->getId(), $content]);
+    }
+}

+ 1 - 0
src/RuleSet.php

@@ -322,6 +322,7 @@ final class RuleSet implements RuleSetInterface
             '@Symfony:risky' => true,
             'comment_to_phpdoc' => true,
             'final_internal_class' => true,
+            'no_trailing_whitespace_in_string' => true,
             'no_unreachable_default_argument_value' => true,
             'no_unset_on_property' => true,
             'php_unit_strict' => true,

+ 153 - 0
tests/Fixer/StringNotation/NoTrailingWhitespaceInStringFixerTest.php

@@ -0,0 +1,153 @@
+<?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\Tests\Fixer\StringNotation;
+
+use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
+
+/**
+ * @author Gregor Harlan
+ *
+ * @internal
+ *
+ * @covers \PhpCsFixer\Fixer\StringNotation\NoTrailingWhitespaceInStringFixer
+ */
+final class NoTrailingWhitespaceInStringFixerTest extends AbstractFixerTestCase
+{
+    /**
+     * @param string      $expected
+     * @param null|string $input
+     *
+     * @dataProvider provideFixCases
+     */
+    public function testFix($expected, $input = null)
+    {
+        $this->doTest($expected, $input);
+    }
+
+    public function provideFixCases()
+    {
+        return [
+            [
+                "<?php \$a = ' foo\r bar\r\n\nbaz\n  ';",
+                "<?php \$a = ' foo  \r bar \r\n  \nbaz  \n  ';",
+            ],
+            [
+                "<?php \$a = \" foo\r bar\r\n\nbaz\n  \";",
+                "<?php \$a = \" foo  \r bar \r\n  \nbaz  \n  \";",
+            ],
+            [
+                "<?php \$a = \"  \$foo\n\";",
+                "<?php \$a = \"  \$foo  \n\";",
+            ],
+            [
+                " foo\r bar\r\nbaz\n",
+                " foo  \r bar \r\nbaz  \n  ",
+            ],
+            [
+                "\n<?php foo() ?>\n  foo",
+                "  \n<?php foo() ?>  \n  foo",
+            ],
+            [
+                "<?php foo() ?>\n<?php foo() ?>\n",
+                "<?php foo() ?>  \n<?php foo() ?>  \n",
+            ],
+            [
+                "<?php foo() ?>\n\nfoo",
+                "<?php foo() ?>\n  \nfoo",
+            ],
+            [
+                "<?php foo() ?>foo\n",
+                "<?php foo() ?>foo  \n",
+            ],
+            [
+                '',
+                ' ',
+            ],
+            [
+                '<?php echo 1; ?>',
+                '<?php echo 1; ?> ',
+            ],
+            [
+                '
+<?php
+$a = <<<EOD
+  foo
+bar
+  $a
+$b
+
+   baz
+EOD;
+                ',
+                '
+<?php
+$a = <<<EOD
+  foo  '.'
+bar
+  $a '.'
+$b
+    '.'
+   baz  '.'
+EOD;
+                ',
+            ],
+            [
+                '
+<?php
+$a = <<<\'EOD\'
+  foo
+bar
+
+   baz
+EOD;
+                ',
+                '
+<?php
+$a = <<<\'EOD\'
+  foo  '.'
+bar
+    '.'
+   baz  '.'
+EOD;
+                ',
+            ],
+        ];
+    }
+
+    /**
+     * @requires PHP 7.3
+     */
+    public function testFix73()
+    {
+        $expected = '
+<?php
+    $a = <<<\'EOD\'
+      foo
+    bar
+
+       baz
+    EOD;
+';
+        $input = '
+<?php
+    $a = <<<\'EOD\'
+      foo  '.'
+    bar
+        '.'
+       baz  '.'
+    EOD;
+';
+
+        $this->doTest($expected, $input);
+    }
+}