syncable_cli/analyzer/helmlint/formatter/
github.rs1use crate::analyzer::helmlint::lint::LintResult;
7use crate::analyzer::helmlint::types::Severity;
8
9pub fn format(result: &LintResult) -> String {
11 let mut output = String::new();
12
13 for error in &result.parse_errors {
15 output.push_str(&format!(
16 "::error file={},title=Parse Error::{}\n",
17 result.chart_path, error
18 ));
19 }
20
21 for failure in &result.failures {
23 let level = match failure.severity {
24 Severity::Error => "error",
25 Severity::Warning => "warning",
26 Severity::Info => "notice",
27 Severity::Style => "notice",
28 Severity::Ignore => continue, };
30
31 let file = failure.file.display().to_string();
32 let line = failure.line;
33 let title = &failure.code;
34 let message = escape_message(&failure.message);
35
36 let annotation = match failure.column {
38 Some(col) => format!(
39 "::{}file={},line={},col={},title={}::{}\n",
40 level, file, line, col, title, message
41 ),
42 None => format!(
43 "::{}file={},line={},title={}::{}\n",
44 level, file, line, title, message
45 ),
46 };
47
48 output.push_str(&annotation);
49 }
50
51 if !result.failures.is_empty() || !result.parse_errors.is_empty() {
53 let total = result.failures.len() + result.parse_errors.len();
54 let summary = format!(
55 "Helmlint found {} {} ({} errors, {} warnings)",
56 total,
57 if total == 1 { "issue" } else { "issues" },
58 result.error_count + result.parse_errors.len(),
59 result.warning_count
60 );
61
62 if result.error_count > 0 || !result.parse_errors.is_empty() {
63 output.push_str(&format!("::error::{}\n", summary));
64 } else {
65 output.push_str(&format!("::warning::{}\n", summary));
66 }
67 }
68
69 output
70}
71
72fn escape_message(message: &str) -> String {
75 message
76 .replace('%', "%25")
77 .replace('\r', "%0D")
78 .replace('\n', "%0A")
79 .replace(':', "%3A")
80 .replace(',', "%2C")
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::analyzer::helmlint::types::{CheckFailure, RuleCategory, Severity};
87
88 #[test]
89 fn test_github_format_empty() {
90 let result = LintResult::new("test-chart");
91 let output = format(&result);
92 assert!(output.is_empty());
93 }
94
95 #[test]
96 fn test_github_format_error() {
97 let mut result = LintResult::new("test-chart");
98 result.failures.push(CheckFailure::new(
99 "HL1001",
100 Severity::Error,
101 "Missing Chart.yaml",
102 "Chart.yaml",
103 1,
104 RuleCategory::Structure,
105 ));
106 result.error_count = 1;
107
108 let output = format(&result);
109 assert!(output.contains("::error"));
110 assert!(output.contains("file=Chart.yaml"));
111 assert!(output.contains("line=1"));
112 assert!(output.contains("title=HL1001"));
113 }
114
115 #[test]
116 fn test_github_format_warning() {
117 let mut result = LintResult::new("test-chart");
118 result.failures.push(CheckFailure::new(
119 "HL1006",
120 Severity::Warning,
121 "Missing description",
122 "Chart.yaml",
123 5,
124 RuleCategory::Structure,
125 ));
126 result.warning_count = 1;
127
128 let output = format(&result);
129 assert!(output.contains("::warning"));
130 }
131
132 #[test]
133 fn test_escape_message() {
134 assert_eq!(escape_message("hello:world"), "hello%3Aworld");
135 assert_eq!(escape_message("a,b"), "a%2Cb");
136 assert_eq!(escape_message("line1\nline2"), "line1%0Aline2");
137 }
138}