harper-core 2.0.0

The language checker for developers.
Documentation
use crate::{
    Token, TokenStringExt,
    expr::{Expr, SequenceExpr},
    patterns::{WhitespacePattern, WordSet},
};

use super::{ExprLinter, Lint, LintKind, Suggestion};
use crate::linting::expr_linter::Chunk;

pub struct WellEducated {
    expr: SequenceExpr,
}

impl Default for WellEducated {
    fn default() -> Self {
        let combined = WordSet::new(&["good-educated"]);

        let separated = SequenceExpr::default()
            .t_aco("good")
            .then_optional(WhitespacePattern)
            .then_hyphen()
            .then_optional(WhitespacePattern)
            .t_aco("educated");

        let expr = SequenceExpr::any_of(vec![Box::new(combined), Box::new(separated)]);

        Self { expr }
    }
}

impl ExprLinter for WellEducated {
    type Unit = Chunk;

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

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

        Some(Lint {
            span,
            lint_kind: LintKind::Miscellaneous,
            suggestions: vec![Suggestion::replace_with_match_case(
                "well-educated".chars().collect(),
                original,
            )],
            message: "Prefer `well-educated` for this compound.".into(),
            priority: 35,
        })
    }

    fn description(&self) -> &'static str {
        "Replaces `good-educated` with the accepted compound `well-educated`."
    }
}

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

    #[test]
    fn corrects_simple_sentence() {
        assert_suggestion_result(
            "She is good-educated.",
            WellEducated::default(),
            "She is well-educated.",
        );
    }

    #[test]
    fn corrects_in_clause() {
        assert_suggestion_result(
            "The panel found him good-educated and articulate.",
            WellEducated::default(),
            "The panel found him well-educated and articulate.",
        );
    }

    #[test]
    fn corrects_with_modifier() {
        assert_suggestion_result(
            "They considered her very good-educated for her age.",
            WellEducated::default(),
            "They considered her very well-educated for her age.",
        );
    }

    #[test]
    fn corrects_all_caps() {
        assert_suggestion_result(
            "Their mentors are GOOD-EDUCATED leaders.",
            WellEducated::default(),
            "Their mentors are WELL-EDUCATED leaders.",
        );
    }

    #[test]
    fn corrects_title_case() {
        assert_suggestion_result(
            "The report lauded Good-Educated Candidates.",
            WellEducated::default(),
            "The report lauded Well-Educated Candidates.",
        );
    }

    #[test]
    fn corrects_with_quotes() {
        assert_suggestion_result(
            "He called them \"good-educated\" professionals.",
            WellEducated::default(),
            "He called them \"well-educated\" professionals.",
        );
    }

    #[test]
    fn corrects_split_tokens() {
        assert_suggestion_result(
            "Their children are good - educated despite the odds.",
            WellEducated::default(),
            "Their children are well-educated despite the odds.",
        );
    }

    #[test]
    fn allows_well_educated() {
        assert_lint_count("She is well-educated.", WellEducated::default(), 0);
    }

    #[test]
    fn allows_good_education_phrase() {
        assert_lint_count(
            "They received a good education.",
            WellEducated::default(),
            0,
        );
    }

    #[test]
    fn allows_good_to_be_educated() {
        assert_lint_count(
            "It is good to be educated about local history.",
            WellEducated::default(),
            0,
        );
    }
}