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