harper_core/linting/
pattern_linter.rs

1use blanket::blanket;
2
3use crate::{
4    Document, LSend, Token, TokenStringExt,
5    patterns::{Pattern, PatternExt},
6};
7
8use super::{Lint, Linter};
9
10/// A trait that searches for [`Pattern`]s in [`Document`]s.
11///
12/// Makes use of [`TokenStringExt::iter_chunks`] to avoid matching across sentence or clause
13/// boundaries.
14#[blanket(derive(Box))]
15pub trait PatternLinter: LSend {
16    /// A simple getter for the pattern to be searched for.
17    fn pattern(&self) -> &dyn Pattern;
18    /// If any portions of a [`Document`] match [`Self::pattern`], they are passed through [`PatternLinter::match_to_lint`] to be
19    /// transformed into a [`Lint`] for editor consumption.
20    ///
21    /// This function may return `None` to elect _not_ to produce a lint.
22    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint>;
23    /// A user-facing description of what kinds of grammatical errors this rule looks for.
24    /// It is usually shown in settings menus.
25    fn description(&self) -> &str;
26}
27
28impl<L> Linter for L
29where
30    L: PatternLinter,
31{
32    fn lint(&mut self, document: &Document) -> Vec<Lint> {
33        let mut lints = Vec::new();
34        let source = document.get_source();
35
36        for chunk in document.iter_chunks() {
37            lints.extend(run_on_chunk(self, chunk, source));
38        }
39
40        lints
41    }
42
43    fn description(&self) -> &str {
44        self.description()
45    }
46}
47
48pub fn run_on_chunk(linter: &impl PatternLinter, chunk: &[Token], source: &[char]) -> Vec<Lint> {
49    let mut lints = Vec::new();
50
51    for match_span in linter.pattern().iter_matches(chunk, source) {
52        let lint = linter.match_to_lint(&chunk[match_span.start..match_span.end], source);
53        lints.extend(lint);
54    }
55
56    lints
57}