From 4dc89370ce127940883bc3e9cbc456f250fd6ea9 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 11 Nov 2025 21:49:43 +0100 Subject: [PATCH] Generic/UnnecessaryHeredoc: bug fix - fixer changes indentation type In a tab indented file with `--tab-width` set, the fixer for the `Generic.Strings.UnnecessaryHeredoc` sniff would replace tabs with spaces in lines in the heredoc body, which were being replaced to update the escaping of special characters. For heredocs using PHP 7.3+ flexible heredoc syntax with tab indentation, that also meant that the fixer would end up causing a parse error in the updated file (see: https://3v4l.org/vpD1u#veol ) Fixed now. Includes extensively expanded tests to safeguard this fix for all variations of heredoc syntax vs indentation whitespace which I could think of. Fixes 1320 --- .../Strings/UnnecessaryHeredocSniff.php | 10 +- .../Strings/UnnecessaryHeredocUnitTest.1.inc | 10 +- .../UnnecessaryHeredocUnitTest.1.inc.fixed | 10 +- .../Strings/UnnecessaryHeredocUnitTest.2.inc | 10 +- .../UnnecessaryHeredocUnitTest.2.inc.fixed | 10 +- .../Strings/UnnecessaryHeredocUnitTest.4.inc | 126 ++++++++++++++++++ .../UnnecessaryHeredocUnitTest.4.inc.fixed | 126 ++++++++++++++++++ .../Strings/UnnecessaryHeredocUnitTest.5.inc | 126 ++++++++++++++++++ .../UnnecessaryHeredocUnitTest.5.inc.fixed | 126 ++++++++++++++++++ .../Strings/UnnecessaryHeredocUnitTest.6.inc | 126 ++++++++++++++++++ .../UnnecessaryHeredocUnitTest.6.inc.fixed | 126 ++++++++++++++++++ .../Strings/UnnecessaryHeredocUnitTest.php | 25 +++- 12 files changed, 815 insertions(+), 16 deletions(-) create mode 100644 src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.4.inc create mode 100644 src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.4.inc.fixed create mode 100644 src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.5.inc create mode 100644 src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.5.inc.fixed create mode 100644 src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.6.inc create mode 100644 src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.6.inc.fixed diff --git a/src/Standards/Generic/Sniffs/Strings/UnnecessaryHeredocSniff.php b/src/Standards/Generic/Sniffs/Strings/UnnecessaryHeredocSniff.php index 226215c96e..0ffa197c2e 100644 --- a/src/Standards/Generic/Sniffs/Strings/UnnecessaryHeredocSniff.php +++ b/src/Standards/Generic/Sniffs/Strings/UnnecessaryHeredocSniff.php @@ -128,9 +128,13 @@ public function process(File $phpcsFile, int $stackPtr) $phpcsFile->fixer->replaceToken($stackPtr, $replacement); for ($i = ($stackPtr + 1); $i < $closer; $i++) { - $content = $tokens[$i]['content']; - $content = str_replace(['\\$', '\\\\'], ['$', '\\'], $content); - if ($tokens[$i]['content'] !== $content) { + $origContent = $tokens[$i]['content']; + if (isset($tokens[$i]['orig_content']) === true) { + $origContent = $tokens[$i]['orig_content']; + } + + $content = str_replace(['\\$', '\\\\'], ['$', '\\'], $origContent); + if ($origContent !== $content) { $phpcsFile->fixer->replaceToken($i, $content); } } diff --git a/src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.1.inc b/src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.1.inc index 428b4b8a40..efd30df5c2 100644 --- a/src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.1.inc +++ b/src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.1.inc @@ -1,10 +1,14 @@ bar} +END; + +$heredoc = <<< "END" + some ${beers::softdrink} +END; + +$heredoc = <<< END + {${$object->getName()}} text +END; + +$heredoc = <<<"END" + some {${getName()}} +END; + +$heredoc = <<baz()()} +END; + +$heredoc = <<values[3]->name} text +END; + +$heredoc = <<<"END" + some ${$bar} +END; + +$heredoc = <<bar} text +END; + +$heredoc = <<<"END" + ${foo["${bar}"]} text +END; + +$heredoc = <<{${'a'}}} text +END; + +$heredoc = <<{$baz[1]}} +END; + +$heredoc = <<john's wife greeted $people->robert. +END; + +$heredoc = <<bar} +END; + +$heredoc = <<< "END" + some ${beers::softdrink} +END; + +$heredoc = <<< END + {${$object->getName()}} text +END; + +$heredoc = <<<"END" + some {${getName()}} +END; + +$heredoc = <<baz()()} +END; + +$heredoc = <<values[3]->name} text +END; + +$heredoc = <<<"END" + some ${$bar} +END; + +$heredoc = <<bar} text +END; + +$heredoc = <<<"END" + ${foo["${bar}"]} text +END; + +$heredoc = <<{${'a'}}} text +END; + +$heredoc = <<{$baz[1]}} +END; + +$heredoc = <<john's wife greeted $people->robert. +END; + +$heredoc = <<bar} +END; + +$heredoc = <<< "END" + some ${beers::softdrink} +END; + +$heredoc = <<< END + {${$object->getName()}} text +END; + +$heredoc = <<<"END" + some {${getName()}} +END; + +$heredoc = <<baz()()} +END; + +$heredoc = <<values[3]->name} text +END; + +$heredoc = <<<"END" + some ${$bar} +END; + +$heredoc = <<bar} text +END; + +$heredoc = <<<"END" + ${foo["${bar}"]} text +END; + +$heredoc = <<{${'a'}}} text +END; + +$heredoc = <<{$baz[1]}} +END; + +$heredoc = <<john's wife greeted $people->robert. +END; + +$heredoc = <<bar} +END; + +$heredoc = <<< "END" + some ${beers::softdrink} +END; + +$heredoc = <<< END + {${$object->getName()}} text +END; + +$heredoc = <<<"END" + some {${getName()}} +END; + +$heredoc = <<baz()()} +END; + +$heredoc = <<values[3]->name} text +END; + +$heredoc = <<<"END" + some ${$bar} +END; + +$heredoc = <<bar} text +END; + +$heredoc = <<<"END" + ${foo["${bar}"]} text +END; + +$heredoc = <<{${'a'}}} text +END; + +$heredoc = <<{$baz[1]}} +END; + +$heredoc = <<john's wife greeted $people->robert. +END; + +$heredoc = <<bar} + END; + +$heredoc = <<< "END" + some ${beers::softdrink} + END; + +$heredoc = <<< END + {${$object->getName()}} text + END; + +$heredoc = <<<"END" + some {${getName()}} + END; + +$heredoc = <<baz()()} + END; + +$heredoc = <<values[3]->name} text + END; + +$heredoc = <<<"END" + some ${$bar} + END; + +$heredoc = <<bar} text + END; + +$heredoc = <<<"END" + ${foo["${bar}"]} text + END; + +$heredoc = <<{${'a'}}} text + END; + +$heredoc = <<{$baz[1]}} + END; + +$heredoc = <<john's wife greeted $people->robert. + END; + +$heredoc = <<bar} + END; + +$heredoc = <<< "END" + some ${beers::softdrink} + END; + +$heredoc = <<< END + {${$object->getName()}} text + END; + +$heredoc = <<<"END" + some {${getName()}} + END; + +$heredoc = <<baz()()} + END; + +$heredoc = <<values[3]->name} text + END; + +$heredoc = <<<"END" + some ${$bar} + END; + +$heredoc = <<bar} text + END; + +$heredoc = <<<"END" + ${foo["${bar}"]} text + END; + +$heredoc = <<{${'a'}}} text + END; + +$heredoc = <<{$baz[1]}} + END; + +$heredoc = <<john's wife greeted $people->robert. + END; + +$heredoc = <<tabWidth = 4; + } + } + + /** * Returns the lines where errors should occur. * @@ -47,15 +67,18 @@ public function getErrorList() public function getWarningList($testFile = '') { $warnings = [ - 100 => 1, 104 => 1, + 108 => 1, ]; switch ($testFile) { case 'UnnecessaryHeredocUnitTest.1.inc': + case 'UnnecessaryHeredocUnitTest.4.inc': + case 'UnnecessaryHeredocUnitTest.5.inc': return $warnings; case 'UnnecessaryHeredocUnitTest.2.inc': + case 'UnnecessaryHeredocUnitTest.6.inc': if (PHP_VERSION_ID >= 70300) { return $warnings; }