Skip to main content

lean_ctx/core/patterns/
buf.rs

1//! buf (protobuf tooling) output compression.
2//!
3//! `buf lint`/`breaking` emit one `path:line:col:message` line per violation.
4//! We prefix a violation count and keep the findings (capped), so large lint
5//! runs collapse to a scannable summary. Clean builds become `buf: ok`.
6
7use crate::core::compressor::strip_ansi;
8
9macro_rules! static_regex {
10    ($pattern:expr_2021) => {{
11        static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
12        RE.get_or_init(|| {
13            regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
14        })
15    }};
16}
17
18fn violation_re() -> &'static regex::Regex {
19    static_regex!(r"^(.+?):\d+:\d+:(.+)$")
20}
21
22pub fn compress(_cmd: &str, output: &str) -> Option<String> {
23    let trimmed = output.trim();
24    if trimmed.is_empty() {
25        return Some("buf: ok".to_string());
26    }
27
28    let mut violations: Vec<String> = Vec::new();
29    let mut other: Vec<String> = Vec::new();
30    for raw in trimmed.lines() {
31        let line = strip_ansi(raw);
32        let t = line.trim();
33        if t.is_empty() {
34            continue;
35        }
36        if violation_re().is_match(t) {
37            violations.push(t.to_string());
38        } else {
39            other.push(t.to_string());
40        }
41    }
42
43    if violations.is_empty() {
44        // build/generate success or an error message we keep verbatim.
45        if other.is_empty() {
46            return Some("buf: ok".to_string());
47        }
48        return Some(other.join("\n"));
49    }
50
51    let mut parts = vec![format!("buf: {} violation(s)", violations.len())];
52    for v in violations.iter().take(20) {
53        parts.push(format!("  {v}"));
54    }
55    if violations.len() > 20 {
56        parts.push(format!("  ... +{} more", violations.len() - 20));
57    }
58    Some(parts.join("\n"))
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn counts_and_keeps_violations() {
67        let out = "proto/foo.proto:10:1:Field name should be lower_snake_case.\nproto/bar.proto:5:3:Enum value should be UPPER_SNAKE_CASE.";
68        let r = compress("buf lint", out).unwrap();
69        assert!(r.contains("buf: 2 violation(s)"), "{r}");
70        assert!(r.contains("foo.proto:10:1"), "{r}");
71    }
72
73    #[test]
74    fn empty_is_ok() {
75        assert_eq!(compress("buf lint", "").unwrap(), "buf: ok");
76    }
77}