From cff91c0457f14397ba361041b72831bcff4b6599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Thu, 18 Jan 2024 19:53:23 +0100 Subject: [PATCH] fix: Support shebang in fixers operating on PHP opening tag (#7687) --- dev-tools/doc.php | 2 + dev-tools/info-extractor.php | 2 + src/Fixer/Comment/HeaderCommentFixer.php | 8 +-- .../PhpTag/BlankLineAfterOpeningTagFixer.php | 25 ++++----- .../PhpTag/LinebreakAfterOpeningTagFixer.php | 13 ++--- src/Fixer/PhpTag/NoClosingTagFixer.php | 6 +- src/Fixer/Strict/DeclareStrictTypesFixer.php | 42 ++++++-------- src/Tokenizer/Tokens.php | 13 +---- .../BlankLineAfterOpeningTagFixerTest.php | 24 +++++++- .../LinebreakAfterOpeningTagFixerTest.php | 14 +++++ .../Strict/DeclareStrictTypesFixerTest.php | 56 ++++++++++++++++++- 11 files changed, 132 insertions(+), 73 deletions(-) diff --git a/dev-tools/doc.php b/dev-tools/doc.php index e0a1f2a7c5c..2197a059d71 100755 --- a/dev-tools/doc.php +++ b/dev-tools/doc.php @@ -1,6 +1,8 @@ #!/usr/bin/env php isMonolithicPhp(); + return $tokens->isMonolithicPhp() && !$tokens->isTokenKindFound(T_OPEN_TAG_WITH_ECHO); } /** @@ -266,11 +266,7 @@ private function findHeaderCommentCurrentIndex(Tokens $tokens, int $headerNewInd */ 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; diff --git a/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php b/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php index 8c8dbad7375..d4af8196ec7 100644 --- a/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php +++ b/src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php @@ -48,23 +48,16 @@ public function getPriority(): int 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 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 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])); } } } diff --git a/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php b/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php index 5be68fab6bd..2a8271613b7 100644 --- a/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php +++ b/src/Fixer/PhpTag/LinebreakAfterOpeningTagFixer.php @@ -37,24 +37,21 @@ public function getDefinition(): FixerDefinitionInterface 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 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void 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()]); } } diff --git a/src/Fixer/PhpTag/NoClosingTagFixer.php b/src/Fixer/PhpTag/NoClosingTagFixer.php index 8921950afdd..14c40c037f1 100644 --- a/src/Fixer/PhpTag/NoClosingTagFixer.php +++ b/src/Fixer/PhpTag/NoClosingTagFixer.php @@ -38,15 +38,11 @@ public function getDefinition(): FixerDefinitionInterface 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); diff --git a/src/Fixer/Strict/DeclareStrictTypesFixer.php b/src/Fixer/Strict/DeclareStrictTypesFixer.php index 4a83a9def78..7e8172c24f9 100644 --- a/src/Fixer/Strict/DeclareStrictTypesFixer.php +++ b/src/Fixer/Strict/DeclareStrictTypesFixer.php @@ -53,7 +53,7 @@ public function getPriority(): int 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 @@ public function isRisky(): bool 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 @@ private function fixStrictTypesCasingAndValue(Tokens $tokens, array $sequence): } } - private function insertSequence(Tokens $tokens): void + private function insertSequence(int $openTagIndex, Tokens $tokens): void { $sequence = [ new Token([T_DECLARE, 'declare']), @@ -113,28 +107,26 @@ private function insertSequence(Tokens $tokens): void 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 "getContent(), "\n")) { - $tokens[0] = new Token([$tokens[0]->getId(), trim($tokens[0]->getContent()).' ']); + // transform "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")]); } } diff --git a/src/Tokenizer/Tokens.php b/src/Tokenizer/Tokens.php index 98e414cfce6..de44ebe5a3a 100644 --- a/src/Tokenizer/Tokens.php +++ b/src/Tokenizer/Tokens.php @@ -1138,19 +1138,12 @@ public function clearRange(int $indexStart, int $indexEnd): void */ 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())); } /** diff --git a/tests/Fixer/PhpTag/BlankLineAfterOpeningTagFixerTest.php b/tests/Fixer/PhpTag/BlankLineAfterOpeningTagFixerTest.php index a23ab62cae0..438b12ac78a 100644 --- a/tests/Fixer/PhpTag/BlankLineAfterOpeningTagFixerTest.php +++ b/tests/Fixer/PhpTag/BlankLineAfterOpeningTagFixerTest.php @@ -40,10 +40,8 @@ public static function provideFixCases(): iterable yield [ '', ]; + + yield 'empty file with open tag without new line' => [ + ' [ + " [ + <<<'EOD' + #!x + [ + <<<'EOD' + #!x + Test', + yield 'monolithic file with closing tag' => [ + '', + '', + ]; + + yield 'monolithic file with closing tag and extra new line' => [ + ''."\n", + ''."\n", + ]; + + yield 'monolithic file with closing tag and extra content' => [ 'Test', ]; @@ -133,6 +142,49 @@ class A { yield [' [ + '', + ]; + + yield 'empty file /w open tag' => [ + ' [ + 'x', + ]; + + yield 'non-empty file /w open tag' => [ + 'x [ + <<<'EOD' + #!x + [ + <<<'EOD' + #!x + y + EOD, + ]; + + yield 'file with shebang not followed by open tag' => [ + <<<'EOD' + #!x + #!not_a_shebang +