big_code_analysis/output/
offenders.rs1#![allow(clippy::doc_markdown)]
10
11use std::path::Path;
12use std::path::PathBuf;
13
14use serde::{Deserialize, Serialize};
15
16use crate::output::numfmt::MessageMetric;
17
18pub const TOOL_ID: &str = "big-code-analysis";
23
24pub(crate) fn warn_non_utf8_path<'a>(format: &str, path: &'a Path) -> Option<&'a str> {
30 if let Some(s) = path.to_str() {
31 Some(s)
32 } else {
33 eprintln!(
34 "Warning: skipping non-UTF-8 path in {format} output: {}",
35 path.display()
36 );
37 None
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
46#[serde(rename_all = "lowercase")]
47pub enum Severity {
48 #[default]
50 Warning,
51 Error,
53}
54
55impl Severity {
56 #[must_use]
58 pub fn as_str(self) -> &'static str {
59 match self {
60 Self::Warning => "warning",
61 Self::Error => "error",
62 }
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct OffenderRecord {
73 pub path: PathBuf,
75 pub function: Option<String>,
77 pub start_line: u32,
79 pub end_line: u32,
81 pub start_col: Option<u32>,
83 pub metric: String,
86 pub value: f64,
88 pub limit: f64,
90 pub severity: Severity,
92}
93
94impl OffenderRecord {
95 #[must_use]
103 pub fn default_message(&self) -> String {
104 format!(
105 "{} {} exceeds limit {}",
106 self.metric,
107 MessageMetric(self.value),
108 MessageMetric(self.limit),
109 )
110 }
111}
112
113#[cfg(test)]
114#[allow(
115 clippy::float_cmp,
116 clippy::cast_precision_loss,
117 clippy::cast_possible_truncation,
118 clippy::cast_sign_loss,
119 clippy::similar_names,
120 clippy::doc_markdown,
121 clippy::needless_raw_string_hashes,
122 clippy::too_many_lines
123)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn severity_default_is_warning() {
129 assert_eq!(Severity::default(), Severity::Warning);
130 }
131
132 #[test]
133 fn severity_as_str_lowercase() {
134 assert_eq!(Severity::Warning.as_str(), "warning");
135 assert_eq!(Severity::Error.as_str(), "error");
136 }
137
138 #[test]
139 fn default_message_renders_integral_value() {
140 let r = OffenderRecord {
141 path: PathBuf::from("a.rs"),
142 function: Some("f".into()),
143 start_line: 1,
144 end_line: 2,
145 start_col: None,
146 metric: "cyclomatic".into(),
147 value: 17.0,
148 limit: 15.0,
149 severity: Severity::Warning,
150 };
151 assert_eq!(r.default_message(), "cyclomatic 17 exceeds limit 15");
152 }
153
154 #[test]
155 fn default_message_renders_fractional_value() {
156 let r = OffenderRecord {
157 path: PathBuf::from("a.rs"),
158 function: None,
159 start_line: 1,
160 end_line: 1,
161 start_col: None,
162 metric: "halstead.volume".into(),
163 value: 12.5,
164 limit: 10.0,
165 severity: Severity::Error,
166 };
167 assert_eq!(r.default_message(), "halstead.volume 12.5 exceeds limit 10");
168 }
169
170 #[test]
171 fn default_message_renders_non_finite_values() {
172 let mut r = OffenderRecord {
173 path: PathBuf::from("a.rs"),
174 function: None,
175 start_line: 1,
176 end_line: 1,
177 start_col: None,
178 metric: "halstead.volume".into(),
179 value: f64::NAN,
180 limit: 10.0,
181 severity: Severity::Warning,
182 };
183 assert_eq!(r.default_message(), "halstead.volume NaN exceeds limit 10");
184
185 r.value = f64::INFINITY;
186 assert_eq!(r.default_message(), "halstead.volume inf exceeds limit 10");
187
188 r.value = f64::NEG_INFINITY;
189 assert_eq!(r.default_message(), "halstead.volume -inf exceeds limit 10");
190 }
191}