use crate::linting::expr_linter::Chunk;
use crate::{
Token, TokenStringExt,
expr::{Expr, SequenceExpr},
linting::{ExprLinter, Lint, LintKind, Suggestion},
};
pub struct PronounAre {
expr: SequenceExpr,
}
impl Default for PronounAre {
fn default() -> Self {
let expr = SequenceExpr::default()
.then_kind_where(|kind| {
kind.is_pronoun()
&& kind.is_subject_pronoun()
&& (kind.is_second_person_pronoun()
|| kind.is_first_person_plural_pronoun()
|| kind.is_third_person_plural_pronoun())
})
.t_ws()
.t_aco("r");
Self { expr }
}
}
impl ExprLinter for PronounAre {
type Unit = Chunk;
fn expr(&self) -> &dyn Expr {
&self.expr
}
fn match_to_lint(&self, tokens: &[Token], source: &[char]) -> Option<Lint> {
let span = tokens.span()?;
let pronoun = tokens.first()?;
let gap = tokens.get(1)?;
let letter = tokens.get(2)?;
let pronoun_chars = pronoun.get_ch(source);
let gap_chars = gap.get_ch(source);
let letter_chars = letter.get_ch(source);
let all_pronoun_letters_uppercase = pronoun_chars
.iter()
.filter(|c| c.is_alphabetic())
.all(|c| c.is_uppercase());
let letter_has_uppercase = letter_chars.iter().any(|c| c.is_uppercase());
let uppercase_suffix = letter_has_uppercase || all_pronoun_letters_uppercase;
let are_suffix: Vec<char> = if uppercase_suffix {
vec!['A', 'R', 'E']
} else {
vec!['a', 'r', 'e']
};
let re_suffix: Vec<char> = if uppercase_suffix {
vec!['R', 'E']
} else {
vec!['r', 'e']
};
let mut with_are = pronoun_chars.to_vec();
with_are.extend_from_slice(gap_chars);
with_are.extend(are_suffix);
let mut with_contraction = pronoun_chars.to_vec();
with_contraction.push('\'');
with_contraction.extend(re_suffix);
Some(Lint {
span,
lint_kind: LintKind::WordChoice,
suggestions: vec![
Suggestion::ReplaceWith(with_are),
Suggestion::ReplaceWith(with_contraction),
],
message: "Use the full verb or the contraction after this pronoun.".to_owned(),
priority: 40,
})
}
fn description(&self) -> &str {
"Spots the letter `r` used in place of `are` or `you're` after plural first- or second-person pronouns."
}
}
#[cfg(test)]
mod tests {
use super::PronounAre;
use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
#[test]
fn fixes_you_r() {
assert_suggestion_result(
"You r absolutely right.",
PronounAre::default(),
"You are absolutely right.",
);
}
#[test]
fn offers_contraction_option() {
assert_suggestion_result(
"You r absolutely right.",
PronounAre::default(),
"You're absolutely right.",
);
}
#[test]
fn keeps_uppercase_pronoun() {
assert_suggestion_result(
"YOU r welcome here.",
PronounAre::default(),
"YOU ARE welcome here.",
);
}
#[test]
fn fixes_they_r_with_comma() {
assert_suggestion_result(
"They r, of course, arriving tomorrow.",
PronounAre::default(),
"They are, of course, arriving tomorrow.",
);
}
#[test]
fn fixes_we_r_lowercase() {
assert_suggestion_result(
"we r ready now.",
PronounAre::default(),
"we are ready now.",
);
}
#[test]
fn fixes_they_r_sentence_start() {
assert_suggestion_result(
"They r planning ahead.",
PronounAre::default(),
"They are planning ahead.",
);
}
#[test]
fn fixes_lowercase_sentence() {
assert_suggestion_result(
"they r late again.",
PronounAre::default(),
"they are late again.",
);
}
#[test]
fn handles_line_break() {
assert_suggestion_result(
"We r\nready to go.",
PronounAre::default(),
"We are\nready to go.",
);
}
#[test]
fn does_not_flag_contraction() {
assert_lint_count("You're looking great.", PronounAre::default(), 0);
}
#[test]
fn does_not_flag_full_form() {
assert_lint_count("They are excited about it.", PronounAre::default(), 0);
}
#[test]
fn ignores_similar_word() {
assert_lint_count("Your results impressed everyone.", PronounAre::default(), 0);
}
}