harper-core 2.13.0

The language checker for developers.
Documentation
use crate::{
    Lint, Token, TokenStringExt,
    expr::{All, Expr, FirstMatchOf, SequenceExpr, UnlessStep},
    linting::{ExprLinter, LintKind, Suggestion, expr_linter::Chunk},
};

pub struct Themself {
    expr: FirstMatchOf,
}

impl Default for Themself {
    fn default() -> Self {
        Self {
            expr: FirstMatchOf::new([
                Box::new(SequenceExpr::aco("their").t_ws().t_aco("selves")) as Box<dyn Expr>,
                Box::new(All::new([
                    Box::new(SequenceExpr::aco("their").t_ws().t_aco("self")) as Box<dyn Expr>,
                    Box::new(UnlessStep::new(
                        SequenceExpr::anything().t_any().t_any().then_any_of([
                            Box::new(SequenceExpr::default().then_hyphen()),
                            Box::new(SequenceExpr::whitespace().then(|t: &Token, _: &[char]| {
                                t.kind.is_noun()
                                    || t.kind.is_verb_past_form()
                                    || t.kind.is_verb_past_participle_form()
                            })),
                        ]),
                        |_: &Token, _: &[char]| true,
                    )),
                ])),
            ]),
        }
    }
}

impl ExprLinter for Themself {
    type Unit = Chunk;

    fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
        let refl = toks.get(2)?.get_ch(src);

        let span = toks.span()?;

        let suggestions = vec![Suggestion::replace_with_match_case_str(
            if refl.len() == 4 {
                "themself"
            } else {
                "themselves"
            },
            span.get_content(src),
        )];

        Some(Lint {
            span,
            lint_kind: LintKind::Usage,
            suggestions,
            message: "This should be one word, not two.".to_owned(),
            ..Default::default()
        })
    }

    fn expr(&self) -> &dyn Expr {
        &self.expr
    }

    fn description(&self) -> &str {
        "Corrects `their self` and `their selves` to `themself` and `themselves`."
    }
}

#[cfg(test)]
mod tests {
    use crate::linting::tests::{assert_no_lints, assert_suggestion_result};

    use super::Themself;

    #[test]
    fn fix_respect_their_self() {
        assert_suggestion_result(
            "You can't respect someone who doesn't respect their self.",
            Themself::default(),
            "You can't respect someone who doesn't respect themself.",
        );
    }

    #[test]
    fn dont_flag_self_hyphen() {
        assert_no_lints(
            "For most renaissance artists, their self-portraits are far from photorealistic.",
            Themself::default(),
        );
    }

    #[test]
    fn fix_their_selves() {
        assert_suggestion_result(
            "Constructedness: see \"people as forming and reforming their selves within each relationship\".",
            Themself::default(),
            "Constructedness: see \"people as forming and reforming themselves within each relationship\".",
        );
    }

    #[test]
    fn dont_flag_self_hosted() {
        assert_no_lints(
            "ZeroTier is certainly another viable alternative, but their self hosted option still relies on the ZeroTier root servers",
            Themself::default(),
        );
    }

    #[test]
    fn dont_flag_self_interest() {
        assert_no_lints(
            "this state that everyone is acting in their self interest is not something we should promote",
            Themself::default(),
        );
    }

    #[test]
    fn dont_flag_self_proclaimed() {
        assert_no_lints(
            "Which is why they feel threatened by anything that challenges their self proclaimed uniquely human trait.",
            Themself::default(),
        );
    }
}