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