1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum IssueKind {
22 UnusedFile,
24 UnusedExport,
26 UnusedType,
28 UnusedDependency,
30 UnusedDevDependency,
32 UnusedEnumMember,
34 UnusedClassMember,
36 UnresolvedImport,
38 UnlistedDependency,
40 DuplicateExport,
42 CodeDuplication,
44 CircularDependency,
46 TypeOnlyDependency,
48 TestOnlyDependency,
50 BoundaryViolation,
52 CoverageGaps,
54}
55
56impl IssueKind {
57 #[must_use]
59 pub fn parse(s: &str) -> Option<Self> {
60 match s {
61 "unused-file" => Some(Self::UnusedFile),
62 "unused-export" => Some(Self::UnusedExport),
63 "unused-type" => Some(Self::UnusedType),
64 "unused-dependency" => Some(Self::UnusedDependency),
65 "unused-dev-dependency" => Some(Self::UnusedDevDependency),
66 "unused-enum-member" => Some(Self::UnusedEnumMember),
67 "unused-class-member" => Some(Self::UnusedClassMember),
68 "unresolved-import" => Some(Self::UnresolvedImport),
69 "unlisted-dependency" => Some(Self::UnlistedDependency),
70 "duplicate-export" => Some(Self::DuplicateExport),
71 "code-duplication" => Some(Self::CodeDuplication),
72 "circular-dependency" => Some(Self::CircularDependency),
73 "type-only-dependency" => Some(Self::TypeOnlyDependency),
74 "test-only-dependency" => Some(Self::TestOnlyDependency),
75 "boundary-violation" => Some(Self::BoundaryViolation),
76 "coverage-gaps" => Some(Self::CoverageGaps),
77 _ => None,
78 }
79 }
80
81 #[must_use]
83 pub const fn to_discriminant(self) -> u8 {
84 match self {
85 Self::UnusedFile => 1,
86 Self::UnusedExport => 2,
87 Self::UnusedType => 3,
88 Self::UnusedDependency => 4,
89 Self::UnusedDevDependency => 5,
90 Self::UnusedEnumMember => 6,
91 Self::UnusedClassMember => 7,
92 Self::UnresolvedImport => 8,
93 Self::UnlistedDependency => 9,
94 Self::DuplicateExport => 10,
95 Self::CodeDuplication => 11,
96 Self::CircularDependency => 12,
97 Self::TypeOnlyDependency => 13,
98 Self::TestOnlyDependency => 14,
99 Self::BoundaryViolation => 15,
100 Self::CoverageGaps => 16,
101 }
102 }
103
104 #[must_use]
106 pub const fn from_discriminant(d: u8) -> Option<Self> {
107 match d {
108 1 => Some(Self::UnusedFile),
109 2 => Some(Self::UnusedExport),
110 3 => Some(Self::UnusedType),
111 4 => Some(Self::UnusedDependency),
112 5 => Some(Self::UnusedDevDependency),
113 6 => Some(Self::UnusedEnumMember),
114 7 => Some(Self::UnusedClassMember),
115 8 => Some(Self::UnresolvedImport),
116 9 => Some(Self::UnlistedDependency),
117 10 => Some(Self::DuplicateExport),
118 11 => Some(Self::CodeDuplication),
119 12 => Some(Self::CircularDependency),
120 13 => Some(Self::TypeOnlyDependency),
121 14 => Some(Self::TestOnlyDependency),
122 15 => Some(Self::BoundaryViolation),
123 16 => Some(Self::CoverageGaps),
124 _ => None,
125 }
126 }
127}
128
129#[derive(Debug, Clone)]
148pub struct Suppression {
149 pub line: u32,
151 pub kind: Option<IssueKind>,
153}
154
155const _: () = assert!(std::mem::size_of::<Suppression>() == 8);
158const _: () = assert!(std::mem::size_of::<IssueKind>() == 1);
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn issue_kind_from_str_all_variants() {
166 assert_eq!(IssueKind::parse("unused-file"), Some(IssueKind::UnusedFile));
167 assert_eq!(
168 IssueKind::parse("unused-export"),
169 Some(IssueKind::UnusedExport)
170 );
171 assert_eq!(IssueKind::parse("unused-type"), Some(IssueKind::UnusedType));
172 assert_eq!(
173 IssueKind::parse("unused-dependency"),
174 Some(IssueKind::UnusedDependency)
175 );
176 assert_eq!(
177 IssueKind::parse("unused-dev-dependency"),
178 Some(IssueKind::UnusedDevDependency)
179 );
180 assert_eq!(
181 IssueKind::parse("unused-enum-member"),
182 Some(IssueKind::UnusedEnumMember)
183 );
184 assert_eq!(
185 IssueKind::parse("unused-class-member"),
186 Some(IssueKind::UnusedClassMember)
187 );
188 assert_eq!(
189 IssueKind::parse("unresolved-import"),
190 Some(IssueKind::UnresolvedImport)
191 );
192 assert_eq!(
193 IssueKind::parse("unlisted-dependency"),
194 Some(IssueKind::UnlistedDependency)
195 );
196 assert_eq!(
197 IssueKind::parse("duplicate-export"),
198 Some(IssueKind::DuplicateExport)
199 );
200 assert_eq!(
201 IssueKind::parse("code-duplication"),
202 Some(IssueKind::CodeDuplication)
203 );
204 assert_eq!(
205 IssueKind::parse("circular-dependency"),
206 Some(IssueKind::CircularDependency)
207 );
208 assert_eq!(
209 IssueKind::parse("type-only-dependency"),
210 Some(IssueKind::TypeOnlyDependency)
211 );
212 assert_eq!(
213 IssueKind::parse("test-only-dependency"),
214 Some(IssueKind::TestOnlyDependency)
215 );
216 assert_eq!(
217 IssueKind::parse("boundary-violation"),
218 Some(IssueKind::BoundaryViolation)
219 );
220 assert_eq!(
221 IssueKind::parse("coverage-gaps"),
222 Some(IssueKind::CoverageGaps)
223 );
224 }
225
226 #[test]
227 fn issue_kind_from_str_unknown() {
228 assert_eq!(IssueKind::parse("foo"), None);
229 assert_eq!(IssueKind::parse(""), None);
230 }
231
232 #[test]
233 fn issue_kind_from_str_near_misses() {
234 assert_eq!(IssueKind::parse("Unused-File"), None);
236 assert_eq!(IssueKind::parse("UNUSED-EXPORT"), None);
237 assert_eq!(IssueKind::parse("unused_file"), None);
239 assert_eq!(IssueKind::parse("unused-files"), None);
240 }
241
242 #[test]
243 fn discriminant_out_of_range() {
244 assert_eq!(IssueKind::from_discriminant(0), None);
245 assert_eq!(IssueKind::from_discriminant(17), None);
246 assert_eq!(IssueKind::from_discriminant(u8::MAX), None);
247 }
248
249 #[test]
250 fn discriminant_roundtrip() {
251 for kind in [
252 IssueKind::UnusedFile,
253 IssueKind::UnusedExport,
254 IssueKind::UnusedType,
255 IssueKind::UnusedDependency,
256 IssueKind::UnusedDevDependency,
257 IssueKind::UnusedEnumMember,
258 IssueKind::UnusedClassMember,
259 IssueKind::UnresolvedImport,
260 IssueKind::UnlistedDependency,
261 IssueKind::DuplicateExport,
262 IssueKind::CodeDuplication,
263 IssueKind::CircularDependency,
264 IssueKind::TypeOnlyDependency,
265 IssueKind::TestOnlyDependency,
266 IssueKind::BoundaryViolation,
267 IssueKind::CoverageGaps,
268 ] {
269 assert_eq!(
270 IssueKind::from_discriminant(kind.to_discriminant()),
271 Some(kind)
272 );
273 }
274 assert_eq!(IssueKind::from_discriminant(0), None);
275 assert_eq!(IssueKind::from_discriminant(17), None);
276 }
277
278 #[test]
281 fn discriminant_values_are_unique() {
282 let all_kinds = [
283 IssueKind::UnusedFile,
284 IssueKind::UnusedExport,
285 IssueKind::UnusedType,
286 IssueKind::UnusedDependency,
287 IssueKind::UnusedDevDependency,
288 IssueKind::UnusedEnumMember,
289 IssueKind::UnusedClassMember,
290 IssueKind::UnresolvedImport,
291 IssueKind::UnlistedDependency,
292 IssueKind::DuplicateExport,
293 IssueKind::CodeDuplication,
294 IssueKind::CircularDependency,
295 IssueKind::TypeOnlyDependency,
296 IssueKind::TestOnlyDependency,
297 IssueKind::BoundaryViolation,
298 IssueKind::CoverageGaps,
299 ];
300 let discriminants: Vec<u8> = all_kinds.iter().map(|k| k.to_discriminant()).collect();
301 let mut sorted = discriminants.clone();
302 sorted.sort_unstable();
303 sorted.dedup();
304 assert_eq!(
305 discriminants.len(),
306 sorted.len(),
307 "discriminant values must be unique"
308 );
309 }
310
311 #[test]
314 fn discriminant_starts_at_one() {
315 assert_eq!(IssueKind::UnusedFile.to_discriminant(), 1);
316 }
317
318 #[test]
321 fn suppression_line_zero_is_file_wide() {
322 let s = Suppression {
323 line: 0,
324 kind: None,
325 };
326 assert_eq!(s.line, 0);
327 assert!(s.kind.is_none());
328 }
329
330 #[test]
331 fn suppression_with_specific_kind_and_line() {
332 let s = Suppression {
333 line: 42,
334 kind: Some(IssueKind::UnusedExport),
335 };
336 assert_eq!(s.line, 42);
337 assert_eq!(s.kind, Some(IssueKind::UnusedExport));
338 }
339}