55//! stdout/stderr interleaving, so every rule here should correspond to a real
66//! source of nondeterminism (paths, durations, versions, machine parallelism).
77
8- use std:: borrow:: Cow ;
8+ use std:: { borrow:: Cow , sync:: LazyLock } ;
9+
10+ // Compiled once per run: redaction runs on every snapshotted step, and regex
11+ // compilation dominates matching cost at that frequency.
12+ static UUID_RE : LazyLock < regex:: Regex > = LazyLock :: new ( || {
13+ regex:: Regex :: new ( r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" ) . unwrap ( )
14+ } ) ;
15+ static DURATION_RE : LazyLock < regex:: Regex > =
16+ LazyLock :: new ( || regex:: Regex :: new ( r"\b\d+(\.\d+)?(ns|µs|ms|s)\b" ) . unwrap ( ) ) ;
17+ static VERSION_RE : LazyLock < regex:: Regex > = LazyLock :: new ( || {
18+ regex:: Regex :: new ( r"\bv?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?\b" ) . unwrap ( )
19+ } ) ;
20+ static THREAD_RE : LazyLock < regex:: Regex > =
21+ LazyLock :: new ( || regex:: Regex :: new ( r"\d+ threads" ) . unwrap ( ) ) ;
22+ static NODE_WARNING_RE : LazyLock < regex:: Regex > =
23+ LazyLock :: new ( || regex:: Regex :: new ( r"(?m)^\(node:\d+\) ExperimentalWarning:.*\n?" ) . unwrap ( ) ) ;
24+ static NODE_TRACE_WARNING_RE : LazyLock < regex:: Regex > = LazyLock :: new ( || {
25+ regex:: Regex :: new (
26+ r"(?m)^\(Use `node --trace-warnings \.\.\.` to show where the warning was created\)\n?" ,
27+ )
28+ . unwrap ( )
29+ } ) ;
930
1031#[ expect(
1132 clippy:: disallowed_types,
@@ -72,33 +93,21 @@ pub fn redact_output(mut output: String, paths: &[(&str, &'static str)]) -> Stri
7293 redact_string ( & mut output, & borrowed) ;
7394
7495 // Redact UUIDs to "<uuid>"
75- let uuid_regex =
76- regex:: Regex :: new ( r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" ) . unwrap ( ) ;
77- output = uuid_regex. replace_all ( & output, "<uuid>" ) . into_owned ( ) ;
96+ output = UUID_RE . replace_all ( & output, "<uuid>" ) . into_owned ( ) ;
7897
7998 // Redact durations like "0ns", "123ms" or "1.23s" to "<duration>".
8099 // Runs before version redaction so "1.23s" never half-matches as a version.
81- let duration_regex = regex:: Regex :: new ( r"\b\d+(\.\d+)?(ns|µs|ms|s)\b" ) . unwrap ( ) ;
82- output = duration_regex. replace_all ( & output, "<duration>" ) . into_owned ( ) ;
100+ output = DURATION_RE . replace_all ( & output, "<duration>" ) . into_owned ( ) ;
83101
84102 // Redact semver-shaped versions (bundled tool versions, Node versions).
85- let version_regex =
86- regex:: Regex :: new ( r"\bv?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?\b" ) . unwrap ( ) ;
87- output = version_regex. replace_all ( & output, "<version>" ) . into_owned ( ) ;
103+ output = VERSION_RE . replace_all ( & output, "<version>" ) . into_owned ( ) ;
88104
89105 // Redact thread counts like "16 threads" to "<n> threads"
90- let thread_regex = regex:: Regex :: new ( r"\d+ threads" ) . unwrap ( ) ;
91- output = thread_regex. replace_all ( & output, "<n> threads" ) . into_owned ( ) ;
106+ output = THREAD_RE . replace_all ( & output, "<n> threads" ) . into_owned ( ) ;
92107
93108 // Remove Node.js experimental warnings (e.g., Type Stripping warnings)
94- let node_warning_regex =
95- regex:: Regex :: new ( r"(?m)^\(node:\d+\) ExperimentalWarning:.*\n?" ) . unwrap ( ) ;
96- output = node_warning_regex. replace_all ( & output, "" ) . into_owned ( ) ;
97- let node_trace_warning_regex = regex:: Regex :: new (
98- r"(?m)^\(Use `node --trace-warnings \.\.\.` to show where the warning was created\)\n?" ,
99- )
100- . unwrap ( ) ;
101- output = node_trace_warning_regex. replace_all ( & output, "" ) . into_owned ( ) ;
109+ output = NODE_WARNING_RE . replace_all ( & output, "" ) . into_owned ( ) ;
110+ output = NODE_TRACE_WARNING_RE . replace_all ( & output, "" ) . into_owned ( ) ;
102111
103112 // Remove ^C echo that Unix terminal drivers emit when ETX (0x03) is written
104113 // to the PTY. Windows ConPTY does not echo it.
@@ -111,8 +120,11 @@ pub fn redact_output(mut output: String, paths: &[(&str, &'static str)]) -> Stri
111120
112121 // Sort consecutive diagnostic blocks to handle non-deterministic tool output
113122 // (e.g., oxlint reports warnings in arbitrary order due to multi-threading).
114- // Each block starts with " ! " and ends at the next empty line.
115- output = sort_diagnostic_blocks ( & output) ;
123+ // Each block starts with " ! " and ends at the next empty line. Most
124+ // screens have none, so skip the split/rejoin allocation entirely then.
125+ if output. contains ( " ! " ) {
126+ output = sort_diagnostic_blocks ( & output) ;
127+ }
116128
117129 output
118130}
@@ -148,12 +160,12 @@ fn sort_diagnostic_blocks(output: &str) -> String {
148160
149161 blocks. sort ( ) ;
150162
151- for ( j, block) in blocks. iter ( ) . enumerate ( ) {
163+ // Restore an empty-line separator after every block (`i` never
164+ // exceeds parts.len(), so the upstream guard here was always
165+ // true; keep the behavior, drop the misleading condition).
166+ for block in & blocks {
152167 result. extend_from_slice ( block) ;
153- // Restore empty line separators (between blocks + trailing)
154- if j < blocks. len ( ) - 1 || i <= parts. len ( ) {
155- result. push ( "" ) ;
156- }
168+ result. push ( "" ) ;
157169 }
158170 } else {
159171 result. push ( parts[ i] ) ;
0 commit comments