diff --git a/src/Reports/Code.php b/src/Reports/Code.php index c97e1681b7..e30fca3c63 100644 --- a/src/Reports/Code.php +++ b/src/Reports/Code.php @@ -228,7 +228,8 @@ public function generateFileReport($report, File $phpcsFile, $showSources=false, if (strpos($tokenContent, "\t") !== false) { $token = $tokens[$i]; $token['content'] = $tokenContent; - if (stripos(PHP_OS, 'WIN') === 0) { + if (stripos(PHP_OS, 'WIN') === 0 && PHP_VERSION_ID < 70100) { + // Printing Unicode characters like '»' to Windows console only works since PHP 7.1. $tab = "\000"; } else { $tab = "\033[30;1m»\033[0m"; diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 9c6c11e4c3..655a645fb4 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -516,10 +516,6 @@ protected function tokenize($string) { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t*** START PHP TOKENIZING ***".PHP_EOL; - $isWin = false; - if (stripos(PHP_OS, 'WIN') === 0) { - $isWin = true; - } } $tokens = @token_get_all($string); @@ -584,11 +580,7 @@ protected function tokenize($string) ) { $token[1] .= "\n"; if (PHP_CODESNIFFER_VERBOSITY > 1) { - if ($isWin === true) { - echo '\n'; - } else { - echo "\033[30;1m\\n\033[0m"; - } + echo "\033[30;1m\\n\033[0m"; } if ($tokens[($stackPtr + 1)][1] === "\n") { diff --git a/src/Util/Common.php b/src/Util/Common.php index cb6965f62c..983b85a1e4 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -267,8 +267,7 @@ public static function escapeshellcmd($cmd) /** * Prepares token content for output to screen. * - * Replaces invisible characters so they are visible. On non-Windows - * operating systems it will also colour the invisible characters. + * Replaces invisible characters so they are visible, and colour them. * * @param string $content The content to prepare. * @param string[] $exclude A list of characters to leave invisible. @@ -278,35 +277,25 @@ public static function escapeshellcmd($cmd) */ public static function prepareForOutput($content, $exclude=[]) { - if (stripos(PHP_OS, 'WIN') === 0) { - if (in_array("\r", $exclude, true) === false) { - $content = str_replace("\r", '\r', $content); - } - - if (in_array("\n", $exclude, true) === false) { - $content = str_replace("\n", '\n', $content); - } - - if (in_array("\t", $exclude, true) === false) { - $content = str_replace("\t", '\t', $content); - } - } else { - if (in_array("\r", $exclude, true) === false) { - $content = str_replace("\r", "\033[30;1m\\r\033[0m", $content); - } + $replacements = [ + "\r" => '\r', + "\n" => '\n', + "\t" => '\t', + " " => '·', + ]; + if (stripos(PHP_OS, 'WIN') === 0 && PHP_VERSION_ID < 70100) { + // Do not replace spaces on old PHP on Windows. + // Printing Unicode characters like '·' to Windows console only works since PHP 7.1. + unset($replacements[" "]); + } - if (in_array("\n", $exclude, true) === false) { - $content = str_replace("\n", "\033[30;1m\\n\033[0m", $content); - } + $replacements = array_diff_key($replacements, array_fill_keys($exclude, true)); - if (in_array("\t", $exclude, true) === false) { - $content = str_replace("\t", "\033[30;1m\\t\033[0m", $content); - } + // Colour runs of invisible characters. + $match = implode('', array_keys($replacements)); + $content = preg_replace("/([$match]+)/", "\033[30;1m$1\033[0m", $content); - if (in_array(' ', $exclude, true) === false) { - $content = str_replace(' ', "\033[30;1m·\033[0m", $content); - } - }//end if + $content = strtr($content, $replacements); return $content; diff --git a/tests/Core/Util/Common/PrepareForOutputTest.php b/tests/Core/Util/Common/PrepareForOutputTest.php index 8eb2fc22f9..1358faaa2b 100644 --- a/tests/Core/Util/Common/PrepareForOutputTest.php +++ b/tests/Core/Util/Common/PrepareForOutputTest.php @@ -27,14 +27,14 @@ final class PrepareForOutputTest extends TestCase * @param string $content The content to prepare. * @param string[] $exclude A list of characters to leave invisible. * @param string $expected Expected function output. - * @param string $expectedWin Expected function output on Windows (unused in this test). + * @param string $expectedOld Expected function output on PHP<7.1 on Windows (unused in this test). * * @requires OS ^(?!WIN).* * @dataProvider dataPrepareForOutput * * @return void */ - public function testPrepareForOutput($content, $exclude, $expected, $expectedWin) + public function testPrepareForOutput($content, $exclude, $expected, $expectedOld) { $this->assertSame($expected, Common::prepareForOutput($content, $exclude)); @@ -42,30 +42,59 @@ public function testPrepareForOutput($content, $exclude, $expected, $expectedWin /** - * Test formatting whitespace characters, on Windows. + * Test formatting whitespace characters, on modern PHP on Windows. * * @param string $content The content to prepare. * @param string[] $exclude A list of characters to leave invisible. - * @param string $expected Expected function output (unused in this test). - * @param string $expectedWin Expected function output on Windows. + * @param string $expected Expected function output. + * @param string $expectedOld Expected function output on PHP<7.1 on Windows (unused in this test). * * @requires OS ^WIN.*. + * @requires PHP 7.1 * @dataProvider dataPrepareForOutput * * @return void */ - public function testPrepareForOutputWindows($content, $exclude, $expected, $expectedWin) + public function testPrepareForOutputWindows($content, $exclude, $expected, $expectedOld) { - $this->assertSame($expectedWin, Common::prepareForOutput($content, $exclude)); + $this->assertSame($expected, Common::prepareForOutput($content, $exclude)); }//end testPrepareForOutputWindows() + /** + * Test formatting whitespace characters, on PHP<7.1 on Windows. + * + * @param string $content The content to prepare. + * @param string[] $exclude A list of characters to leave invisible. + * @param string $expected Expected function output (unused in this test). + * @param string $expectedOld Expected function output on PHP<7.1 on Windows. + * + * @requires OS ^WIN.*. + * @requires PHP < 7.1 + * @dataProvider dataPrepareForOutput + * + * @return void + */ + public function testPrepareForOutputOldPHPWindows($content, $exclude, $expected, $expectedOld) + { + // PHPUnit 4.8 (used on PHP 5.4) does not support the `@requires PHP < 7.1` syntax, + // so double-check to avoid test failures. + if (PHP_VERSION_ID >= 70100) { + $this->markTestSkipped("Only for PHP < 7.1"); + } + + $this->assertSame($expectedOld, Common::prepareForOutput($content, $exclude)); + + }//end testPrepareForOutputOldPHPWindows() + + /** * Data provider. * * @see testPrepareForOutput() * @see testPrepareForOutputWindows() + * @see testPrepareForOutputOldPHPWindows() * * @return array> */ @@ -75,26 +104,26 @@ public static function dataPrepareForOutput() 'Special characters are replaced with their escapes' => [ 'content' => "\r\n\t", 'exclude' => [], - 'expected' => "\033[30;1m\\r\033[0m\033[30;1m\\n\033[0m\033[30;1m\\t\033[0m", - 'expectedWin' => "\\r\\n\\t", + 'expected' => "\033[30;1m\\r\\n\\t\033[0m", + 'expectedOld' => "\033[30;1m\\r\\n\\t\033[0m", ], 'Spaces are replaced with a unique mark' => [ 'content' => " ", 'exclude' => [], - 'expected' => "\033[30;1m·\033[0m\033[30;1m·\033[0m\033[30;1m·\033[0m\033[30;1m·\033[0m", - 'expectedWin' => " ", + 'expected' => "\033[30;1m····\033[0m", + 'expectedOld' => " ", ], 'Other characters are unaffected' => [ 'content' => "{echo 1;}", 'exclude' => [], 'expected' => "{echo\033[30;1m·\033[0m1;}", - 'expectedWin' => "{echo 1;}", + 'expectedOld' => "{echo 1;}", ], 'No replacements' => [ 'content' => 'nothing-should-be-replaced', 'exclude' => [], 'expected' => 'nothing-should-be-replaced', - 'expectedWin' => 'nothing-should-be-replaced', + 'expectedOld' => 'nothing-should-be-replaced', ], 'Excluded whitespace characters are unaffected' => [ 'content' => "\r\n\t ", @@ -102,8 +131,8 @@ public static function dataPrepareForOutput() "\r", "\n", ], - 'expected' => "\r\n\033[30;1m\\t\033[0m\033[30;1m·\033[0m", - 'expectedWin' => "\r\n\\t ", + 'expected' => "\r\n\033[30;1m\\t·\033[0m", + 'expectedOld' => "\r\n\033[30;1m\\t\033[0m ", ], ];