lean_ctx_patterns/
typescript.rs1macro_rules! static_regex {
4 ($pattern:expr_2021) => {{
5 static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
6 RE.get_or_init(|| {
7 regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
8 })
9 }};
10}
11
12fn tsc_error_re() -> &'static regex::Regex {
14 static_regex!(r"^(?:(\S+)\((\d+),(\d+)\):|(\S+?):(\d+):(\d+) -) error (TS\d+): (.+)$")
15}
16fn error_count_re() -> &'static regex::Regex {
17 static_regex!(r"Found (\d+) errors?")
18}
19
20pub fn compress(output: &str) -> Option<String> {
24 let mut files: Vec<(String, Vec<String>)> = Vec::new();
25 let mut total_errors = 0u32;
26 let mut diagnostics = 0u32;
27 let mut in_diagnostic = false;
28
29 for line in output.lines() {
30 if let Some(caps) = tsc_error_re().captures(line) {
31 let (file, line_no, col) = match caps.get(1) {
32 Some(f) => (f.as_str(), &caps[2], &caps[3]),
33 None => (&caps[4], &caps[5], &caps[6]),
34 };
35 let entry = format!(" {line_no}:{col} {} {}", &caps[7], caps[8].trim());
36 match files.iter_mut().find(|(f, _)| f == file) {
37 Some((_, list)) => list.push(entry),
38 None => files.push((file.to_string(), vec![entry])),
39 }
40 diagnostics += 1;
41 in_diagnostic = true;
42 continue;
43 }
44 if let Some(caps) = error_count_re().captures(line) {
45 total_errors = caps[1].parse().unwrap_or(0);
46 in_diagnostic = false;
47 continue;
48 }
49 let is_elaboration = line.starts_with(" ") && !line.trim().is_empty();
50 if in_diagnostic
51 && is_elaboration
52 && let Some((_, list)) = files.last_mut()
53 {
54 list.push(format!(" {}", line.trim()));
55 } else {
56 in_diagnostic = false;
57 }
58 }
59
60 if files.is_empty() {
61 return None;
62 }
63
64 let total = if total_errors > 0 {
65 total_errors
66 } else {
67 diagnostics
68 };
69 let mut result = vec![format!("{total} errors in {} files:", files.len())];
70 for (file, entries) in files {
71 result.push(file);
72 result.extend(entries);
73 }
74 Some(result.join("\n"))
75}
76
77#[cfg(test)]
78mod tests {
79 use super::compress;
80
81 #[test]
82 fn keeps_full_path_position_and_whole_message() {
83 let out = "src/a/index.ts(3,7): error TS2322: Type '{ id: string; }' is not assignable to type 'UserRecordWithPermissions'.\n Property 'permissions' is missing in type '{ id: string; }'.\nsrc/b/index.ts(10,1): error TS2304: Cannot find name 'foo'.\n\nFound 2 errors in 2 files.\n";
84 assert_eq!(
85 compress(out).unwrap(),
86 "2 errors in 2 files:\nsrc/a/index.ts\n 3:7 TS2322 Type '{ id: string; }' is not assignable to type 'UserRecordWithPermissions'.\n Property 'permissions' is missing in type '{ id: string; }'.\nsrc/b/index.ts\n 10:1 TS2304 Cannot find name 'foo'."
87 );
88 }
89
90 #[test]
91 fn pretty_format_and_grouping() {
92 let out = "src/x.ts:1:2 - error TS1005: ';' expected.\n\n1 const a = 1 const b\n\nsrc/x.ts:4:1 - error TS2304: Cannot find name 'c'.\n\nFound 2 errors in the same file, starting at: src/x.ts:1\n";
93 assert_eq!(
94 compress(out).unwrap(),
95 "2 errors in 1 files:\nsrc/x.ts\n 1:2 TS1005 ';' expected.\n 4:1 TS2304 Cannot find name 'c'."
96 );
97 }
98
99 #[test]
100 fn no_diagnostics_is_not_handled() {
101 assert!(compress("Version 5.4.5\n").is_none());
102 }
103}