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