Browse Source

fix: Support shebang in fixers operating on PHP opening tag (#7687)

Michael Voříšek 1 year ago
parent
commit
cff91c0457

+ 2 - 0
dev-tools/doc.php

@@ -1,6 +1,8 @@
 #!/usr/bin/env php
 <?php
 
+declare(strict_types=1);
+
 /*
  * This file is part of PHP CS Fixer.
  *

+ 2 - 0
dev-tools/info-extractor.php

@@ -1,6 +1,8 @@
 #!/usr/bin/env php
 <?php
 
+declare(strict_types=1);
+
 /*
  * This file is part of PHP CS Fixer.
  *

+ 2 - 6
src/Fixer/Comment/HeaderCommentFixer.php

@@ -112,7 +112,7 @@ echo 1;
 
     public function isCandidate(Tokens $tokens): bool
     {
-        return $tokens->isMonolithicPhp();
+        return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO);
     }
 
     /**
@@ -266,11 +266,7 @@ echo 1;
      */
     private function findHeaderCommentInsertionIndex(Tokens $tokens, string $location): int
     {
-        $openTagIndex = $tokens[0]->isGivenKind(T_OPEN_TAG) ? 0 : $tokens->getNextTokenOfKind(0, [[T_OPEN_TAG]]);
-
-        if (null === $openTagIndex) {
-            return 1;
-        }
+        $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0;
 
         if ('after_open' === $location) {
             return $openTagIndex + 1;

+ 10 - 15
src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php

@@ -48,23 +48,16 @@ final class BlankLineAfterOpeningTagFixer extends AbstractFixer implements White
 
     public function isCandidate(Tokens $tokens): bool
     {
-        return $tokens->isTokenKindFound(T_OPEN_TAG);
+        return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO);
     }
 
     protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
     {
         $lineEnding = $this->whitespacesConfig->getLineEnding();
 
-        // ignore files with short open tag and ignore non-monolithic files
-        if (!$tokens[0]->isGivenKind(T_OPEN_TAG) || !$tokens->isMonolithicPhp()) {
-            return;
-        }
-
         $newlineFound = false;
-
-        /** @var Token $token */
         foreach ($tokens as $token) {
-            if ($token->isWhitespace() && str_contains($token->getContent(), "\n")) {
+            if (($token->isWhitespace() || $token->isGivenKind(T_OPEN_TAG)) && str_contains($token->getContent(), "\n")) {
                 $newlineFound = true;
 
                 break;
@@ -76,17 +69,19 @@ final class BlankLineAfterOpeningTagFixer extends AbstractFixer implements White
             return;
         }
 
-        $token = $tokens[0];
+        $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0;
+        $token = $tokens[$openTagIndex];
 
         if (!str_contains($token->getContent(), "\n")) {
-            $tokens[0] = new Token([$token->getId(), rtrim($token->getContent()).$lineEnding]);
+            $tokens[$openTagIndex] = new Token([$token->getId(), rtrim($token->getContent()).$lineEnding]);
         }
 
-        if (!str_contains($tokens[1]->getContent(), "\n")) {
-            if ($tokens[1]->isWhitespace()) {
-                $tokens[1] = new Token([T_WHITESPACE, $lineEnding.$tokens[1]->getContent()]);
+        $newLineIndex = $openTagIndex + 1;
+        if (isset($tokens[$newLineIndex]) && !str_contains($tokens[$newLineIndex]->getContent(), "\n")) {
+            if ($tokens[$newLineIndex]->isWhitespace()) {
+                $tokens[$newLineIndex] = new Token([T_WHITESPACE, $lineEnding.$tokens[$newLineIndex]->getContent()]);
             } else {
-                $tokens->insertAt(1, new Token([T_WHITESPACE, $lineEnding]));
+                $tokens->insertAt($newLineIndex, new Token([T_WHITESPACE, $lineEnding]));
             }
         }
     }

+ 5 - 8
src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php

@@ -37,24 +37,21 @@ final class LinebreakAfterOpeningTagFixer extends AbstractFixer implements White
 
     public function isCandidate(Tokens $tokens): bool
     {
-        return $tokens->isTokenKindFound(T_OPEN_TAG);
+        return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO);
     }
 
     protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
     {
-        // ignore files with short open tag and ignore non-monolithic files
-        if (!$tokens[0]->isGivenKind(T_OPEN_TAG) || !$tokens->isMonolithicPhp()) {
-            return;
-        }
+        $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0;
 
         // ignore if linebreak already present
-        if (str_contains($tokens[0]->getContent(), "\n")) {
+        if (str_contains($tokens[$openTagIndex]->getContent(), "\n")) {
             return;
         }
 
         $newlineFound = false;
         foreach ($tokens as $token) {
-            if ($token->isWhitespace() && str_contains($token->getContent(), "\n")) {
+            if (($token->isWhitespace() || $token->isGivenKind(T_OPEN_TAG)) && str_contains($token->getContent(), "\n")) {
                 $newlineFound = true;
 
                 break;
@@ -66,6 +63,6 @@ final class LinebreakAfterOpeningTagFixer extends AbstractFixer implements White
             return;
         }
 
-        $tokens[0] = new Token([T_OPEN_TAG, rtrim($tokens[0]->getContent()).$this->whitespacesConfig->getLineEnding()]);
+        $tokens[$openTagIndex] = new Token([T_OPEN_TAG, rtrim($tokens[$openTagIndex]->getContent()).$this->whitespacesConfig->getLineEnding()]);
     }
 }

+ 1 - 5
src/Fixer/PhpTag/NoClosingTagFixer.php

@@ -38,15 +38,11 @@ final class NoClosingTagFixer extends AbstractFixer
 
     public function isCandidate(Tokens $tokens): bool
     {
-        return $tokens->isTokenKindFound(T_CLOSE_TAG);
+        return \count($tokens) >= 2 && $tokens->isMonolithicPhp() && $tokens->isTokenKindFound(T_CLOSE_TAG);
     }
 
     protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
     {
-        if (\count($tokens) < 2 || !$tokens->isMonolithicPhp() || !$tokens->isTokenKindFound(T_CLOSE_TAG)) {
-            return;
-        }
-
         $closeTags = $tokens->findGivenKind(T_CLOSE_TAG);
         $index = array_key_first($closeTags);
 

+ 17 - 25
src/Fixer/Strict/DeclareStrictTypesFixer.php

@@ -53,7 +53,7 @@ final class DeclareStrictTypesFixer extends AbstractFixer implements Whitespaces
 
     public function isCandidate(Tokens $tokens): bool
     {
-        return isset($tokens[0]) && $tokens[0]->isGivenKind(T_OPEN_TAG);
+        return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO);
     }
 
     public function isRisky(): bool
@@ -63,17 +63,11 @@ final class DeclareStrictTypesFixer extends AbstractFixer implements Whitespaces
 
     protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
     {
-        // check if the declaration is already done
-        $searchIndex = $tokens->getNextMeaningfulToken(0);
-        if (null === $searchIndex) {
-            $this->insertSequence($tokens); // declaration not found, insert one
+        $openTagIndex = $tokens[0]->isGivenKind(T_INLINE_HTML) ? 1 : 0;
 
-            return;
-        }
-
-        $sequenceLocation = $tokens->findSequence([[T_DECLARE, 'declare'], '(', [T_STRING, 'strict_types'], '=', [T_LNUMBER], ')'], $searchIndex, null, false);
+        $sequenceLocation = $tokens->findSequence([[T_DECLARE, 'declare'], '(', [T_STRING, 'strict_types'], '=', [T_LNUMBER], ')'], $openTagIndex, null, false);
         if (null === $sequenceLocation) {
-            $this->insertSequence($tokens); // declaration not found, insert one
+            $this->insertSequence($openTagIndex, $tokens); // declaration not found, insert one
 
             return;
         }
@@ -102,7 +96,7 @@ final class DeclareStrictTypesFixer extends AbstractFixer implements Whitespaces
         }
     }
 
-    private function insertSequence(Tokens $tokens): void
+    private function insertSequence(int $openTagIndex, Tokens $tokens): void
     {
         $sequence = [
             new Token([T_DECLARE, 'declare']),
@@ -113,28 +107,26 @@ final class DeclareStrictTypesFixer extends AbstractFixer implements Whitespaces
             new Token(')'),
             new Token(';'),
         ];
-        $endIndex = \count($sequence);
+        $nextIndex = $openTagIndex + \count($sequence) + 1;
 
-        $tokens->insertAt(1, $sequence);
+        $tokens->insertAt($openTagIndex + 1, $sequence);
 
-        // start index of the sequence is always 1 here, 0 is always open tag
-        // transform "<?php\n" to "<?php " if needed
-        if (str_contains($tokens[0]->getContent(), "\n")) {
-            $tokens[0] = new Token([$tokens[0]->getId(), trim($tokens[0]->getContent()).' ']);
+        // transform "<?php" or "<?php\n" to "<?php " if needed
+        $content = $tokens[$openTagIndex]->getContent();
+        if (!str_contains($content, ' ') || str_contains($content, "\n")) {
+            $tokens[$openTagIndex] = new Token([$tokens[$openTagIndex]->getId(), trim($tokens[$openTagIndex]->getContent()).' ']);
         }
 
-        if ($endIndex === \count($tokens) - 1) {
+        if (\count($tokens) === $nextIndex) {
             return; // no more tokens after sequence, single_blank_line_at_eof might add a line
         }
 
         $lineEnding = $this->whitespacesConfig->getLineEnding();
-        if (!$tokens[1 + $endIndex]->isWhitespace()) {
-            $tokens->insertAt(1 + $endIndex, new Token([T_WHITESPACE, $lineEnding]));
-
-            return;
+        if ($tokens[$nextIndex]->isWhitespace()) {
+            $content = $tokens[$nextIndex]->getContent();
+            $tokens[$nextIndex] = new Token([T_WHITESPACE, $lineEnding.ltrim($content, " \t")]);
+        } else {
+            $tokens->insertAt($nextIndex, new Token([T_WHITESPACE, $lineEnding]));
         }
-
-        $content = $tokens[1 + $endIndex]->getContent();
-        $tokens[1 + $endIndex] = new Token([T_WHITESPACE, $lineEnding.ltrim($content, " \t")]);
     }
 }

+ 3 - 10
src/Tokenizer/Tokens.php

@@ -1138,19 +1138,12 @@ class Tokens extends \SplFixedArray
      */
     public function isMonolithicPhp(): bool
     {
-        if (0 === $this->count()) {
+        if (1 !== ($this->countTokenKind(T_OPEN_TAG) + $this->countTokenKind(T_OPEN_TAG_WITH_ECHO))) {
             return false;
         }
 
-        if ($this->countTokenKind(T_INLINE_HTML) > 1) {
-            return false;
-        }
-
-        if (1 === $this->countTokenKind(T_INLINE_HTML)) {
-            return Preg::match('/^#!.+$/', $this[0]->getContent());
-        }
-
-        return 1 === ($this->countTokenKind(T_OPEN_TAG) + $this->countTokenKind(T_OPEN_TAG_WITH_ECHO));
+        return 0 === $this->countTokenKind(T_INLINE_HTML)
+            || (1 === $this->countTokenKind(T_INLINE_HTML) && Preg::match('/^#!.+$/', $this[0]->getContent()));
     }
 
     /**

+ 22 - 2
tests/Fixer/PhpTag/BlankLineAfterOpeningTagFixerTest.php

@@ -40,10 +40,8 @@ final class BlankLineAfterOpeningTagFixerTest extends AbstractFixerTestCase
         yield [
             '<?php
 
-    $a = 0;
     echo 1;',
             '<?php
-    $a = 0;
     echo 1;',
         ];
 
@@ -138,6 +136,28 @@ Html here
 $foo = $bar;
 ?>',
         ];
+
+        yield 'empty file with open tag without new line' => [
+            '<?php',
+        ];
+
+        yield 'empty file with open tag with new line' => [
+            "<?php\n",
+        ];
+
+        yield 'file with shebang' => [
+            <<<'EOD'
+                #!x
+                <?php
+
+                echo 1;
+                EOD,
+            <<<'EOD'
+                #!x
+                <?php
+                echo 1;
+                EOD,
+        ];
     }
 
     /**

+ 14 - 0
tests/Fixer/PhpTag/LinebreakAfterOpeningTagFixerTest.php

@@ -91,6 +91,20 @@ $foo = $bar;
 // linebreak already present in file with Windows line endings
 '),
         ];
+
+        yield 'file with shebang' => [
+            <<<'EOD'
+                #!x
+                <?php
+                echo 1;
+                echo 2;
+                EOD,
+            <<<'EOD'
+                #!x
+                <?php echo 1;
+                echo 2;
+                EOD,
+        ];
     }
 
     /**

Some files were not shown because too many files changed in this diff