syncable_cli/analyzer/dclint/formatter/
stylish.rs1use crate::analyzer::dclint::lint::LintResult;
4use crate::analyzer::dclint::types::Severity;
5
6pub fn format(results: &[LintResult]) -> String {
8 let mut output = String::new();
9 let mut total_errors = 0;
10 let mut total_warnings = 0;
11 let mut total_fixable = 0;
12
13 for result in results {
14 if result.failures.is_empty() && result.parse_errors.is_empty() {
15 continue;
16 }
17
18 output.push_str(&format!("\n{}\n", result.file_path));
20
21 for err in &result.parse_errors {
23 output.push_str(&format!(" error {}\n", err));
24 total_errors += 1;
25 }
26
27 for failure in &result.failures {
29 let severity_str = match failure.severity {
30 Severity::Error => "error",
31 Severity::Warning => "warning",
32 Severity::Info => "info",
33 Severity::Style => "style",
34 };
35
36 let fixable_str = if failure.fixable { " (fixable)" } else { "" };
37
38 output.push_str(&format!(
39 " {}:{} {} {} {}{}\n",
40 failure.line,
41 failure.column,
42 severity_str,
43 failure.message,
44 failure.code,
45 fixable_str
46 ));
47
48 match failure.severity {
49 Severity::Error => total_errors += 1,
50 Severity::Warning => total_warnings += 1,
51 _ => {}
52 }
53
54 if failure.fixable {
55 total_fixable += 1;
56 }
57 }
58 }
59
60 if total_errors > 0 || total_warnings > 0 {
62 output.push('\n');
63
64 let mut parts = Vec::new();
65 if total_errors > 0 {
66 parts.push(format!(
67 "{} {}",
68 total_errors,
69 if total_errors == 1 { "error" } else { "errors" }
70 ));
71 }
72 if total_warnings > 0 {
73 parts.push(format!(
74 "{} {}",
75 total_warnings,
76 if total_warnings == 1 {
77 "warning"
78 } else {
79 "warnings"
80 }
81 ));
82 }
83
84 output.push_str(&format!(
85 " {} problem{}\n",
86 parts.join(" and "),
87 if total_errors + total_warnings == 1 {
88 ""
89 } else {
90 "s"
91 }
92 ));
93
94 if total_fixable > 0 {
95 output.push_str(&format!(
96 " {} {} potentially fixable with --fix\n",
97 total_fixable,
98 if total_fixable == 1 { "is" } else { "are" }
99 ));
100 }
101 }
102
103 output
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::analyzer::dclint::types::{CheckFailure, RuleCategory};
110
111 #[test]
112 fn test_stylish_format() {
113 let mut result = LintResult::new("docker-compose.yml");
114 result.failures.push(CheckFailure::new(
115 "DCL001",
116 "no-build-and-image",
117 Severity::Error,
118 RuleCategory::BestPractice,
119 "Service has both build and image",
120 5,
121 1,
122 ));
123 result.error_count = 1;
124
125 let output = format(&[result]);
126 assert!(output.contains("docker-compose.yml"));
127 assert!(output.contains("5:1"));
128 assert!(output.contains("error"));
129 assert!(output.contains("DCL001"));
130 assert!(output.contains("1 error"));
131 }
132
133 #[test]
134 fn test_stylish_format_multiple() {
135 let mut result = LintResult::new("docker-compose.yml");
136 result.failures.push(CheckFailure::new(
137 "DCL001",
138 "test",
139 Severity::Error,
140 RuleCategory::BestPractice,
141 "Error 1",
142 5,
143 1,
144 ));
145 result.failures.push(
146 CheckFailure::new(
147 "DCL006",
148 "test",
149 Severity::Warning,
150 RuleCategory::Style,
151 "Warning 1",
152 1,
153 1,
154 )
155 .with_fixable(true),
156 );
157 result.error_count = 1;
158 result.warning_count = 1;
159
160 let output = format(&[result]);
161 assert!(output.contains("1 error"));
162 assert!(output.contains("1 warning"));
163 assert!(output.contains("fixable"));
164 }
165
166 #[test]
167 fn test_stylish_format_empty() {
168 let result = LintResult::new("docker-compose.yml");
169 let output = format(&[result]);
170 assert!(output.is_empty());
171 }
172}