harper_core/linting/
expr_linter.rs1use crate::expr::{Expr, ExprExt};
2use blanket::blanket;
3
4use crate::{Document, LSend, Token, TokenStringExt};
5
6use super::{Lint, Linter};
7
8#[blanket(derive(Box))]
13pub trait ExprLinter: LSend {
14 fn expr(&self) -> &dyn Expr;
16 fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint>;
21 fn description(&self) -> &str;
24}
25
26pub fn find_the_only_token_matching<'a, F>(
31 tokens: &'a [Token],
32 source: &[char],
33 predicate: F,
34) -> Option<&'a Token>
35where
36 F: Fn(&Token, &[char]) -> bool,
37{
38 let mut matches = tokens.iter().filter(|&tok| predicate(tok, source));
39 match (matches.next(), matches.next()) {
40 (Some(tok), None) => Some(tok),
41 _ => None,
42 }
43}
44
45impl<L> Linter for L
46where
47 L: ExprLinter,
48{
49 fn lint(&mut self, document: &Document) -> Vec<Lint> {
50 let mut lints = Vec::new();
51 let source = document.get_source();
52
53 for chunk in document.iter_chunks() {
54 lints.extend(run_on_chunk(self, chunk, source));
55 }
56
57 lints
58 }
59
60 fn description(&self) -> &str {
61 self.description()
62 }
63}
64
65pub fn run_on_chunk<'a>(
66 linter: &'a impl ExprLinter,
67 chunk: &'a [Token],
68 source: &'a [char],
69) -> impl Iterator<Item = Lint> + 'a {
70 linter
71 .expr()
72 .iter_matches(chunk, source)
73 .filter_map(|match_span| {
74 linter.match_to_lint(&chunk[match_span.start..match_span.end], source)
75 })
76}