Skip to main content

wdl_lint/
tags.rs

1//! Definition of lint rule tags.
2
3use strum::VariantArray;
4
5/// A lint rule tag.
6#[derive(VariantArray, Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7#[repr(u8)]
8pub enum Tag {
9    /// Rules associated with having a complete document.
10    Completeness,
11
12    /// Rules associated with the names of WDL elements.
13    Naming,
14
15    /// Rules associated with the whitespace in a document.
16    Spacing,
17
18    /// Rules associated with the style of a document.
19    Style,
20
21    /// Rules associated with the clarity of a document.
22    Clarity,
23
24    /// Rules associated with the portability of a document.
25    Portability,
26
27    /// Rules associated with the correctness of a document.
28    Correctness,
29
30    /// Rules associated with sorting of document elements.
31    Sorting,
32
33    /// Rules associated with the use of deprecated language constructs.
34    Deprecated,
35
36    /// Rules associated with documentation.
37    Documentation,
38
39    /// Rules associated with keeping WDL compatible with other Sprocket
40    /// commands (e.g. `doc`).
41    SprocketCompatibility,
42
43    /// Rules associated with the performance of a document.
44    Performance,
45}
46
47/// An error for when an unknown tag is encountered.
48#[derive(Debug)]
49pub struct UnknownTagError(String);
50
51impl std::fmt::Display for UnknownTagError {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        write!(f, "unknown tag: {}", self.0)
54    }
55}
56
57impl std::error::Error for UnknownTagError {}
58
59impl std::str::FromStr for Tag {
60    type Err = UnknownTagError;
61
62    fn from_str(s: &str) -> Result<Self, Self::Err> {
63        match s {
64            s if s.eq_ignore_ascii_case("completeness") => Ok(Self::Completeness),
65            s if s.eq_ignore_ascii_case("naming") => Ok(Self::Naming),
66            s if s.eq_ignore_ascii_case("spacing") => Ok(Self::Spacing),
67            s if s.eq_ignore_ascii_case("style") => Ok(Self::Style),
68            s if s.eq_ignore_ascii_case("clarity") => Ok(Self::Clarity),
69            s if s.eq_ignore_ascii_case("portability") => Ok(Self::Portability),
70            s if s.eq_ignore_ascii_case("correctness") => Ok(Self::Correctness),
71            s if s.eq_ignore_ascii_case("sorting") => Ok(Self::Sorting),
72            s if s.eq_ignore_ascii_case("deprecated") => Ok(Self::Deprecated),
73            s if s.eq_ignore_ascii_case("documentation") => Ok(Self::Documentation),
74            s if s.eq_ignore_ascii_case("sprocketcompatibility") => Ok(Self::SprocketCompatibility),
75            s if s.eq_ignore_ascii_case("performance") => Ok(Self::Performance),
76            _ => Err(UnknownTagError(s.to_string())),
77        }
78    }
79}
80
81impl std::fmt::Display for Tag {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            Self::Completeness => write!(f, "Completeness"),
85            Self::Naming => write!(f, "Naming"),
86            Self::Spacing => write!(f, "Spacing"),
87            Self::Style => write!(f, "Style"),
88            Self::Clarity => write!(f, "Clarity"),
89            Self::Portability => write!(f, "Portability"),
90            Self::Correctness => write!(f, "Correctness"),
91            Self::Sorting => write!(f, "Sorting"),
92            Self::Deprecated => write!(f, "Deprecated"),
93            Self::Documentation => write!(f, "Documentation"),
94            Self::SprocketCompatibility => write!(f, "SprocketCompatibility"),
95            Self::Performance => write!(f, "Performance"),
96        }
97    }
98}
99
100/// A set of lint tags.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
102pub struct TagSet(u32);
103
104impl TagSet {
105    /// Constructs a tag set from a slice of tags.
106    pub const fn new(tags: &[Tag]) -> Self {
107        if tags.is_empty() {
108            return Self(0);
109        }
110
111        let mut bits = 0u32;
112        let mut i = 0;
113        while i < tags.len() {
114            bits |= Self::mask(tags[i]);
115            i += 1;
116        }
117        Self(bits)
118    }
119
120    /// Unions two tag sets together.
121    pub const fn union(self, other: Self) -> Self {
122        Self(self.0 | other.0)
123    }
124
125    /// Intersects two tag sets.
126    pub const fn intersect(self, other: Self) -> Self {
127        Self(self.0 & other.0)
128    }
129
130    /// Checks if the tag is contained in the set.
131    pub const fn contains(&self, tag: Tag) -> bool {
132        self.0 & Self::mask(tag) != 0
133    }
134
135    /// Gets the count of tags in the set.
136    pub const fn count(&self) -> usize {
137        self.0.count_ones() as usize
138    }
139
140    /// Masks the given tag to a `u32`.
141    const fn mask(tag: Tag) -> u32 {
142        1u32 << (tag as u8)
143    }
144
145    /// Iterates the tags in the set.
146    pub fn iter(&self) -> impl Iterator<Item = Tag> + use<> {
147        let mut bits = self.0;
148        std::iter::from_fn(move || {
149            if bits == 0 {
150                return None;
151            }
152
153            let tag = unsafe {
154                std::mem::transmute::<u8, Tag>(
155                    u8::try_from(bits.trailing_zeros())
156                        .expect("the maximum tag value should be less than 32"),
157                )
158            };
159
160            bits ^= bits & bits.overflowing_neg().0;
161            Some(tag)
162        })
163    }
164}
165
166/// Display for a tag set.
167impl std::fmt::Display for TagSet {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        let mut tags = self.iter().collect::<Vec<_>>();
170        tags.sort();
171        write!(f, "{tags:?}")
172    }
173}
174
175#[cfg(test)]
176mod test {
177    use super::*;
178
179    #[test]
180    fn it_unions() {
181        let a = TagSet::new(&[Tag::Clarity, Tag::Completeness]);
182        assert_eq!(a.count(), 2);
183        let b = TagSet::new(&[Tag::Clarity, Tag::Deprecated]);
184        assert_eq!(b.count(), 2);
185
186        let union = a.union(b);
187        assert_eq!(
188            union,
189            TagSet::new(&[Tag::Clarity, Tag::Completeness, Tag::Deprecated])
190        );
191        assert_eq!(union.count(), 3);
192    }
193
194    #[test]
195    fn it_intersects() {
196        let a = TagSet::new(&[Tag::Clarity, Tag::Completeness]);
197        assert_eq!(a.count(), 2);
198        let b = TagSet::new(&[Tag::Clarity, Tag::Deprecated]);
199        assert_eq!(b.count(), 2);
200
201        let intersection = a.intersect(b);
202
203        assert_eq!(intersection, TagSet::new(&[Tag::Clarity]));
204        assert_eq!(intersection.count(), 1);
205    }
206
207    #[test]
208    fn empty_slice_behaves() {
209        let a = TagSet::new(&[]);
210        assert_eq!(a.0, 0u32);
211
212        let b = TagSet::new(&[]);
213        assert_eq!(a, b);
214        assert_eq!(a, b.intersect(a));
215        assert_eq!(b, a.union(b));
216        assert_eq!(a.count(), 0);
217    }
218}