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