use crate::FileId; use crate::ast; use crate::rules::deprecated_to_path::DeprecatedToPath; use crate::rules::empty_inherit::EmptyInherit; use crate::rules::empty_let_in::EmptyLetIn; use crate::rules::unquoted_uri::UnquotedUri; pub mod deprecated_to_path; pub mod empty_inherit; pub mod empty_let_in; pub mod unquoted_uri; #[derive(Debug)] pub struct Diagnostic { pub file_id: FileId, pub finding: Finding, } #[derive(Debug)] pub struct Finding { pub rule_id: &'static str, pub range: ast::SourceRange, pub message: &'static str, } impl std::fmt::Display for Diagnostic { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( formatter, "{}: {}: {}", self.finding.range, self.finding.rule_id, self.finding.message, ) } } pub trait NodeRule { type Node; const ID: &'static str; fn select(data: &ast::NodeData) -> Option<&Self::Node>; fn check(node: &Self::Node) -> Option<&'static str>; } pub trait TokenRule { type Token; const ID: &'static str; fn select(data: &ast::TokenData) -> Option<&Self::Token>; fn check(token: &Self::Token) -> Option<&'static str>; } pub fn run_rules(file: &ast::ParsedFile) -> Vec { let node_diagnostics = file.nodes.iter().flat_map(|node| { [ run_node_rule::(node), run_node_rule::(node), run_node_rule::(node), ] .into_iter() .flatten() .map(|finding| Diagnostic { file_id: file.file_id, finding, }) }); let token_diagnostics = file.tokens.iter().flat_map(|token| { [run_token_rule::(token)] .into_iter() .flatten() .map(|finding| Diagnostic { file_id: file.file_id, finding, }) }); node_diagnostics.chain(token_diagnostics).collect() } fn run_node_rule(node: &ast::Node) -> Option where R: NodeRule, { let typed_node = R::select(&node.data)?; let message = R::check(typed_node)?; Some(Finding { rule_id: R::ID, range: node.range, message, }) } fn run_token_rule(token: &ast::Token) -> Option where R: TokenRule, { let typed_token = R::select(&token.data)?; let message = R::check(typed_token)?; Some(Finding { rule_id: R::ID, range: token.range, message, }) }