harper_core/linting/
oxymorons.rs

1use crate::linting::{Lint, LintKind, PatternLinter};
2use crate::patterns::{EitherPattern, ExactPhrase, Pattern};
3use crate::{Token, TokenStringExt};
4
5/// A linter that flags oxymoronic phrases.
6pub struct Oxymorons {
7    pattern: Box<dyn Pattern>,
8}
9
10impl Oxymorons {
11    pub fn new() -> Self {
12        // List of phrases that are considered oxymoronic.
13        let phrases = vec![
14            "amateur expert",
15            "increasingly less",
16            "advancing backwards?",
17            "alludes explicitly to",
18            "explicitly alludes to",
19            "totally obsolescent",
20            "completely obsolescent",
21            "generally always",
22            "usually always",
23            "build down",
24            "conspicuous absence",
25            "exact estimate",
26            "found missing",
27            "intense apathy",
28            "mandatory choice",
29            "nonworking mother",
30            "organized mess",
31        ];
32
33        // Build a vector of exact-match patterns for each oxymoron.
34        let patterns: Vec<Box<dyn Pattern>> = phrases
35            .into_iter()
36            .map(|s| Box::new(ExactPhrase::from_phrase(s)) as Box<dyn Pattern>)
37            .collect();
38
39        let pattern = Box::new(EitherPattern::new(patterns));
40        Self { pattern }
41    }
42}
43
44impl Default for Oxymorons {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl PatternLinter for Oxymorons {
51    /// Returns the underlying pattern.
52    fn pattern(&self) -> &dyn Pattern {
53        self.pattern.as_ref()
54    }
55
56    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
57        let span = matched_tokens.span()?;
58        let matched_text: String = span.get_content(source).iter().collect();
59        Some(Lint {
60            span,
61            lint_kind: LintKind::Miscellaneous,
62            suggestions: Vec::new(),
63            message: format!("'{}' is an oxymoron.", matched_text),
64            priority: 31,
65        })
66    }
67
68    fn description(&self) -> &str {
69        "Flags oxymoronic phrases (e.g. `amateur expert`, `increasingly less`, etc.)."
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::Oxymorons;
76    use crate::linting::tests::assert_lint_count;
77
78    #[test]
79    fn detects_amateur_expert() {
80        assert_lint_count("The amateur expert gave his opinion.", Oxymorons::new(), 1);
81    }
82
83    #[test]
84    fn detects_increasingly_less() {
85        assert_lint_count(
86            "The solution was increasingly less effective.",
87            Oxymorons::new(),
88            1,
89        );
90    }
91
92    #[test]
93    fn detects_advancing_backwards() {
94        assert_lint_count("The project is advancing backwards?", Oxymorons::new(), 1);
95    }
96
97    #[test]
98    fn detects_alludes_explicitly_to() {
99        assert_lint_count(
100            "The report alludes explicitly to several issues.",
101            Oxymorons::new(),
102            1,
103        );
104    }
105
106    #[test]
107    fn detects_explicitly_alludes_to() {
108        assert_lint_count(
109            "The report explicitly alludes to several issues.",
110            Oxymorons::new(),
111            1,
112        );
113    }
114
115    #[test]
116    fn does_not_flag_clean_text() {
117        assert_lint_count("The expert provided clear advice.", Oxymorons::new(), 0);
118    }
119
120    #[test]
121    fn lowercase_match() {
122        assert_lint_count(
123            "the amateur expert is often unreliable.",
124            Oxymorons::new(),
125            1,
126        );
127    }
128
129    #[test]
130    fn phrase_with_extra_whitespace() {
131        assert_lint_count("An organized    mess was found.", Oxymorons::new(), 1);
132    }
133
134    #[test]
135    fn phrase_split_by_line_break() {
136        assert_lint_count(
137            "nonworking\nmother is not a term to be used.",
138            Oxymorons::new(),
139            1,
140        );
141    }
142}