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