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