Skip to main content

fallow_types/
suppress.rs

1//! Inline suppression comment types and issue kind definitions.
2
3/// Issue kind for suppression matching.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum IssueKind {
6    /// An unused file.
7    UnusedFile,
8    /// An unused export.
9    UnusedExport,
10    /// An unused type export.
11    UnusedType,
12    /// An unused dependency.
13    UnusedDependency,
14    /// An unused dev dependency.
15    UnusedDevDependency,
16    /// An unused enum member.
17    UnusedEnumMember,
18    /// An unused class member.
19    UnusedClassMember,
20    /// An unresolved import.
21    UnresolvedImport,
22    /// An unlisted dependency.
23    UnlistedDependency,
24    /// A duplicate export name across modules.
25    DuplicateExport,
26    /// Code duplication.
27    CodeDuplication,
28    /// A circular dependency chain.
29    CircularDependency,
30}
31
32impl IssueKind {
33    /// Parse an issue kind from the string tokens used in CLI output and suppression comments.
34    pub fn parse(s: &str) -> Option<Self> {
35        match s {
36            "unused-file" => Some(Self::UnusedFile),
37            "unused-export" => Some(Self::UnusedExport),
38            "unused-type" => Some(Self::UnusedType),
39            "unused-dependency" => Some(Self::UnusedDependency),
40            "unused-dev-dependency" => Some(Self::UnusedDevDependency),
41            "unused-enum-member" => Some(Self::UnusedEnumMember),
42            "unused-class-member" => Some(Self::UnusedClassMember),
43            "unresolved-import" => Some(Self::UnresolvedImport),
44            "unlisted-dependency" => Some(Self::UnlistedDependency),
45            "duplicate-export" => Some(Self::DuplicateExport),
46            "code-duplication" => Some(Self::CodeDuplication),
47            "circular-dependency" => Some(Self::CircularDependency),
48            _ => None,
49        }
50    }
51
52    /// Convert to a u8 discriminant for compact cache storage.
53    pub const fn to_discriminant(self) -> u8 {
54        match self {
55            Self::UnusedFile => 1,
56            Self::UnusedExport => 2,
57            Self::UnusedType => 3,
58            Self::UnusedDependency => 4,
59            Self::UnusedDevDependency => 5,
60            Self::UnusedEnumMember => 6,
61            Self::UnusedClassMember => 7,
62            Self::UnresolvedImport => 8,
63            Self::UnlistedDependency => 9,
64            Self::DuplicateExport => 10,
65            Self::CodeDuplication => 11,
66            Self::CircularDependency => 12,
67        }
68    }
69
70    /// Reconstruct from a cache discriminant.
71    pub const fn from_discriminant(d: u8) -> Option<Self> {
72        match d {
73            1 => Some(Self::UnusedFile),
74            2 => Some(Self::UnusedExport),
75            3 => Some(Self::UnusedType),
76            4 => Some(Self::UnusedDependency),
77            5 => Some(Self::UnusedDevDependency),
78            6 => Some(Self::UnusedEnumMember),
79            7 => Some(Self::UnusedClassMember),
80            8 => Some(Self::UnresolvedImport),
81            9 => Some(Self::UnlistedDependency),
82            10 => Some(Self::DuplicateExport),
83            11 => Some(Self::CodeDuplication),
84            12 => Some(Self::CircularDependency),
85            _ => None,
86        }
87    }
88}
89
90/// A suppression directive parsed from a source comment.
91#[derive(Debug, Clone)]
92pub struct Suppression {
93    /// 1-based line this suppression applies to. 0 = file-wide suppression.
94    pub line: u32,
95    /// None = suppress all issue kinds on this line.
96    pub kind: Option<IssueKind>,
97}
98
99// Size assertions to prevent memory regressions.
100// `Suppression` is stored in a Vec per file; `IssueKind` appears in every suppression.
101const _: () = assert!(std::mem::size_of::<Suppression>() == 8);
102const _: () = assert!(std::mem::size_of::<IssueKind>() == 1);
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn issue_kind_from_str_all_variants() {
110        assert_eq!(IssueKind::parse("unused-file"), Some(IssueKind::UnusedFile));
111        assert_eq!(
112            IssueKind::parse("unused-export"),
113            Some(IssueKind::UnusedExport)
114        );
115        assert_eq!(IssueKind::parse("unused-type"), Some(IssueKind::UnusedType));
116        assert_eq!(
117            IssueKind::parse("unused-dependency"),
118            Some(IssueKind::UnusedDependency)
119        );
120        assert_eq!(
121            IssueKind::parse("unused-dev-dependency"),
122            Some(IssueKind::UnusedDevDependency)
123        );
124        assert_eq!(
125            IssueKind::parse("unused-enum-member"),
126            Some(IssueKind::UnusedEnumMember)
127        );
128        assert_eq!(
129            IssueKind::parse("unused-class-member"),
130            Some(IssueKind::UnusedClassMember)
131        );
132        assert_eq!(
133            IssueKind::parse("unresolved-import"),
134            Some(IssueKind::UnresolvedImport)
135        );
136        assert_eq!(
137            IssueKind::parse("unlisted-dependency"),
138            Some(IssueKind::UnlistedDependency)
139        );
140        assert_eq!(
141            IssueKind::parse("duplicate-export"),
142            Some(IssueKind::DuplicateExport)
143        );
144        assert_eq!(
145            IssueKind::parse("circular-dependency"),
146            Some(IssueKind::CircularDependency)
147        );
148    }
149
150    #[test]
151    fn issue_kind_from_str_unknown() {
152        assert_eq!(IssueKind::parse("foo"), None);
153        assert_eq!(IssueKind::parse(""), None);
154    }
155
156    #[test]
157    fn discriminant_roundtrip() {
158        for kind in [
159            IssueKind::UnusedFile,
160            IssueKind::UnusedExport,
161            IssueKind::UnusedType,
162            IssueKind::UnusedDependency,
163            IssueKind::UnusedDevDependency,
164            IssueKind::UnusedEnumMember,
165            IssueKind::UnusedClassMember,
166            IssueKind::UnresolvedImport,
167            IssueKind::UnlistedDependency,
168            IssueKind::DuplicateExport,
169            IssueKind::CodeDuplication,
170            IssueKind::CircularDependency,
171        ] {
172            assert_eq!(
173                IssueKind::from_discriminant(kind.to_discriminant()),
174                Some(kind)
175            );
176        }
177        assert_eq!(IssueKind::from_discriminant(0), None);
178        assert_eq!(IssueKind::from_discriminant(13), None);
179    }
180}