harper-core 2.0.0

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

pub struct Theres {
    expr: SequenceExpr,
}

impl Default for Theres {
    fn default() -> Self {
        let expr = SequenceExpr::aco("their's").t_ws().then_kind_any_or_words(
            &[TokenKind::is_determiner, TokenKind::is_quantifier] as &[_],
            &["no", "enough"],
        );

        Self { expr }
    }
}

impl ExprLinter for Theres {
    type Unit = Chunk;

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

    fn match_to_lint(&self, tokens: &[Token], source: &[char]) -> Option<Lint> {
        let offender = tokens.first()?;
        let span = offender.span;
        let template = span.get_content(source);

        Some(Lint {
            span,
            lint_kind: LintKind::WordChoice,
            suggestions: vec![Suggestion::replace_with_match_case_str("there's", template)],
            message: "Use `there's`—the contraction of “there is”—for this construction.".into(),
            priority: 31,
        })
    }

    fn description(&self) -> &str {
        "Replaces the mistaken possessive `their's` before a determiner with the contraction `there's`."
    }
}

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

    #[test]
    fn corrects_lowercase_before_the() {
        assert_suggestion_result(
            "We realized their's the clue we missed.",
            Theres::default(),
            "We realized there's the clue we missed.",
        );
    }

    #[test]
    fn corrects_sentence_start() {
        assert_suggestion_result(
            "Their's the solution on the table.",
            Theres::default(),
            "There's the solution on the table.",
        );
    }

    #[test]
    fn corrects_before_no() {
        assert_suggestion_result(
            "I promise their's no extra charge.",
            Theres::default(),
            "I promise there's no extra charge.",
        );
    }

    #[test]
    fn corrects_before_an() {
        assert_suggestion_result(
            "I suspect their's an error in the log.",
            Theres::default(),
            "I suspect there's an error in the log.",
        );
    }

    #[test]
    fn corrects_before_a() {
        assert_suggestion_result(
            "Maybe their's a better route available.",
            Theres::default(),
            "Maybe there's a better route available.",
        );
    }

    #[test]
    fn corrects_before_another() {
        assert_suggestion_result(
            "Their's another round after this.",
            Theres::default(),
            "There's another round after this.",
        );
    }

    #[test]
    fn corrects_before_enough() {
        assert_suggestion_result(
            "Their's enough context in the report.",
            Theres::default(),
            "There's enough context in the report.",
        );
    }

    #[test]
    fn allows_possessive_pronoun_form() {
        assert_lint_count("Theirs is the final draft.", Theres::default(), 0);
    }

    #[test]
    fn ignores_without_determiner_afterward() {
        assert_lint_count("I think their's better already.", Theres::default(), 0);
    }

    #[test]
    fn ignores_correct_contraction() {
        assert_lint_count("There's a bright sign ahead.", Theres::default(), 0);
    }
}