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 FeatureFlag,
56 Complexity,
58 StaleSuppression,
60}
61
62impl IssueKind {
63 #[must_use]
65 pub fn parse(s: &str) -> Option<Self> {
66 match s {
67 "unused-file" => Some(Self::UnusedFile),
68 "unused-export" => Some(Self::UnusedExport),
69 "unused-type" => Some(Self::UnusedType),
70 "unused-dependency" => Some(Self::UnusedDependency),
71 "unused-dev-dependency" => Some(Self::UnusedDevDependency),
72 "unused-enum-member" => Some(Self::UnusedEnumMember),
73 "unused-class-member" => Some(Self::UnusedClassMember),
74 "unresolved-import" => Some(Self::UnresolvedImport),
75 "unlisted-dependency" => Some(Self::UnlistedDependency),
76 "duplicate-export" => Some(Self::DuplicateExport),
77 "code-duplication" => Some(Self::CodeDuplication),
78 "circular-dependency" => Some(Self::CircularDependency),
79 "type-only-dependency" => Some(Self::TypeOnlyDependency),
80 "test-only-dependency" => Some(Self::TestOnlyDependency),
81 "boundary-violation" => Some(Self::BoundaryViolation),
82 "coverage-gaps" => Some(Self::CoverageGaps),
83 "feature-flag" => Some(Self::FeatureFlag),
84 "complexity" => Some(Self::Complexity),
85 "stale-suppression" => Some(Self::StaleSuppression),
86 _ => None,
87 }
88 }
89
90 #[must_use]
92 pub const fn to_discriminant(self) -> u8 {
93 match self {
94 Self::UnusedFile => 1,
95 Self::UnusedExport => 2,
96 Self::UnusedType => 3,
97 Self::UnusedDependency => 4,
98 Self::UnusedDevDependency => 5,
99 Self::UnusedEnumMember => 6,
100 Self::UnusedClassMember => 7,
101 Self::UnresolvedImport => 8,
102 Self::UnlistedDependency => 9,
103 Self::DuplicateExport => 10,
104 Self::CodeDuplication => 11,
105 Self::CircularDependency => 12,
106 Self::TypeOnlyDependency => 13,
107 Self::TestOnlyDependency => 14,
108 Self::BoundaryViolation => 15,
109 Self::CoverageGaps => 16,
110 Self::FeatureFlag => 17,
111 Self::Complexity => 18,
112 Self::StaleSuppression => 19,
113 }
114 }
115
116 #[must_use]
118 pub const fn from_discriminant(d: u8) -> Option<Self> {
119 match d {
120 1 => Some(Self::UnusedFile),
121 2 => Some(Self::UnusedExport),
122 3 => Some(Self::UnusedType),
123 4 => Some(Self::UnusedDependency),
124 5 => Some(Self::UnusedDevDependency),
125 6 => Some(Self::UnusedEnumMember),
126 7 => Some(Self::UnusedClassMember),
127 8 => Some(Self::UnresolvedImport),
128 9 => Some(Self::UnlistedDependency),
129 10 => Some(Self::DuplicateExport),
130 11 => Some(Self::CodeDuplication),
131 12 => Some(Self::CircularDependency),
132 13 => Some(Self::TypeOnlyDependency),
133 14 => Some(Self::TestOnlyDependency),
134 15 => Some(Self::BoundaryViolation),
135 16 => Some(Self::CoverageGaps),
136 17 => Some(Self::FeatureFlag),
137 18 => Some(Self::Complexity),
138 19 => Some(Self::StaleSuppression),
139 _ => None,
140 }
141 }
142}
143
144#[derive(Debug, Clone)]
164pub struct Suppression {
165 pub line: u32,
167 pub comment_line: u32,
171 pub kind: Option<IssueKind>,
173}
174
175const _: () = assert!(std::mem::size_of::<Suppression>() == 12);
178const _: () = assert!(std::mem::size_of::<IssueKind>() == 1);
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn issue_kind_from_str_all_variants() {
186 assert_eq!(IssueKind::parse("unused-file"), Some(IssueKind::UnusedFile));
187 assert_eq!(
188 IssueKind::parse("unused-export"),
189 Some(IssueKind::UnusedExport)
190 );
191 assert_eq!(IssueKind::parse("unused-type"), Some(IssueKind::UnusedType));
192 assert_eq!(
193 IssueKind::parse("unused-dependency"),
194 Some(IssueKind::UnusedDependency)
195 );
196 assert_eq!(
197 IssueKind::parse("unused-dev-dependency"),
198 Some(IssueKind::UnusedDevDependency)
199 );
200 assert_eq!(
201 IssueKind::parse("unused-enum-member"),
202 Some(IssueKind::UnusedEnumMember)
203 );
204 assert_eq!(
205 IssueKind::parse("unused-class-member"),
206 Some(IssueKind::UnusedClassMember)
207 );
208 assert_eq!(
209 IssueKind::parse("unresolved-import"),
210 Some(IssueKind::UnresolvedImport)
211 );
212 assert_eq!(
213 IssueKind::parse("unlisted-dependency"),
214 Some(IssueKind::UnlistedDependency)
215 );
216 assert_eq!(
217 IssueKind::parse("duplicate-export"),
218 Some(IssueKind::DuplicateExport)
219 );
220 assert_eq!(
221 IssueKind::parse("code-duplication"),
222 Some(IssueKind::CodeDuplication)
223 );
224 assert_eq!(
225 IssueKind::parse("circular-dependency"),
226 Some(IssueKind::CircularDependency)
227 );
228 assert_eq!(
229 IssueKind::parse("type-only-dependency"),
230 Some(IssueKind::TypeOnlyDependency)
231 );
232 assert_eq!(
233 IssueKind::parse("test-only-dependency"),
234 Some(IssueKind::TestOnlyDependency)
235 );
236 assert_eq!(
237 IssueKind::parse("boundary-violation"),
238 Some(IssueKind::BoundaryViolation)
239 );
240 assert_eq!(
241 IssueKind::parse("coverage-gaps"),
242 Some(IssueKind::CoverageGaps)
243 );
244 assert_eq!(
245 IssueKind::parse("feature-flag"),
246 Some(IssueKind::FeatureFlag)
247 );
248 assert_eq!(IssueKind::parse("complexity"), Some(IssueKind::Complexity));
249 assert_eq!(
250 IssueKind::parse("stale-suppression"),
251 Some(IssueKind::StaleSuppression)
252 );
253 }
254
255 #[test]
256 fn issue_kind_from_str_unknown() {
257 assert_eq!(IssueKind::parse("foo"), None);
258 assert_eq!(IssueKind::parse(""), None);
259 }
260
261 #[test]
262 fn issue_kind_from_str_near_misses() {
263 assert_eq!(IssueKind::parse("Unused-File"), None);
265 assert_eq!(IssueKind::parse("UNUSED-EXPORT"), None);
266 assert_eq!(IssueKind::parse("unused_file"), None);
268 assert_eq!(IssueKind::parse("unused-files"), None);
269 }
270
271 #[test]
272 fn discriminant_out_of_range() {
273 assert_eq!(IssueKind::from_discriminant(0), None);
274 assert_eq!(IssueKind::from_discriminant(20), None);
275 assert_eq!(IssueKind::from_discriminant(u8::MAX), None);
276 }
277
278 #[test]
279 fn discriminant_roundtrip() {
280 for kind in [
281 IssueKind::UnusedFile,
282 IssueKind::UnusedExport,
283 IssueKind::UnusedType,
284 IssueKind::UnusedDependency,
285 IssueKind::UnusedDevDependency,
286 IssueKind::UnusedEnumMember,
287 IssueKind::UnusedClassMember,
288 IssueKind::UnresolvedImport,
289 IssueKind::UnlistedDependency,
290 IssueKind::DuplicateExport,
291 IssueKind::CodeDuplication,
292 IssueKind::CircularDependency,
293 IssueKind::TypeOnlyDependency,
294 IssueKind::TestOnlyDependency,
295 IssueKind::BoundaryViolation,
296 IssueKind::CoverageGaps,
297 IssueKind::FeatureFlag,
298 IssueKind::Complexity,
299 IssueKind::StaleSuppression,
300 ] {
301 assert_eq!(
302 IssueKind::from_discriminant(kind.to_discriminant()),
303 Some(kind)
304 );
305 }
306 assert_eq!(IssueKind::from_discriminant(0), None);
307 assert_eq!(IssueKind::from_discriminant(20), None);
308 }
309
310 #[test]
313 fn discriminant_values_are_unique() {
314 let all_kinds = [
315 IssueKind::UnusedFile,
316 IssueKind::UnusedExport,
317 IssueKind::UnusedType,
318 IssueKind::UnusedDependency,
319 IssueKind::UnusedDevDependency,
320 IssueKind::UnusedEnumMember,
321 IssueKind::UnusedClassMember,
322 IssueKind::UnresolvedImport,
323 IssueKind::UnlistedDependency,
324 IssueKind::DuplicateExport,
325 IssueKind::CodeDuplication,
326 IssueKind::CircularDependency,
327 IssueKind::TypeOnlyDependency,
328 IssueKind::TestOnlyDependency,
329 IssueKind::BoundaryViolation,
330 IssueKind::CoverageGaps,
331 IssueKind::FeatureFlag,
332 IssueKind::Complexity,
333 IssueKind::StaleSuppression,
334 ];
335 let discriminants: Vec<u8> = all_kinds.iter().map(|k| k.to_discriminant()).collect();
336 let mut sorted = discriminants.clone();
337 sorted.sort_unstable();
338 sorted.dedup();
339 assert_eq!(
340 discriminants.len(),
341 sorted.len(),
342 "discriminant values must be unique"
343 );
344 }
345
346 #[test]
349 fn discriminant_starts_at_one() {
350 assert_eq!(IssueKind::UnusedFile.to_discriminant(), 1);
351 }
352
353 #[test]
356 fn suppression_line_zero_is_file_wide() {
357 let s = Suppression {
358 line: 0,
359 comment_line: 1,
360 kind: None,
361 };
362 assert_eq!(s.line, 0);
363 assert!(s.kind.is_none());
364 }
365
366 #[test]
367 fn suppression_with_specific_kind_and_line() {
368 let s = Suppression {
369 line: 42,
370 comment_line: 41,
371 kind: Some(IssueKind::UnusedExport),
372 };
373 assert_eq!(s.line, 42);
374 assert_eq!(s.comment_line, 41);
375 assert_eq!(s.kind, Some(IssueKind::UnusedExport));
376 }
377}