use std::collections::VecDeque;
use convert_case::Case;
use convert_case::Casing;
use nonempty::NonEmpty;
use crate::concern::Code;
use crate::file::location;
mod level;
mod tag_set;
pub mod warning;
pub use level::Level;
pub use tag_set::Tag;
pub use tag_set::TagSet;
pub use warning::Warning;
#[derive(Debug)]
pub enum Error {
Location(location::Error),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Location(err) => write!(f, "location error: {err}"),
}
}
}
impl std::error::Error for Error {}
pub type Result = std::result::Result<Option<NonEmpty<Warning>>, Error>;
#[derive(Debug)]
pub struct Linter;
impl Linter {
pub fn lint<'a, E>(tree: &'a E, rules: Vec<Box<dyn Rule<&'a E>>>) -> Result {
let mut warnings = rules
.iter()
.map(|rule| rule.check(tree))
.collect::<std::result::Result<Vec<Option<NonEmpty<Warning>>>, Error>>()?
.into_iter()
.flatten()
.flatten()
.collect::<VecDeque<Warning>>();
match warnings.pop_front() {
Some(front) => {
let mut result = NonEmpty::new(front);
result.extend(warnings);
Ok(Some(result))
}
None => Ok(None),
}
}
}
pub trait Rule<E>: std::fmt::Debug + Sync {
fn name(&self) -> String {
format!("{:?}", self).to_case(Case::Snake)
}
fn code(&self) -> Code;
fn tags(&self) -> TagSet;
fn body(&self) -> &'static str;
fn check(&self, tree: E) -> Result;
}