Repository: nodejs/node
Pull request: util: add fast path to stripVTControlCharacters

## Problem

`stripVTControlCharacters` always runs a regex replacement, even when the input contains no ANSI escape codes. This is the common case — most strings passed through are already plain text (e.g. checking/sanitizing user input, processing log lines that are mostly text).

## Changes

Adds a `StringPrototypeIndexOf` guard for ESC (`\u001B`) and CSI (`\u009B`) before the regex. Since all ANSI escape sequences start with one of these introducers, their absence means no ANSI codes exist and we can return the string immediately — skipping regex execution.

Based on the same optimization proposed in [chalk/strip-ansi#54](https://github.com/chalk/strip-ansi/pull/54).

Also adds a benchmark and test coverage for the fast path, 7-bit ESC sequences, and 8-bit CSI sequences.

## Benchmarks

Node.js benchmark results using `benchmark/compare.js` (30 runs, two separate builds):

```
                                                         confidence improvement accuracy (*)   (**)  (***)
util/strip-vt-control-characters.js input='noAnsi-short' n=1000000        ***     93.43 %       ±3.42% ±4.56% ±5.94%
util/strip-vt-control-characters.js input='noAnsi-long'  n=1000000        ***    591.26 %       ±9.71% ±12.97% ±16.97%
util/strip-vt-control-characters.js input='ansi-short'   n=1000000                -0.32 %       ±1.39% ±1.85% ±2.41%
util/strip-vt-control-characters.js input='ansi-long'    n=1000000                 1.74 %       ±2.55% ±3.39% ±4.42%
```

~1.93x faster for short non-ANSI strings, ~6.91x for long non-ANSI strings. Negligible impact on ANSI input.

Changed files:
filename: benchmark/util/strip-vt-control-characters.js
patch: @@ -0,0 +1,36 @@
+'use strict';
+
+const common = require('../common.js');
+
+const { stripVTControlCharacters } = require('node:util');
+const assert = require('node:assert');
+
+const bench = common.createBenchmark(main, {
+  input: ['noAnsi-short', 'noAnsi-long', 'ansi-short', 'ansi-long'],
+  n: [1e6],
+});
+
+function main({ input, n }) {
+  let str;
+  switch (input) {
+    case 'noAnsi-short':
+      str = 'This is a plain text string without any ANSI codes';
+      break;
+    case 'noAnsi-long':
+      str = 'Long plain text without ANSI. '.repeat(333);
+      break;
+    case 'ansi-short':
+      str = '\u001B[31mHello\u001B[39m';
+      break;
+    case 'ansi-long':
+      str = ('\u001B[31m' + 'colored text '.repeat(10) + '\u001B[39m').repeat(10);
+      break;
+  }
+
+  bench.start();
+  for (let i = 0; i < n; i++) {
+    const result = stripVTControlCharacters(str);
+    assert.ok(typeof result === 'string');
+  }
+  bench.end(n);
+}

filename: lib/internal/util/inspect.js
patch: @@ -3036,6 +3036,13 @@ if (internalBinding('config').hasIntl) {
 function stripVTControlCharacters(str) {
   validateString(str, 'str');
 
+  // Short-circuit: all ANSI escape sequences start with either
+  // ESC (\u001B, 7-bit) or CSI (\u009B, 8-bit) introducer.
+  // If neither is present, the string has no VT control characters.
+  if (StringPrototypeIndexOf(str, '\u001B') === -1 &&
+      StringPrototypeIndexOf(str, '\u009B') === -1)
+    return str;
+
   return RegExpPrototypeSymbolReplace(ansi, str, '');
 }
 

filename: test/parallel/test-util.js
patch: @@ -87,3 +87,13 @@ assert.throws(() => {
   message: 'The "str" argument must be of type string.' +
            common.invalidArgTypeHelper({})
 });
+
+// stripVTControlCharacters: fast path returns input when no ANSI codes
+assert.strictEqual(util.stripVTControlCharacters('hello'), 'hello');
+assert.strictEqual(util.stripVTControlCharacters(''), '');
+
+// stripVTControlCharacters: strips 7-bit ESC sequences
+assert.strictEqual(util.stripVTControlCharacters('\u001B[31mfoo\u001B[39m'), 'foo');
+
+// stripVTControlCharacters: strips 8-bit CSI sequences
+assert.strictEqual(util.stripVTControlCharacters('\u009B31mfoo\u009B39m'), 'foo');