harper_core/linting/
no_oxford_comma.rs

1use crate::expr::ExprExt;
2use crate::expr::SequenceExpr;
3use crate::{
4    Document, Token, TokenStringExt,
5    patterns::{NominalPhrase, WordSet},
6};
7
8use super::{Lint, LintKind, Linter, Suggestion};
9
10pub struct NoOxfordComma {
11    expr: SequenceExpr,
12}
13
14impl NoOxfordComma {
15    pub fn new() -> Self {
16        Self {
17            expr: {
18                let this = {
19                    let this = SequenceExpr::default();
20                    this.then(NominalPhrase)
21                }
22                .then_comma()
23                .then_whitespace();
24                this.then(NominalPhrase)
25            }
26            .then_comma()
27            .then_whitespace()
28            .then(WordSet::new(&["and", "or", "nor"])),
29        }
30    }
31
32    fn match_to_lint(&self, matched_toks: &[Token], _source: &[char]) -> Option<Lint> {
33        let last_comma_index = matched_toks.last_comma_index()?;
34        let offender = &matched_toks[last_comma_index];
35
36        Some(Lint {
37            span: offender.span,
38            lint_kind: LintKind::Style,
39            suggestions: vec![Suggestion::Remove],
40            message: "Remove the Oxford comma here.".to_owned(),
41            priority: 31,
42        })
43    }
44}
45
46impl Default for NoOxfordComma {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl Linter for NoOxfordComma {
53    fn lint(&mut self, document: &Document) -> Vec<Lint> {
54        let mut lints = Vec::new();
55
56        for sentence in document.iter_sentences() {
57            for match_span in self.expr.iter_matches(sentence, document.get_source()) {
58                let lint = self.match_to_lint(
59                    &sentence[match_span.start..match_span.end],
60                    document.get_source(),
61                );
62                lints.extend(lint);
63            }
64        }
65
66        lints
67    }
68
69    fn description(&self) -> &str {
70        "The Oxford comma is one of the more controversial rules in common use today. Enabling this lint checks that there is no comma before `and`, `or` or `nor` when listing out more than two ideas."
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
77
78    use super::NoOxfordComma;
79
80    #[test]
81    fn fruits() {
82        assert_lint_count(
83            "An apple, a banana, and a pear",
84            NoOxfordComma::default(),
85            1,
86        );
87    }
88
89    #[test]
90    fn people() {
91        assert_suggestion_result(
92            "Nancy, Steve, and Carl are going to the coffee shop.",
93            NoOxfordComma::default(),
94            "Nancy, Steve and Carl are going to the coffee shop.",
95        );
96    }
97
98    #[test]
99    fn places() {
100        assert_suggestion_result(
101            "I've always wanted to visit Paris, Tokyo, and Rome.",
102            NoOxfordComma::default(),
103            "I've always wanted to visit Paris, Tokyo and Rome.",
104        );
105    }
106
107    #[test]
108    fn foods() {
109        assert_suggestion_result(
110            "My favorite foods are pizza, sushi, tacos, and burgers.",
111            NoOxfordComma::default(),
112            "My favorite foods are pizza, sushi, tacos and burgers.",
113        );
114    }
115
116    #[test]
117    fn allows_clean_music() {
118        assert_lint_count(
119            "I enjoy listening to pop music, rock, hip-hop, electronic dance and classical music.",
120            NoOxfordComma::default(),
121            0,
122        );
123    }
124
125    #[test]
126    fn allows_clean_nations() {
127        assert_lint_count(
128            "The team consists of players from different countries: France, Germany, Italy and Spain.",
129            NoOxfordComma::default(),
130            0,
131        );
132    }
133
134    #[test]
135    fn or_writing() {
136        assert_suggestion_result(
137            "Harper can be a lifesaver when writing technical documents, emails, or other formal forms of communication.",
138            NoOxfordComma::default(),
139            "Harper can be a lifesaver when writing technical documents, emails or other formal forms of communication.",
140        );
141    }
142
143    #[test]
144    fn sports() {
145        assert_suggestion_result(
146            "They enjoy playing soccer, basketball, or tennis.",
147            NoOxfordComma::default(),
148            "They enjoy playing soccer, basketball or tennis.",
149        );
150    }
151
152    #[test]
153    fn nor_vegetables() {
154        assert_suggestion_result(
155            "I like carrots, kale, nor broccoli.",
156            NoOxfordComma::default(),
157            "I like carrots, kale nor broccoli.",
158        );
159    }
160}