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