lean_ctx_patterns/
eslint.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 eslint_file_re() -> &'static regex::Regex {
13 static_regex!(r"^(/\S+|[A-Z]:\\\S+|\S+\.\w+)$")
14}
15fn eslint_error_re() -> &'static regex::Regex {
16 static_regex!(r"^\s+(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}(\S+)$")
17}
18fn eslint_summary_re() -> &'static regex::Regex {
19 static_regex!(r"(\d+)\s+problems?\s*\((\d+)\s+errors?,\s*(\d+)\s+warnings?\)")
20}
21fn biome_diag_re() -> &'static regex::Regex {
22 static_regex!(r"^([\w/.-]+):(\d+):(\d+)\s+(\w+)\s+(.+)$")
23}
24
25pub fn compress(command: &str, output: &str) -> Option<String> {
26 if command.contains("biome") {
27 return Some(compress_biome(output));
28 }
29 if command.contains("stylelint") {
30 return Some(compress_stylelint(output));
31 }
32 Some(compress_eslint(output))
33}
34
35fn compress_eslint(output: &str) -> String {
36 let trimmed = output.trim();
37 if trimmed.is_empty() {
38 return "clean".to_string();
39 }
40
41 let mut by_rule: std::collections::HashMap<String, (u32, u32)> =
42 std::collections::HashMap::new();
43 let mut file_count = 0u32;
44 let mut total_errors = 0u32;
45 let mut total_warnings = 0u32;
46
47 for line in trimmed.lines() {
48 let l = line.trim();
49 if eslint_file_re().is_match(l) {
50 file_count += 1;
51 continue;
52 }
53 if let Some(caps) = eslint_error_re().captures(line) {
54 let severity = &caps[3];
55 let rule = caps[5].to_string();
56 let entry = by_rule.entry(rule).or_insert((0, 0));
57 if severity == "error" {
58 entry.0 += 1;
59 total_errors += 1;
60 } else {
61 entry.1 += 1;
62 total_warnings += 1;
63 }
64 }
65 if let Some(caps) = eslint_summary_re().captures(line) {
66 total_errors = caps[2].parse().unwrap_or(total_errors);
67 total_warnings = caps[3].parse().unwrap_or(total_warnings);
68 }
69 }
70
71 if by_rule.is_empty() && total_errors == 0 && total_warnings == 0 {
72 if trimmed.lines().count() <= 5 {
73 return trimmed.to_string();
74 }
75 return compact_output(trimmed, 10);
76 }
77
78 let mut parts = Vec::new();
79 parts.push(format!(
80 "{total_errors} errors, {total_warnings} warnings in {file_count} files"
81 ));
82
83 let mut rules: Vec<(String, (u32, u32))> = by_rule.into_iter().collect();
84 rules.sort_by_key(|(_, (errors, warnings))| std::cmp::Reverse(*errors + *warnings));
85
86 for (rule, (errors, warnings)) in rules.iter().take(10) {
87 let total = errors + warnings;
88 parts.push(format!(" {rule}: {total}"));
89 }
90 if rules.len() > 10 {
91 parts.push(format!(" ... +{} more rules", rules.len() - 10));
92 }
93
94 parts.join("\n")
95}
96
97fn compress_biome(output: &str) -> String {
98 let trimmed = output.trim();
99 if trimmed.is_empty() || trimmed.contains("No diagnostics") {
100 return "clean".to_string();
101 }
102
103 let mut errors = 0u32;
104 let mut warnings = 0u32;
105 let mut files: std::collections::HashSet<String> = std::collections::HashSet::new();
106
107 for line in trimmed.lines() {
108 if let Some(caps) = biome_diag_re().captures(line) {
109 files.insert(caps[1].to_string());
110 let severity = &caps[4];
111 if severity.contains("error") || severity.contains("ERROR") {
112 errors += 1;
113 } else {
114 warnings += 1;
115 }
116 }
117 }
118
119 if errors == 0 && warnings == 0 {
120 return compact_output(trimmed, 10);
121 }
122
123 format!(
124 "{errors} errors, {warnings} warnings in {} files",
125 files.len()
126 )
127}
128
129fn compress_stylelint(output: &str) -> String {
130 let trimmed = output.trim();
131 if trimmed.is_empty() {
132 return "clean".to_string();
133 }
134 compress_eslint(output)
135}
136
137fn compact_output(text: &str, max: usize) -> String {
138 let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
139 if lines.len() <= max {
140 return lines.join("\n");
141 }
142 format!(
143 "{}\n{}",
144 lines[..max].join("\n"),
145 super::elision_marker(lines.len() - max)
146 )
147}