Repository: denoland/deno
Pull request: fix(ext/node): `util.stripVTControlCharacters` compatibility

Fixes #32931

`util.stripVTControlCharacters` was not removing OSC 8 hyperlinks because the
`ansiPattern` regex only recognized BEL (`\u0007`) as the string terminator for
OSC sequences. OSC 8 hyperlinks use `ESC \` (i.e. `\u001B\u005C`) as their
terminator, so they passed through unstripped.

Updated the regex in both `ext/node/polyfills/internal/util/inspect.mjs` and
`ext/web/01_console.js` to match the current Node.js source:

- Added `\u001B\u005C` (ESC `\`) and `\u009C` (ST) as additional string
  terminators alongside `\u0007` (BEL)
- Updated the final character class to `[\dA-PR-TZcf-nq-uy=><~]` to match
  Node.js

Added test cases for OSC 8 hyperlinks with both ESC `\` and BEL terminators.

**AI tool usage disclosure:** Claude was used to assist with this PR.

Changed files:
filename: ext/node/polyfills/internal/util/inspect.mjs
patch: @@ -282,8 +282,9 @@ const builtInObjects = new SafeSet(
 // Matches all ansi escape code sequences in a string
 const ansiPattern = "[\\u001B\\u009B][[\\]()#;?]*" +
   "(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*" +
-  "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)" +
-  "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))";
+  "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)" +
+  "?(?:\\u0007|\\u001B\\u005C|\\u009C))" +
+  "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))";
 const ansi = new SafeRegExp(ansiPattern, "g");
 
 const reEmojiPresentation = new SafeRegExp("^\\p{Emoji_Presentation}$", "u");

filename: ext/web/01_console.js
patch: @@ -2700,8 +2700,9 @@ function formatSetIterInner(
 // Matches all ansi escape code sequences in a string
 const ansiPattern = "[\\u001B\\u009B][[\\]()#;?]*" +
   "(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*" +
-  "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)" +
-  "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))";
+  "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)" +
+  "?(?:\\u0007|\\u001B\\u005C|\\u009C))" +
+  "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))";
 const ansi = new SafeRegExp(ansiPattern, "g");
 
 /**

filename: tests/node_compat/config.jsonc
patch: @@ -2508,6 +2508,7 @@
     "parallel/test-util-isDeepStrictEqual.js": {},
     "parallel/test-util-parse-env.js": {},
     "parallel/test-util-primordial-monkeypatching.js": {},
+    "parallel/test-util-stripvtcontrolcharacters.js": {},
     "parallel/test-util-text-decoder.js": {},
     "parallel/test-util-types-exists.js": {},
     "parallel/test-util.js": {},

filename: tests/unit_node/util_test.ts
patch: @@ -232,6 +232,18 @@ Deno.test("[util] styleText() with array of formats", () => {
   assertEquals(colored, "\x1b[32m\x1b[31merror\x1b[39m\x1b[39m");
 });
 
+Deno.test("[util] stripVTControlCharacters() removes OSC 8 hyperlinks", () => {
+  // OSC 8 hyperlink with ESC \ (ST) terminator
+  const input =
+    "\x1b]8;;http://example.com\x1b\\This is a link\x1b]8;;\x1b\\ hello";
+  assertEquals(util.stripVTControlCharacters(input), "This is a link hello");
+
+  // OSC 8 hyperlink with BEL terminator
+  const inputBel =
+    "\x1b]8;;http://example.com\x07This is a link\x1b]8;;\x07 hello";
+  assertEquals(util.stripVTControlCharacters(inputBel), "This is a link hello");
+});
+
 Deno.test("[util] parseEnv()", () => {
   const env =
     "KEY1=VALUE1\nKEY2='VALUE2'\nKEYÄ3=\"VALUE3\"\nKEY4=VALÜE4\nKEY5='VALUE6'INVALID_LINE\nKEY6=A";