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