use crate::{
expr::{Expr, SequenceExpr},
linting::{ExprLinter, LintKind, Suggestion, expr_linter::Chunk},
{Lint, Lrc, Token, TokenStringExt},
};
pub struct MultipleSequentialPronouns {
expr: SequenceExpr,
}
impl MultipleSequentialPronouns {
fn new() -> Self {
let pronouns = Lrc::new(|t: &Token, _s: &[char]| {
t.kind.is_subject_pronoun() || t.kind.is_object_pronoun() || t.kind.is_possessive_pronoun() || t.kind.is_possessive_determiner() });
Self {
expr: SequenceExpr::with(pronouns.clone())
.then_one_or_more(SequenceExpr::whitespace().then(pronouns.clone())),
}
}
}
impl ExprLinter for MultipleSequentialPronouns {
type Unit = Chunk;
fn expr(&self) -> &dyn Expr {
&self.expr
}
fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
let mut suggestions = Vec::new();
if matched_tokens.len() == 3 {
let first_word_tok = &matched_tokens[0];
let second_word_tok = &matched_tokens[2];
let first_word_raw = first_word_tok.get_ch(source);
let second_word_raw = second_word_tok.get_ch(source);
if first_word_tok.kind.is_object_pronoun()
&& second_word_tok.kind.is_possessive_determiner()
{
return None;
}
if first_word_tok.kind.is_object_pronoun() && second_word_tok.kind.is_subject_pronoun()
{
return None;
}
if first_word_tok.kind.is_possessive_determiner()
&& (second_word_raw == ['U', 'S'] || second_word_raw == ['I', 'T'])
{
return None;
}
if first_word_raw == ['U', 'S'] && second_word_tok.kind.is_subject_pronoun() {
return None;
}
suggestions.push(Suggestion::ReplaceWith(
matched_tokens[0].get_ch(source).to_vec(),
));
suggestions.push(Suggestion::ReplaceWith(
matched_tokens[2].get_ch(source).to_vec(),
));
}
Some(Lint {
span: matched_tokens.span()?,
lint_kind: LintKind::Repetition,
message: "There are too many personal pronouns in sequence here.".to_owned(),
priority: 63,
suggestions,
})
}
fn description(&self) -> &'static str {
"When editing work to change point of view (i.e. first-person or third-person) it is common to add pronouns while neglecting to remove old ones. This rule catches cases where you have multiple disparate pronouns in sequence."
}
}
impl Default for MultipleSequentialPronouns {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::MultipleSequentialPronouns;
use crate::linting::tests::assert_lint_count;
#[test]
fn can_detect_two_pronouns() {
assert_lint_count(
"...little bit about my I want to do.",
MultipleSequentialPronouns::new(),
1,
)
}
#[test]
fn can_detect_three_pronouns() {
assert_lint_count(
"...little bit about my I you want to do.",
MultipleSequentialPronouns::new(),
1,
)
}
#[test]
fn allows_single_pronouns() {
assert_lint_count(
"...little bit about I want to do.",
MultipleSequentialPronouns::new(),
0,
)
}
#[test]
fn detects_multiple_pronouns_at_end() {
assert_lint_count(
"...I need to explain this to you them.",
MultipleSequentialPronouns::new(),
1,
)
}
#[test]
fn comma_separated() {
assert_lint_count("To prove it, we...", MultipleSequentialPronouns::new(), 0)
}
#[test]
fn dont_flag_578() {
assert_lint_count(
"I can lend you my car.",
MultipleSequentialPronouns::new(),
0,
)
}
#[test]
fn dont_flag_724() {
assert_lint_count(
"One told me they were able to begin reading.",
MultipleSequentialPronouns::new(),
0,
)
}
#[test]
fn dont_flag_us() {
assert_lint_count(
"Take the plunge and pull plug from their US tech.",
MultipleSequentialPronouns::new(),
0,
)
}
#[test]
fn dont_flag_my_us_your_us() {
assert_lint_count(
"My US passport looks different from your US passport.",
MultipleSequentialPronouns::new(),
0,
)
}
#[test]
fn dont_flag_subject_after_usa() {
assert_lint_count(
"And if it’s manufactured in the US it may have more automation.",
MultipleSequentialPronouns::new(),
0,
)
}
#[test]
fn dont_flag_case_insensitive_cost_him_his_life() {
assert_lint_count(
"to the point where it very well likely cost Him his life",
MultipleSequentialPronouns::new(),
0,
)
}
#[test]
fn dont_flag_2870() {
assert_lint_count(
"their sales derp was just having none of it when our IT director told him, point blank, that we're not moving anything into the cloud",
MultipleSequentialPronouns::new(),
0,
)
}
}