Browse Source

RangeAnalyzer - introduction

SpacePossum 3 years ago
parent
commit
7eb14461cb

+ 2 - 38
src/Fixer/Operator/AssignNullCoalescingToCoalesceEqualFixer.php

@@ -19,6 +19,7 @@ use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
 use PhpCsFixer\FixerDefinition\VersionSpecification;
 use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
+use PhpCsFixer\Tokenizer\Analyzer\RangeAnalyzer;
 use PhpCsFixer\Tokenizer\CT;
 use PhpCsFixer\Tokenizer\Token;
 use PhpCsFixer\Tokenizer\Tokens;
@@ -102,7 +103,7 @@ final class AssignNullCoalescingToCoalesceEqualFixer extends AbstractFixer
 
             // make sure before and after are the same
 
-            if (!$this->rangeEqualsRange($tokens, $assignRange, $beforeRange)) {
+            if (!RangeAnalyzer::rangeEqualsRange($tokens, $assignRange, $beforeRange)) {
                 continue;
             }
 
@@ -174,43 +175,6 @@ final class AssignNullCoalescingToCoalesceEqualFixer extends AbstractFixer
         return $range;
     }
 
-    /**
-     * Meaningful compare of tokens within ranges.
-     */
-    private function rangeEqualsRange(Tokens $tokens, array $range1, array $range2): bool
-    {
-        $leftStart = $range1['start'];
-        $leftEnd = $range1['end'];
-
-        while ($tokens[$leftStart]->equals('(') && $tokens[$leftEnd]->equals(')')) {
-            $leftStart = $tokens->getNextMeaningfulToken($leftStart);
-            $leftEnd = $tokens->getPrevMeaningfulToken($leftEnd);
-        }
-
-        $rightStart = $range2['start'];
-        $rightEnd = $range2['end'];
-
-        while ($tokens[$rightStart]->equals('(') && $tokens[$rightEnd]->equals(')')) {
-            $rightStart = $tokens->getNextMeaningfulToken($rightStart);
-            $rightEnd = $tokens->getPrevMeaningfulToken($rightEnd);
-        }
-
-        while ($leftStart <= $leftEnd && $rightStart <= $rightEnd) {
-            if (
-                !$tokens[$leftStart]->equals($tokens[$rightStart])
-                && !($tokens[$leftStart]->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]]) && $tokens[$rightStart]->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]]))
-                && !($tokens[$leftStart]->equalsAny([']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]]) && $tokens[$rightStart]->equalsAny([']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]]))
-            ) {
-                return false;
-            }
-
-            $leftStart = $tokens->getNextMeaningfulToken($leftStart);
-            $rightStart = $tokens->getNextMeaningfulToken($rightStart);
-        }
-
-        return $leftStart > $leftEnd && $rightStart > $rightEnd;
-    }
-
     private function clearMeaningfulFromRange(Tokens $tokens, array $range): void
     {
         // $range['end'] must be meaningful!

+ 2 - 38
src/Fixer/Operator/TernaryToElvisOperatorFixer.php

@@ -18,6 +18,7 @@ use PhpCsFixer\AbstractFixer;
 use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
+use PhpCsFixer\Tokenizer\Analyzer\RangeAnalyzer;
 use PhpCsFixer\Tokenizer\CT;
 use PhpCsFixer\Tokenizer\Tokens;
 
@@ -133,7 +134,7 @@ final class TernaryToElvisOperatorFixer extends AbstractFixer
 
             // if before and after the `?` operator are the same (in meaningful matter), clear after
 
-            if ($this->rangeEqualsRange($tokens, $beforeOperator, $afterOperator)) {
+            if (RangeAnalyzer::rangeEqualsRange($tokens, $beforeOperator, $afterOperator)) {
                 $this->clearMeaningfulFromRange($tokens, $afterOperator);
             }
         }
@@ -212,43 +213,6 @@ final class TernaryToElvisOperatorFixer extends AbstractFixer
         return $after;
     }
 
-    /**
-     * Meaningful compare of tokens within ranges.
-     */
-    private function rangeEqualsRange(Tokens $tokens, array $range1, array $range2): bool
-    {
-        $leftStart = $range1['start'];
-        $leftEnd = $range1['end'];
-
-        while ($tokens[$leftStart]->equals('(') && $tokens[$leftEnd]->equals(')')) {
-            $leftStart = $tokens->getNextMeaningfulToken($leftStart);
-            $leftEnd = $tokens->getPrevMeaningfulToken($leftEnd);
-        }
-
-        $rightStart = $range2['start'];
-        $rightEnd = $range2['end'];
-
-        while ($tokens[$rightStart]->equals('(') && $tokens[$rightEnd]->equals(')')) {
-            $rightStart = $tokens->getNextMeaningfulToken($rightStart);
-            $rightEnd = $tokens->getPrevMeaningfulToken($rightEnd);
-        }
-
-        while ($leftStart <= $leftEnd && $rightStart <= $rightEnd) {
-            if (
-                !$tokens[$leftStart]->equals($tokens[$rightStart])
-                && !($tokens[$leftStart]->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]]) && $tokens[$rightStart]->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]]))
-                && !($tokens[$leftStart]->equalsAny([']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]]) && $tokens[$rightStart]->equalsAny([']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]]))
-            ) {
-                return false;
-            }
-
-            $leftStart = $tokens->getNextMeaningfulToken($leftStart);
-            $rightStart = $tokens->getNextMeaningfulToken($rightStart);
-        }
-
-        return $leftStart > $leftEnd && $rightStart > $rightEnd;
-    }
-
     private function clearMeaningfulFromRange(Tokens $tokens, array $range): void
     {
         // $range['end'] must be meaningful!

+ 87 - 0
src/Tokenizer/Analyzer/RangeAnalyzer.php

@@ -0,0 +1,87 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * 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\Tokenizer\Analyzer;
+
+use PhpCsFixer\Tokenizer\CT;
+use PhpCsFixer\Tokenizer\Tokens;
+
+/**
+ * @internal
+ */
+final class RangeAnalyzer
+{
+    private function __construct()
+    {
+        // cannot create instance of util. class
+    }
+
+    /**
+     * Meaningful compare of tokens within ranges.
+     */
+    public static function rangeEqualsRange(Tokens $tokens, array $range1, array $range2): bool
+    {
+        $leftStart = $range1['start'];
+        $leftEnd = $range1['end'];
+
+        if ($tokens[$leftStart]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
+            $leftStart = $tokens->getNextMeaningfulToken($leftStart);
+        }
+
+        while ($tokens[$leftStart]->equals('(') && $tokens[$leftEnd]->equals(')')) {
+            $leftStart = $tokens->getNextMeaningfulToken($leftStart);
+            $leftEnd = $tokens->getPrevMeaningfulToken($leftEnd);
+        }
+
+        $rightStart = $range2['start'];
+        $rightEnd = $range2['end'];
+
+        if ($tokens[$rightStart]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
+            $rightStart = $tokens->getNextMeaningfulToken($rightStart);
+        }
+
+        while ($tokens[$rightStart]->equals('(') && $tokens[$rightEnd]->equals(')')) {
+            $rightStart = $tokens->getNextMeaningfulToken($rightStart);
+            $rightEnd = $tokens->getPrevMeaningfulToken($rightEnd);
+        }
+
+        $arrayOpenTypes = ['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]];
+        $arrayCloseTypes = [']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]];
+
+        while (true) {
+            $leftToken = $tokens[$leftStart];
+            $rightToken = $tokens[$rightStart];
+
+            if (
+                !$leftToken->equals($rightToken)
+                && !($leftToken->equalsAny($arrayOpenTypes) && $rightToken->equalsAny($arrayOpenTypes))
+                && !($leftToken->equalsAny($arrayCloseTypes) && $rightToken->equalsAny($arrayCloseTypes))
+            ) {
+                return false;
+            }
+
+            $leftStart = $tokens->getNextMeaningfulToken($leftStart);
+            $rightStart = $tokens->getNextMeaningfulToken($rightStart);
+
+            $reachedLeftEnd = null === $leftStart || $leftStart > $leftEnd; // reached end left or moved over
+            $reachedRightEnd = null === $rightStart || $rightStart > $rightEnd; // reached end right or moved over
+
+            if (!$reachedLeftEnd && !$reachedRightEnd) {
+                continue;
+            }
+
+            return $reachedLeftEnd && $reachedRightEnd;
+        }
+    }
+}

+ 105 - 0
tests/Tokenizer/Analyzer/RangeAnalyzerTest.php

@@ -0,0 +1,105 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * 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\Tokenizer\Analyzer;
+
+use PhpCsFixer\Tests\TestCase;
+use PhpCsFixer\Tokenizer\Analyzer\RangeAnalyzer;
+use PhpCsFixer\Tokenizer\Tokens;
+
+/**
+ * @internal
+ *
+ * @covers \PhpCsFixer\Tokenizer\Analyzer\RangeAnalyzer
+ */
+final class RangeAnalyzerTest extends TestCase
+{
+    /**
+     * @dataProvider provideRangeEqualsRangeCases
+     */
+    public function testRangeEqualsRange(bool $expected, string $code, array $range1, array $range2): void
+    {
+        $tokens = Tokens::fromCode($code);
+
+        static::assertSame($expected, RangeAnalyzer::rangeEqualsRange($tokens, $range1, $range2));
+    }
+
+    public function provideRangeEqualsRangeCases(): \Generator
+    {
+        $ranges = [
+            [['start' => 2, 'end' => 6], ['start' => 10, 'end' => 14]],
+            [['start' => 10, 'end' => 14], ['start' => 20, 'end' => 24]],
+            [['start' => 1, 'end' => 6], ['start' => 20, 'end' => 25]],
+        ];
+
+        foreach ($ranges as $i => [$range1, $range2]) {
+            yield 'extra "()" and space #'.$i => [
+                true,
+                '<?php
+                    $a = 1;
+                    ($a = 1);
+                    (($a = 1 ));
+                ',
+                $range1,
+                $range2,
+            ];
+        }
+
+        yield [
+            false,
+            '<?php echo 1;',
+            ['start' => 0, 'end' => 1],
+            ['start' => 1, 'end' => 2],
+        ];
+
+        yield 'comment + space' => [
+            true,
+            '<?php
+                foo(1);
+                /* */ foo/* */(1) /* */ ;
+            ',
+            ['start' => 1, 'end' => 5],
+            ['start' => 9, 'end' => 17],
+        ];
+    }
+
+    /**
+     * @requires PHP <8.0
+     */
+    public function testFixPrePHP80(): void
+    {
+        $code = '<?php
+            $a = [1,2,3];
+            echo $a[1];
+            echo $a{1};
+        ';
+
+        $tokens = Tokens::fromCode($code);
+
+        $ranges = [
+            [
+                ['start' => 15, 'end' => 21],
+                ['start' => 23, 'end' => 29],
+            ],
+            [
+                ['start' => 17, 'end' => 20],
+                ['start' => 24, 'end' => 28],
+            ],
+        ];
+
+        foreach ($ranges as [$range1, $range2]) {
+            static::assertTrue(RangeAnalyzer::rangeEqualsRange($tokens, $range1, $range2));
+        }
+    }
+}