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