From 880a25b9407d9abbf52b30251863063a44b0f0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Dziewo=C5=84ski?= Date: Sun, 17 Nov 2024 21:36:37 +0100 Subject: [PATCH 1/3] Common: prepareForOutput(): Use single color code per run of characters This reduces the size of the output, and lessens weird effects of the ANSI color code characters on text wrapping, when the result of this method is used in a sniff error message (e.g. see LanguageConstructSpacingSniff). --- src/Util/Common.php | 41 ++++++++----------- .../Core/Util/Common/PrepareForOutputTest.php | 6 +-- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/Util/Common.php b/src/Util/Common.php index cb6965f62c..ddc636adcc 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -278,35 +278,26 @@ public static function escapeshellcmd($cmd) */ public static function prepareForOutput($content, $exclude=[]) { + $replacements = [ + "\r" => '\r', + "\n" => '\n', + "\t" => '\t', + " " => '·', + ]; 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); - } + // Do not replace spaces on Windows. + 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); - } + if (stripos(PHP_OS, 'WIN') !== 0) { + // On non-Windows, 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..068d9fdd1d 100644 --- a/tests/Core/Util/Common/PrepareForOutputTest.php +++ b/tests/Core/Util/Common/PrepareForOutputTest.php @@ -75,13 +75,13 @@ 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", + 'expected' => "\033[30;1m\\r\\n\\t\033[0m", 'expectedWin' => "\\r\\n\\t", ], '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", + 'expected' => "\033[30;1m····\033[0m", 'expectedWin' => " ", ], 'Other characters are unaffected' => [ @@ -102,7 +102,7 @@ public static function dataPrepareForOutput() "\r", "\n", ], - 'expected' => "\r\n\033[30;1m\\t\033[0m\033[30;1m·\033[0m", + 'expected' => "\r\n\033[30;1m\\t·\033[0m", 'expectedWin' => "\r\n\\t ", ], ]; From 41bc621e73db85186e5cff40905d81352d46a185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Dziewo=C5=84ski?= Date: Sun, 10 Nov 2024 00:46:02 +0100 Subject: [PATCH 2/3] Common: prepareForOutput(): Use ANSI color codes on Windows as well Windows' console supports ANSI escape sequences since Windows 10. https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences PHP enables this by default since PHP 7.2. https://www.php.net/manual/en/function.sapi-windows-vt100-support.php On older PHP versions, the output with --colors will not be correct, but this is already the case when using this option. PHP CodeSniffer special-cased Windows in this code in 2014 (bfd095d39895abef6afae575382d58f7ad058232). This workaround is no longer needed today in 2024. Note that the --colors option was already supported on Windows. This only affects inline highlighting in error and debug messages. --- src/Tokenizers/PHP.php | 10 +--------- src/Util/Common.php | 11 ++++------- tests/Core/Util/Common/PrepareForOutputTest.php | 4 ++-- 3 files changed, 7 insertions(+), 18 deletions(-) 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 ddc636adcc..8762072f72 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. @@ -291,11 +290,9 @@ public static function prepareForOutput($content, $exclude=[]) $replacements = array_diff_key($replacements, array_fill_keys($exclude, true)); - if (stripos(PHP_OS, 'WIN') !== 0) { - // On non-Windows, colour runs of invisible characters. - $match = implode('', array_keys($replacements)); - $content = preg_replace("/([$match]+)/", "\033[30;1m$1\033[0m", $content); - } + // Colour runs of invisible characters. + $match = implode('', array_keys($replacements)); + $content = preg_replace("/([$match]+)/", "\033[30;1m$1\033[0m", $content); $content = strtr($content, $replacements); diff --git a/tests/Core/Util/Common/PrepareForOutputTest.php b/tests/Core/Util/Common/PrepareForOutputTest.php index 068d9fdd1d..e786f4ac0c 100644 --- a/tests/Core/Util/Common/PrepareForOutputTest.php +++ b/tests/Core/Util/Common/PrepareForOutputTest.php @@ -76,7 +76,7 @@ public static function dataPrepareForOutput() 'content' => "\r\n\t", 'exclude' => [], 'expected' => "\033[30;1m\\r\\n\\t\033[0m", - 'expectedWin' => "\\r\\n\\t", + 'expectedWin' => "\033[30;1m\\r\\n\\t\033[0m", ], 'Spaces are replaced with a unique mark' => [ 'content' => " ", @@ -103,7 +103,7 @@ public static function dataPrepareForOutput() "\n", ], 'expected' => "\r\n\033[30;1m\\t·\033[0m", - 'expectedWin' => "\r\n\\t ", + 'expectedWin' => "\r\n\033[30;1m\\t\033[0m ", ], ]; From 8e976bc3674c971e09c387ad2ba5c1640d8fe63d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Dziewo=C5=84ski?= Date: Sun, 17 Nov 2024 21:54:22 +0100 Subject: [PATCH 3/3] Common: prepareForOutput(): Use Unicode output on Windows as well on PHP 7.1+ PHP 7.1 fixed problems with printing Unicode characters to Windows console that forced us to avoid using them. https://www.php.net/manual/en/migration71.windows-support.php --- src/Reports/Code.php | 3 +- src/Util/Common.php | 5 +- .../Core/Util/Common/PrepareForOutputTest.php | 53 ++++++++++++++----- 3 files changed, 46 insertions(+), 15 deletions(-) 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/Util/Common.php b/src/Util/Common.php index 8762072f72..983b85a1e4 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -283,8 +283,9 @@ public static function prepareForOutput($content, $exclude=[]) "\t" => '\t', " " => '·', ]; - if (stripos(PHP_OS, 'WIN') === 0) { - // Do not replace spaces on Windows. + 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[" "]); } diff --git a/tests/Core/Util/Common/PrepareForOutputTest.php b/tests/Core/Util/Common/PrepareForOutputTest.php index e786f4ac0c..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> */ @@ -76,25 +105,25 @@ public static function dataPrepareForOutput() 'content' => "\r\n\t", 'exclude' => [], 'expected' => "\033[30;1m\\r\\n\\t\033[0m", - 'expectedWin' => "\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", - 'expectedWin' => " ", + '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 ", @@ -103,7 +132,7 @@ public static function dataPrepareForOutput() "\n", ], 'expected' => "\r\n\033[30;1m\\t·\033[0m", - 'expectedWin' => "\r\n\033[30;1m\\t\033[0m ", + 'expectedOld' => "\r\n\033[30;1m\\t\033[0m ", ], ];