wdl_core/concern/
lint.rs

1//! Linting.
2
3use std::collections::VecDeque;
4
5use convert_case::Case;
6use convert_case::Casing;
7use nonempty::NonEmpty;
8
9use crate::concern::Code;
10use crate::file::location;
11
12mod level;
13mod tag_set;
14pub mod warning;
15
16pub use level::Level;
17pub use tag_set::Tag;
18pub use tag_set::TagSet;
19pub use warning::Warning;
20
21/// An unrecoverable error that occurs during linting.
22#[derive(Debug)]
23pub enum Error {
24    /// A location error.
25    Location(location::Error),
26}
27
28impl std::fmt::Display for Error {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            Error::Location(err) => write!(f, "location error: {err}"),
32        }
33    }
34}
35
36impl std::error::Error for Error {}
37
38/// A [`Result`](std::result::Result) returned from a lint check.
39pub type Result = std::result::Result<Option<NonEmpty<Warning>>, Error>;
40
41/// A tree linter.
42#[derive(Debug)]
43pub struct Linter;
44
45impl Linter {
46    /// Lints a tree according to a set of lint rules and returns a
47    /// set of lint warnings (if any are detected).
48    pub fn lint<'a, E>(tree: &'a E, rules: Vec<Box<dyn Rule<&'a E>>>) -> Result {
49        let mut warnings = rules
50            .iter()
51            .map(|rule| rule.check(tree))
52            .collect::<std::result::Result<Vec<Option<NonEmpty<Warning>>>, Error>>()?
53            .into_iter()
54            .flatten()
55            .flatten()
56            .collect::<VecDeque<Warning>>();
57
58        match warnings.pop_front() {
59            Some(front) => {
60                let mut result = NonEmpty::new(front);
61                result.extend(warnings);
62                Ok(Some(result))
63            }
64            None => Ok(None),
65        }
66    }
67}
68
69/// A lint rule.
70pub trait Rule<E>: std::fmt::Debug + Sync {
71    /// The name of the lint rule.
72    fn name(&self) -> String {
73        format!("{:?}", self).to_case(Case::Snake)
74    }
75
76    /// Get the code for this lint rule.
77    fn code(&self) -> Code;
78
79    /// Get the lint tags for this lint rule.
80    fn tags(&self) -> TagSet;
81
82    /// Get the body of the lint rule.
83    fn body(&self) -> &'static str;
84
85    /// Checks the tree according to the implemented lint rule.
86    fn check(&self, tree: E) -> Result;
87}