harper_core/linting/
use_genitive.rs

1use crate::linting::{LintKind, PatternLinter, Suggestion};
2use crate::patterns::{EitherPattern, Invert, Pattern, SequencePattern, WordPatternGroup};
3use crate::{Lint, Lrc, Token};
4
5// Looks for places where the genitive case _isn't_ being used, and should be.
6pub struct UseGenitive {
7    pattern: Box<dyn Pattern>,
8}
9
10impl UseGenitive {
11    fn new() -> Self {
12        // Define the environment in which the genitive case __should__ be used.
13        let environment = Lrc::new(SequencePattern::default().then_whitespace().then(
14            EitherPattern::new(vec![
15                    Box::new(
16                        SequencePattern::default()
17                            .then_one_or_more_adjectives()
18                            .then_whitespace()
19                            .then_noun(),
20                    ),
21                    Box::new(SequencePattern::default().then_noun()),
22                ]),
23        ));
24
25        let trigger_words = ["there", "they're"];
26
27        let mut primary_pattern = WordPatternGroup::default();
28
29        for word in trigger_words {
30            primary_pattern.add(
31                word,
32                Box::new(
33                    SequencePattern::default()
34                        .then_exact_word(word)
35                        .then(environment.clone()),
36                ),
37            )
38        }
39
40        // Add a prelude to remove false-positives.
41        let full_pattern = SequencePattern::default()
42            .then(Invert::new(EitherPattern::new(vec![
43                Box::new(SequencePattern::default().t_aco("is")),
44                Box::new(SequencePattern::default().t_aco("were")),
45                Box::new(SequencePattern::default().then_adjective()),
46            ])))
47            .then_whitespace()
48            .then(primary_pattern);
49
50        Self {
51            pattern: Box::new(full_pattern),
52        }
53    }
54}
55
56impl PatternLinter for UseGenitive {
57    fn pattern(&self) -> &dyn crate::patterns::Pattern {
58        self.pattern.as_ref()
59    }
60
61    fn match_to_lint(&self, matched_tokens: &[Token], _source: &[char]) -> Option<Lint> {
62        Some(Lint {
63            span: matched_tokens[2].span,
64            lint_kind: LintKind::Miscellaneous,
65            suggestions: vec![Suggestion::ReplaceWith(vec!['t', 'h', 'e', 'i', 'r'])],
66            message: "Use the genitive case.".to_string(),
67            priority: 31,
68        })
69    }
70
71    fn description(&self) -> &'static str {
72        "Looks for situations where the genitive case of \"there\" should be used."
73    }
74}
75
76impl Default for UseGenitive {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
85
86    use super::UseGenitive;
87
88    #[test]
89    fn catches_adjective_noun() {
90        assert_suggestion_result(
91            "What are there big problems?",
92            UseGenitive::default(),
93            "What are their big problems?",
94        )
95    }
96
97    #[test]
98    fn catches_just_noun() {
99        assert_suggestion_result(
100            "What are there problems?",
101            UseGenitive::default(),
102            "What are their problems?",
103        )
104    }
105
106    #[test]
107    fn allows_clause_termination() {
108        assert_lint_count("Look there!", UseGenitive::default(), 0)
109    }
110
111    #[test]
112    fn allows_there_are() {
113        assert_lint_count(
114            "Since there are people here, we should be socially aware.",
115            UseGenitive::default(),
116            0,
117        )
118    }
119
120    #[test]
121    fn allows_there_at_beginning() {
122        assert_lint_count(
123            "There is a cute cat sitting on the chair at home.",
124            UseGenitive::default(),
125            0,
126        )
127    }
128
129    #[test]
130    fn catches_they_are() {
131        assert_suggestion_result(
132            "The students received they're test results today.",
133            UseGenitive::default(),
134            "The students received their test results today.",
135        )
136    }
137
138    #[test]
139    fn allows_grantlemons_issue_267_cat() {
140        assert_lint_count("Were there cats at her house?", UseGenitive::default(), 0);
141    }
142
143    #[test]
144    fn allows_grantlemons_issue_267_apple() {
145        assert_lint_count(
146            "Were there any apples at the store?",
147            UseGenitive::default(),
148            0,
149        );
150    }
151
152    #[test]
153    fn allows_grantlemons_issue_267_fruit() {
154        assert_lint_count(
155            "Were there many kinds of fruit at the store?",
156            UseGenitive::default(),
157            0,
158        );
159    }
160
161    #[test]
162    fn allows_grantlemons_issue_267_people() {
163        assert_lint_count(
164            "Were there more than, or less than six people at the party?",
165            UseGenitive::default(),
166            0,
167        );
168    }
169
170    #[test]
171    fn allows_faster_at_running() {
172        assert_lint_count(
173            "Melissa was faster at running than her friend.",
174            UseGenitive::default(),
175            0,
176        );
177    }
178}