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