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 PnpmCatalogEntry,
64 EmptyCatalogGroup,
66 UnresolvedCatalogReference,
69 UnusedDependencyOverride,
72 MisconfiguredDependencyOverride,
75}
76
77impl IssueKind {
78 #[must_use]
80 pub fn parse(s: &str) -> Option<Self> {
81 match s {
82 "unused-file" => Some(Self::UnusedFile),
83 "unused-export" => Some(Self::UnusedExport),
84 "unused-type" => Some(Self::UnusedType),
85 "private-type-leak" => Some(Self::PrivateTypeLeak),
86 "unused-dependency" => Some(Self::UnusedDependency),
87 "unused-dev-dependency" => Some(Self::UnusedDevDependency),
88 "unused-enum-member" => Some(Self::UnusedEnumMember),
89 "unused-class-member" => Some(Self::UnusedClassMember),
90 "unresolved-import" => Some(Self::UnresolvedImport),
91 "unlisted-dependency" => Some(Self::UnlistedDependency),
92 "duplicate-export" => Some(Self::DuplicateExport),
93 "code-duplication" => Some(Self::CodeDuplication),
94 "circular-dependency" | "circular-dependencies" => Some(Self::CircularDependency),
95 "type-only-dependency" => Some(Self::TypeOnlyDependency),
96 "test-only-dependency" => Some(Self::TestOnlyDependency),
97 "boundary-violation" => Some(Self::BoundaryViolation),
98 "coverage-gaps" => Some(Self::CoverageGaps),
99 "feature-flag" => Some(Self::FeatureFlag),
100 "complexity" => Some(Self::Complexity),
101 "stale-suppression" => Some(Self::StaleSuppression),
102 "unused-catalog-entry" | "unused-catalog-entries" => Some(Self::PnpmCatalogEntry),
103 "empty-catalog-group" | "empty-catalog-groups" => Some(Self::EmptyCatalogGroup),
104 "unresolved-catalog-reference" | "unresolved-catalog-references" => {
105 Some(Self::UnresolvedCatalogReference)
106 }
107 "unused-dependency-override" | "unused-dependency-overrides" => {
108 Some(Self::UnusedDependencyOverride)
109 }
110 "misconfigured-dependency-override" | "misconfigured-dependency-overrides" => {
111 Some(Self::MisconfiguredDependencyOverride)
112 }
113 _ => None,
114 }
115 }
116
117 #[must_use]
119 pub const fn to_discriminant(self) -> u8 {
120 match self {
121 Self::UnusedFile => 1,
122 Self::UnusedExport => 2,
123 Self::UnusedType => 3,
124 Self::PrivateTypeLeak => 4,
125 Self::UnusedDependency => 5,
126 Self::UnusedDevDependency => 6,
127 Self::UnusedEnumMember => 7,
128 Self::UnusedClassMember => 8,
129 Self::UnresolvedImport => 9,
130 Self::UnlistedDependency => 10,
131 Self::DuplicateExport => 11,
132 Self::CodeDuplication => 12,
133 Self::CircularDependency => 13,
134 Self::TypeOnlyDependency => 14,
135 Self::TestOnlyDependency => 15,
136 Self::BoundaryViolation => 16,
137 Self::CoverageGaps => 17,
138 Self::FeatureFlag => 18,
139 Self::Complexity => 19,
140 Self::StaleSuppression => 20,
141 Self::PnpmCatalogEntry => 21,
142 Self::UnresolvedCatalogReference => 22,
143 Self::UnusedDependencyOverride => 23,
144 Self::MisconfiguredDependencyOverride => 24,
145 Self::EmptyCatalogGroup => 25,
146 }
147 }
148
149 #[must_use]
151 pub const fn from_discriminant(d: u8) -> Option<Self> {
152 match d {
153 1 => Some(Self::UnusedFile),
154 2 => Some(Self::UnusedExport),
155 3 => Some(Self::UnusedType),
156 4 => Some(Self::PrivateTypeLeak),
157 5 => Some(Self::UnusedDependency),
158 6 => Some(Self::UnusedDevDependency),
159 7 => Some(Self::UnusedEnumMember),
160 8 => Some(Self::UnusedClassMember),
161 9 => Some(Self::UnresolvedImport),
162 10 => Some(Self::UnlistedDependency),
163 11 => Some(Self::DuplicateExport),
164 12 => Some(Self::CodeDuplication),
165 13 => Some(Self::CircularDependency),
166 14 => Some(Self::TypeOnlyDependency),
167 15 => Some(Self::TestOnlyDependency),
168 16 => Some(Self::BoundaryViolation),
169 17 => Some(Self::CoverageGaps),
170 18 => Some(Self::FeatureFlag),
171 19 => Some(Self::Complexity),
172 20 => Some(Self::StaleSuppression),
173 21 => Some(Self::PnpmCatalogEntry),
174 22 => Some(Self::UnresolvedCatalogReference),
175 23 => Some(Self::UnusedDependencyOverride),
176 24 => Some(Self::MisconfiguredDependencyOverride),
177 25 => Some(Self::EmptyCatalogGroup),
178 _ => None,
179 }
180 }
181}
182
183#[derive(Debug, Clone)]
203pub struct Suppression {
204 pub line: u32,
206 pub comment_line: u32,
210 pub kind: Option<IssueKind>,
212}
213
214const _: () = assert!(std::mem::size_of::<Suppression>() == 12);
217const _: () = assert!(std::mem::size_of::<IssueKind>() == 1);
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn issue_kind_from_str_all_variants() {
225 assert_eq!(IssueKind::parse("unused-file"), Some(IssueKind::UnusedFile));
226 assert_eq!(
227 IssueKind::parse("unused-export"),
228 Some(IssueKind::UnusedExport)
229 );
230 assert_eq!(IssueKind::parse("unused-type"), Some(IssueKind::UnusedType));
231 assert_eq!(
232 IssueKind::parse("private-type-leak"),
233 Some(IssueKind::PrivateTypeLeak)
234 );
235 assert_eq!(
236 IssueKind::parse("unused-dependency"),
237 Some(IssueKind::UnusedDependency)
238 );
239 assert_eq!(
240 IssueKind::parse("unused-dev-dependency"),
241 Some(IssueKind::UnusedDevDependency)
242 );
243 assert_eq!(
244 IssueKind::parse("unused-enum-member"),
245 Some(IssueKind::UnusedEnumMember)
246 );
247 assert_eq!(
248 IssueKind::parse("unused-class-member"),
249 Some(IssueKind::UnusedClassMember)
250 );
251 assert_eq!(
252 IssueKind::parse("unresolved-import"),
253 Some(IssueKind::UnresolvedImport)
254 );
255 assert_eq!(
256 IssueKind::parse("unlisted-dependency"),
257 Some(IssueKind::UnlistedDependency)
258 );
259 assert_eq!(
260 IssueKind::parse("duplicate-export"),
261 Some(IssueKind::DuplicateExport)
262 );
263 assert_eq!(
264 IssueKind::parse("code-duplication"),
265 Some(IssueKind::CodeDuplication)
266 );
267 assert_eq!(
268 IssueKind::parse("circular-dependency"),
269 Some(IssueKind::CircularDependency)
270 );
271 assert_eq!(
272 IssueKind::parse("circular-dependencies"),
273 Some(IssueKind::CircularDependency)
274 );
275 assert_eq!(
276 IssueKind::parse("type-only-dependency"),
277 Some(IssueKind::TypeOnlyDependency)
278 );
279 assert_eq!(
280 IssueKind::parse("test-only-dependency"),
281 Some(IssueKind::TestOnlyDependency)
282 );
283 assert_eq!(
284 IssueKind::parse("boundary-violation"),
285 Some(IssueKind::BoundaryViolation)
286 );
287 assert_eq!(
288 IssueKind::parse("coverage-gaps"),
289 Some(IssueKind::CoverageGaps)
290 );
291 assert_eq!(
292 IssueKind::parse("feature-flag"),
293 Some(IssueKind::FeatureFlag)
294 );
295 assert_eq!(IssueKind::parse("complexity"), Some(IssueKind::Complexity));
296 assert_eq!(
297 IssueKind::parse("stale-suppression"),
298 Some(IssueKind::StaleSuppression)
299 );
300 assert_eq!(
301 IssueKind::parse("unused-catalog-entry"),
302 Some(IssueKind::PnpmCatalogEntry)
303 );
304 assert_eq!(
305 IssueKind::parse("unused-catalog-entries"),
306 Some(IssueKind::PnpmCatalogEntry)
307 );
308 assert_eq!(
309 IssueKind::parse("empty-catalog-group"),
310 Some(IssueKind::EmptyCatalogGroup)
311 );
312 assert_eq!(
313 IssueKind::parse("empty-catalog-groups"),
314 Some(IssueKind::EmptyCatalogGroup)
315 );
316 assert_eq!(
317 IssueKind::parse("unresolved-catalog-reference"),
318 Some(IssueKind::UnresolvedCatalogReference)
319 );
320 assert_eq!(
321 IssueKind::parse("unresolved-catalog-references"),
322 Some(IssueKind::UnresolvedCatalogReference)
323 );
324 assert_eq!(
325 IssueKind::parse("unused-dependency-override"),
326 Some(IssueKind::UnusedDependencyOverride)
327 );
328 assert_eq!(
329 IssueKind::parse("unused-dependency-overrides"),
330 Some(IssueKind::UnusedDependencyOverride)
331 );
332 assert_eq!(
333 IssueKind::parse("misconfigured-dependency-override"),
334 Some(IssueKind::MisconfiguredDependencyOverride)
335 );
336 assert_eq!(
337 IssueKind::parse("misconfigured-dependency-overrides"),
338 Some(IssueKind::MisconfiguredDependencyOverride)
339 );
340 }
341
342 #[test]
343 fn issue_kind_from_str_unknown() {
344 assert_eq!(IssueKind::parse("foo"), None);
345 assert_eq!(IssueKind::parse(""), None);
346 }
347
348 #[test]
349 fn issue_kind_from_str_near_misses() {
350 assert_eq!(IssueKind::parse("Unused-File"), None);
352 assert_eq!(IssueKind::parse("UNUSED-EXPORT"), None);
353 assert_eq!(IssueKind::parse("unused_file"), None);
355 assert_eq!(IssueKind::parse("unused-files"), None);
356 }
357
358 #[test]
359 fn discriminant_out_of_range() {
360 assert_eq!(IssueKind::from_discriminant(0), None);
361 assert_eq!(IssueKind::from_discriminant(26), None);
362 assert_eq!(IssueKind::from_discriminant(u8::MAX), None);
363 }
364
365 #[test]
366 fn discriminant_roundtrip() {
367 for kind in [
368 IssueKind::UnusedFile,
369 IssueKind::UnusedExport,
370 IssueKind::UnusedType,
371 IssueKind::PrivateTypeLeak,
372 IssueKind::UnusedDependency,
373 IssueKind::UnusedDevDependency,
374 IssueKind::UnusedEnumMember,
375 IssueKind::UnusedClassMember,
376 IssueKind::UnresolvedImport,
377 IssueKind::UnlistedDependency,
378 IssueKind::DuplicateExport,
379 IssueKind::CodeDuplication,
380 IssueKind::CircularDependency,
381 IssueKind::TypeOnlyDependency,
382 IssueKind::TestOnlyDependency,
383 IssueKind::BoundaryViolation,
384 IssueKind::CoverageGaps,
385 IssueKind::FeatureFlag,
386 IssueKind::Complexity,
387 IssueKind::StaleSuppression,
388 IssueKind::PnpmCatalogEntry,
389 IssueKind::EmptyCatalogGroup,
390 IssueKind::UnresolvedCatalogReference,
391 IssueKind::UnusedDependencyOverride,
392 IssueKind::MisconfiguredDependencyOverride,
393 ] {
394 assert_eq!(
395 IssueKind::from_discriminant(kind.to_discriminant()),
396 Some(kind)
397 );
398 }
399 assert_eq!(IssueKind::from_discriminant(0), None);
400 assert_eq!(IssueKind::from_discriminant(26), None);
401 }
402
403 #[test]
406 fn discriminant_values_are_unique() {
407 let all_kinds = [
408 IssueKind::UnusedFile,
409 IssueKind::UnusedExport,
410 IssueKind::UnusedType,
411 IssueKind::PrivateTypeLeak,
412 IssueKind::UnusedDependency,
413 IssueKind::UnusedDevDependency,
414 IssueKind::UnusedEnumMember,
415 IssueKind::UnusedClassMember,
416 IssueKind::UnresolvedImport,
417 IssueKind::UnlistedDependency,
418 IssueKind::DuplicateExport,
419 IssueKind::CodeDuplication,
420 IssueKind::CircularDependency,
421 IssueKind::TypeOnlyDependency,
422 IssueKind::TestOnlyDependency,
423 IssueKind::BoundaryViolation,
424 IssueKind::CoverageGaps,
425 IssueKind::FeatureFlag,
426 IssueKind::Complexity,
427 IssueKind::StaleSuppression,
428 IssueKind::PnpmCatalogEntry,
429 IssueKind::EmptyCatalogGroup,
430 IssueKind::UnresolvedCatalogReference,
431 IssueKind::UnusedDependencyOverride,
432 IssueKind::MisconfiguredDependencyOverride,
433 ];
434 let discriminants: Vec<u8> = all_kinds.iter().map(|k| k.to_discriminant()).collect();
435 let mut sorted = discriminants.clone();
436 sorted.sort_unstable();
437 sorted.dedup();
438 assert_eq!(
439 discriminants.len(),
440 sorted.len(),
441 "discriminant values must be unique"
442 );
443 }
444
445 #[test]
448 fn discriminant_starts_at_one() {
449 assert_eq!(IssueKind::UnusedFile.to_discriminant(), 1);
450 }
451
452 #[test]
455 fn suppression_line_zero_is_file_wide() {
456 let s = Suppression {
457 line: 0,
458 comment_line: 1,
459 kind: None,
460 };
461 assert_eq!(s.line, 0);
462 assert!(s.kind.is_none());
463 }
464
465 #[test]
466 fn suppression_with_specific_kind_and_line() {
467 let s = Suppression {
468 line: 42,
469 comment_line: 41,
470 kind: Some(IssueKind::UnusedExport),
471 };
472 assert_eq!(s.line, 42);
473 assert_eq!(s.comment_line, 41);
474 assert_eq!(s.kind, Some(IssueKind::UnusedExport));
475 }
476}