harper_core/linting/
that_which.rs

1use itertools::Itertools;
2
3use crate::{
4    Lrc, Token, TokenStringExt,
5    patterns::{Pattern, SequencePattern, WordPatternGroup},
6};
7
8use super::{Lint, LintKind, PatternLinter, Suggestion};
9
10pub struct ThatWhich {
11    pattern: Box<dyn Pattern>,
12}
13
14impl Default for ThatWhich {
15    fn default() -> Self {
16        let mut pattern = WordPatternGroup::default();
17
18        let matching_pattern = Lrc::new(
19            SequencePattern::default()
20                .then_any_capitalization_of("that")
21                .then_whitespace()
22                .then_any_capitalization_of("that"),
23        );
24
25        pattern.add("that", Box::new(matching_pattern.clone()));
26        pattern.add("That", Box::new(matching_pattern));
27
28        Self {
29            pattern: Box::new(pattern),
30        }
31    }
32}
33
34impl PatternLinter for ThatWhich {
35    fn pattern(&self) -> &dyn Pattern {
36        self.pattern.as_ref()
37    }
38
39    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
40        let suggestion = format!(
41            "{} which",
42            matched_tokens[0]
43                .span
44                .get_content(source)
45                .iter()
46                .collect::<String>()
47        )
48        .chars()
49        .collect_vec();
50
51        Some(Lint {
52            span: matched_tokens.span()?,
53            lint_kind: LintKind::Repetition,
54            suggestions: vec![Suggestion::ReplaceWith(suggestion)],
55            message: "“that that” sometimes means “that which”, which is clearer.".to_string(),
56            priority: 126,
57        })
58    }
59
60    fn description(&self) -> &'static str {
61        "Repeating the word \"that\" is often redundant. The phrase `that which` is easier to read."
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::super::tests::assert_lint_count;
68    use super::ThatWhich;
69
70    #[test]
71    fn catches_lowercase() {
72        assert_lint_count(
73            "To reiterate, that that is cool is not uncool.",
74            ThatWhich::default(),
75            1,
76        );
77    }
78
79    #[test]
80    fn catches_different_cases() {
81        assert_lint_count("That that is cool is not uncool.", ThatWhich::default(), 1);
82    }
83
84    #[test]
85    fn likes_correction() {
86        assert_lint_count(
87            "To reiterate, that which is cool is not uncool.",
88            ThatWhich::default(),
89            0,
90        );
91    }
92}