use crate::expr::ExprExt;
use crate::expr::SequenceExpr;
use crate::{Document, Token, TokenStringExt, patterns::NominalPhrase};
use super::{Lint, LintKind, Linter, Suggestion};
pub struct NoOxfordComma {
expr: SequenceExpr,
}
impl NoOxfordComma {
pub fn new() -> Self {
Self {
expr: {
let this = {
let this = SequenceExpr::default();
this.then(NominalPhrase)
}
.then_comma()
.then_whitespace();
this.then(NominalPhrase)
}
.then_comma()
.then_whitespace()
.then_word_set(&["and", "or", "nor"]),
}
}
fn match_to_lint(&self, matched_toks: &[Token], _source: &[char]) -> Option<Lint> {
let last_comma_index = matched_toks.last_comma_index()?;
let offender = &matched_toks[last_comma_index];
Some(Lint {
span: offender.span,
lint_kind: LintKind::Style,
suggestions: vec![Suggestion::Remove],
message: "Remove the Oxford comma here.".to_owned(),
priority: 31,
})
}
}
impl Default for NoOxfordComma {
fn default() -> Self {
Self::new()
}
}
impl Linter for NoOxfordComma {
fn lint(&mut self, document: &Document) -> Vec<Lint> {
let mut lints = Vec::new();
for sentence in document.iter_sentences() {
for match_span in self.expr.iter_matches(sentence, document.get_source()) {
let lint = self.match_to_lint(
&sentence[match_span.start..match_span.end],
document.get_source(),
);
lints.extend(lint);
}
}
lints
}
fn description(&self) -> &str {
"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."
}
}
#[cfg(test)]
mod tests {
use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
use super::NoOxfordComma;
#[test]
fn fruits() {
assert_lint_count(
"An apple, a banana, and a pear",
NoOxfordComma::default(),
1,
);
}
#[test]
fn people() {
assert_suggestion_result(
"Nancy, Steve, and Carl are going to the coffee shop.",
NoOxfordComma::default(),
"Nancy, Steve and Carl are going to the coffee shop.",
);
}
#[test]
fn places() {
assert_suggestion_result(
"I've always wanted to visit Paris, Tokyo, and Rome.",
NoOxfordComma::default(),
"I've always wanted to visit Paris, Tokyo and Rome.",
);
}
#[test]
fn foods() {
assert_suggestion_result(
"My favorite foods are pizza, sushi, tacos, and burgers.",
NoOxfordComma::default(),
"My favorite foods are pizza, sushi, tacos and burgers.",
);
}
#[test]
fn allows_clean_music() {
assert_lint_count(
"I enjoy listening to pop music, rock, hip-hop, electronic dance and classical music.",
NoOxfordComma::default(),
0,
);
}
#[test]
fn allows_clean_nations() {
assert_lint_count(
"The team consists of players from different countries: France, Germany, Italy and Spain.",
NoOxfordComma::default(),
0,
);
}
#[test]
fn or_writing() {
assert_suggestion_result(
"Harper can be a lifesaver when writing technical documents, emails, or other formal forms of communication.",
NoOxfordComma::default(),
"Harper can be a lifesaver when writing technical documents, emails or other formal forms of communication.",
);
}
#[test]
fn sports() {
assert_suggestion_result(
"They enjoy playing soccer, basketball, or tennis.",
NoOxfordComma::default(),
"They enjoy playing soccer, basketball or tennis.",
);
}
#[test]
fn nor_vegetables() {
assert_suggestion_result(
"I like carrots, kale, nor broccoli.",
NoOxfordComma::default(),
"I like carrots, kale nor broccoli.",
);
}
}