big_code_analysis/output/
warning_line.rs1#![allow(clippy::doc_markdown)]
26
27use std::io::{self, Write};
28
29use crate::output::offenders::{OffenderRecord, TOOL_ID, warn_non_utf8_path};
30
31const DEFAULT_COL: u32 = 1;
36
37pub fn write_clang_warning<W: Write>(
49 offenders: &[OffenderRecord],
50 mut writer: W,
51) -> io::Result<()> {
52 for record in offenders {
53 let Some(path) = warn_non_utf8_path("clang-warning", &record.path) else {
54 continue;
55 };
56 let line = record.start_line.max(1);
57 let col = record.start_col.unwrap_or(DEFAULT_COL).max(1);
58 writeln!(
59 writer,
60 "{path}:{line}:{col}: {severity}: {message} [{prefix}-{metric}]",
61 severity = record.severity.as_str(),
62 message = record.default_message(),
63 prefix = TOOL_ID,
64 metric = record.metric,
65 )?;
66 }
67 Ok(())
68}
69
70pub fn write_msvc_warning<W: Write>(offenders: &[OffenderRecord], mut writer: W) -> io::Result<()> {
85 for record in offenders {
86 let Some(raw_path) = warn_non_utf8_path("msvc-warning", &record.path) else {
87 continue;
88 };
89 let path = msvc_path(raw_path);
90 let line = record.start_line.max(1);
91 let col = record.start_col.unwrap_or(DEFAULT_COL).max(1);
92 writeln!(
93 writer,
94 "{path}({line},{col}): {severity} : {message}",
95 severity = record.severity.as_str(),
96 message = record.default_message(),
97 )?;
98 }
99 Ok(())
100}
101
102#[cfg(windows)]
108fn msvc_path(raw: &str) -> std::borrow::Cow<'_, str> {
109 if raw.contains('/') {
110 std::borrow::Cow::Owned(raw.replace('/', "\\"))
111 } else {
112 std::borrow::Cow::Borrowed(raw)
113 }
114}
115
116#[cfg(not(windows))]
117fn msvc_path(raw: &str) -> &str {
118 raw
119}
120
121#[cfg(test)]
122#[allow(
123 clippy::float_cmp,
124 clippy::cast_precision_loss,
125 clippy::cast_possible_truncation,
126 clippy::cast_sign_loss,
127 clippy::similar_names,
128 clippy::doc_markdown,
129 clippy::needless_raw_string_hashes,
130 clippy::too_many_lines
131)]
132mod tests {
133 use super::*;
134 use crate::output::offenders::Severity;
135 use std::path::PathBuf;
136
137 fn rec(path: &str, metric: &str, value: f64, limit: f64) -> OffenderRecord {
138 OffenderRecord {
139 path: PathBuf::from(path),
140 function: Some("f".into()),
141 start_line: 42,
142 end_line: 50,
143 start_col: Some(5),
144 metric: metric.into(),
145 value,
146 limit,
147 severity: Severity::Warning,
148 }
149 }
150
151 fn render_clang(offenders: &[OffenderRecord]) -> String {
152 let mut buf = Vec::new();
153 write_clang_warning(offenders, &mut buf).expect("writing to Vec is infallible");
154 String::from_utf8(buf).expect("output is UTF-8")
155 }
156
157 fn render_msvc(offenders: &[OffenderRecord]) -> String {
158 let mut buf = Vec::new();
159 write_msvc_warning(offenders, &mut buf).expect("writing to Vec is infallible");
160 String::from_utf8(buf).expect("output is UTF-8")
161 }
162
163 #[test]
164 fn clang_empty_writes_nothing() {
165 assert_eq!(render_clang(&[]), "");
166 }
167
168 #[test]
169 fn msvc_empty_writes_nothing() {
170 assert_eq!(render_msvc(&[]), "");
171 }
172
173 #[test]
174 fn clang_single_offender() {
175 let out = render_clang(&[rec("src/foo.rs", "cyclomatic", 17.0, 15.0)]);
176 assert_eq!(
177 out,
178 "src/foo.rs:42:5: warning: cyclomatic 17 exceeds limit 15 [big-code-analysis-cyclomatic]\n"
179 );
180 }
181
182 #[test]
183 fn msvc_single_offender() {
184 let out = render_msvc(&[rec("src/foo.rs", "cyclomatic", 17.0, 15.0)]);
185 #[cfg(not(windows))]
187 assert_eq!(
188 out,
189 "src/foo.rs(42,5): warning : cyclomatic 17 exceeds limit 15\n"
190 );
191 #[cfg(windows)]
192 assert_eq!(
193 out,
194 "src\\foo.rs(42,5): warning : cyclomatic 17 exceeds limit 15\n"
195 );
196 }
197
198 #[test]
199 fn clang_missing_column_defaults_to_one() {
200 let mut r = rec("a.rs", "cognitive", 30.0, 15.0);
201 r.start_col = None;
202 let out = render_clang(&[r]);
203 assert!(out.starts_with("a.rs:42:1: warning: "), "{out}");
204 }
205
206 #[test]
207 fn msvc_missing_column_defaults_to_one() {
208 let mut r = rec("a.rs", "cognitive", 30.0, 15.0);
209 r.start_col = None;
210 let out = render_msvc(&[r]);
211 assert!(out.starts_with("a.rs(42,1): warning : "), "{out}");
212 }
213
214 #[test]
215 fn clang_error_severity_renders_error_token() {
216 let mut r = rec("a.rs", "cyclomatic", 99.0, 15.0);
217 r.severity = Severity::Error;
218 let out = render_clang(&[r]);
219 assert!(out.contains(": error: "), "{out}");
220 }
221
222 #[test]
223 fn msvc_error_severity_renders_error_token() {
224 let mut r = rec("a.rs", "cyclomatic", 99.0, 15.0);
225 r.severity = Severity::Error;
226 let out = render_msvc(&[r]);
227 assert!(out.contains("): error : "), "{out}");
229 }
230
231 #[test]
232 fn clang_integer_value_has_no_decimal_point() {
233 let out = render_clang(&[rec("a.rs", "cyclomatic", 17.0, 15.0)]);
234 assert!(out.contains("cyclomatic 17 exceeds limit 15"), "{out}");
235 assert!(!out.contains("17.0"), "{out}");
236 assert!(!out.contains("15.0"), "{out}");
237 }
238
239 #[test]
240 fn clang_fractional_value_renders_decimals() {
241 let out = render_clang(&[rec("a.rs", "halstead.volume", 12.5, 10.0)]);
242 assert!(
243 out.contains("halstead.volume 12.5 exceeds limit 10"),
244 "{out}"
245 );
246 }
247
248 #[test]
249 fn clang_zero_start_line_clamps_to_one() {
250 let mut r = rec("a.rs", "cyclomatic", 17.0, 15.0);
251 r.start_line = 0;
252 let out = render_clang(&[r]);
253 assert!(out.starts_with("a.rs:1:5: "), "{out}");
254 }
255
256 #[test]
257 fn msvc_zero_start_line_clamps_to_one() {
258 let mut r = rec("a.rs", "cyclomatic", 17.0, 15.0);
259 r.start_line = 0;
260 let out = render_msvc(&[r]);
261 assert!(out.starts_with("a.rs(1,5): "), "{out}");
262 }
263
264 #[test]
265 fn clang_multi_offender_one_line_each() {
266 let offenders = vec![
267 rec("src/alpha.rs", "cyclomatic", 17.0, 15.0),
268 rec("src/alpha.rs", "loc.lloc", 250.0, 100.0),
269 rec("src/zeta.rs", "cognitive", 30.0, 15.0),
270 ];
271 let out = render_clang(&offenders);
272 assert_eq!(out.lines().count(), 3);
273 assert!(out.ends_with('\n'));
274 }
275
276 #[test]
277 fn clang_function_name_does_not_appear_in_line() {
278 let r = rec("a.rs", "cyclomatic", 17.0, 15.0);
281 let out = render_clang(&[r]);
283 assert_eq!(
286 out,
287 "a.rs:42:5: warning: cyclomatic 17 exceeds limit 15 [big-code-analysis-cyclomatic]\n"
288 );
289 }
290
291 #[test]
292 fn clang_empty_snapshot() {
293 insta::assert_snapshot!("clang_warning_empty", render_clang(&[]));
294 }
295
296 #[test]
297 fn clang_multi_snapshot() {
298 let mut err = rec("src/zeta.rs", "cognitive", 30.0, 15.0);
299 err.severity = Severity::Error;
300 err.start_col = None;
301 err.function = None;
302 let offenders = vec![
303 rec("src/alpha.rs", "cyclomatic", 17.0, 15.0),
304 rec("src/alpha.rs", "loc.lloc", 250.0, 100.0),
305 err,
306 ];
307 insta::assert_snapshot!("clang_warning_multi", render_clang(&offenders));
308 }
309
310 #[test]
311 fn msvc_empty_snapshot() {
312 insta::assert_snapshot!("msvc_warning_empty", render_msvc(&[]));
313 }
314
315 #[cfg(not(windows))]
320 #[test]
321 fn msvc_multi_snapshot() {
322 let mut err = rec("src/zeta.rs", "cognitive", 30.0, 15.0);
323 err.severity = Severity::Error;
324 err.start_col = None;
325 err.function = None;
326 let offenders = vec![
327 rec("src/alpha.rs", "cyclomatic", 17.0, 15.0),
328 rec("src/alpha.rs", "loc.lloc", 250.0, 100.0),
329 err,
330 ];
331 insta::assert_snapshot!("msvc_warning_multi", render_msvc(&offenders));
332 }
333
334 #[cfg(windows)]
335 #[test]
336 fn msvc_path_uses_backslashes_on_windows() {
337 let out = render_msvc(&[rec("src/foo/bar.rs", "cyclomatic", 17.0, 15.0)]);
338 assert!(out.starts_with("src\\foo\\bar.rs("), "{out}");
339 }
340
341 #[cfg(not(windows))]
342 #[test]
343 fn msvc_path_keeps_forward_slashes_off_windows() {
344 let out = render_msvc(&[rec("src/foo/bar.rs", "cyclomatic", 17.0, 15.0)]);
345 assert!(out.starts_with("src/foo/bar.rs("), "{out}");
346 }
347}