harper-core 2.0.0

The language checker for developers.
Documentation
use harper_brill::UPOS;

use crate::expr::Expr;
use crate::expr::LongestMatchOf;
use crate::expr::OwnedExprExt;
use crate::expr::SequenceExpr;
use crate::linting::expr_linter::Chunk;
use crate::patterns::{ModalVerb, UPOSSet, WordSet};
use crate::{
    Token,
    linting::{ExprLinter, Lint, LintKind, Suggestion},
};

pub struct SafeToSave {
    expr: LongestMatchOf,
}

impl Default for SafeToSave {
    fn default() -> Self {
        let with_adv = SequenceExpr::with(ModalVerb::default())
            .then_whitespace()
            .then(UPOSSet::new(&[UPOS::ADV]))
            .then_whitespace()
            .t_aco("safe")
            .then_whitespace()
            .then_unless(WordSet::new(&["to"]));

        let without_adv = SequenceExpr::with(ModalVerb::default())
            .then_whitespace()
            .t_aco("safe")
            .then_whitespace()
            .then_unless(WordSet::new(&["to"]));

        let pattern = with_adv.or_longest(without_adv);

        Self { expr: pattern }
    }
}

impl ExprLinter for SafeToSave {
    type Unit = Chunk;

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

    fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
        let safe_idx = toks
            .iter()
            .position(|t| t.get_str(src).to_lowercase() == "safe")?;

        let safe_tok = &toks[safe_idx];

        Some(Lint {
            span: safe_tok.span,
            lint_kind: LintKind::WordChoice,
            suggestions: vec![Suggestion::ReplaceWith("save".chars().collect())],
            message: "The word `safe` is an adjective. Did you mean the verb `save`?".to_string(),
            priority: 57,
        })
    }

    fn description(&self) -> &str {
        "Detects `safe` (adjective) when `save` (verb) is intended after modal verbs like `could` or `should`."
    }
}

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

    #[test]
    fn corrects_could_safe() {
        assert_suggestion_result(
            "He could safe my life.",
            SafeToSave::default(),
            "He could save my life.",
        );
    }

    #[test]
    fn corrects_should_safe() {
        assert_suggestion_result(
            "You should safe your work frequently.",
            SafeToSave::default(),
            "You should save your work frequently.",
        );
    }

    #[test]
    fn corrects_will_safe() {
        assert_suggestion_result(
            "This will safe you time.",
            SafeToSave::default(),
            "This will save you time.",
        );
    }

    #[test]
    fn corrects_would_safe() {
        assert_suggestion_result(
            "It would safe us money.",
            SafeToSave::default(),
            "It would save us money.",
        );
    }

    #[test]
    fn corrects_can_safe() {
        assert_suggestion_result(
            "You can safe the document now.",
            SafeToSave::default(),
            "You can save the document now.",
        );
    }

    #[test]
    fn corrects_might_safe() {
        assert_suggestion_result(
            "This might safe the company.",
            SafeToSave::default(),
            "This might save the company.",
        );
    }

    #[test]
    fn corrects_must_safe() {
        assert_suggestion_result(
            "We must safe our resources.",
            SafeToSave::default(),
            "We must save our resources.",
        );
    }

    #[test]
    fn corrects_may_safe() {
        assert_suggestion_result(
            "You may safe your progress here.",
            SafeToSave::default(),
            "You may save your progress here.",
        );
    }

    #[test]
    fn corrects_with_adverb() {
        assert_suggestion_result(
            "You should definitely safe your changes.",
            SafeToSave::default(),
            "You should definitely save your changes.",
        );
    }

    #[test]
    fn corrects_shall_safe() {
        assert_suggestion_result(
            "We shall safe the nation.",
            SafeToSave::default(),
            "We shall save the nation.",
        );
    }

    #[test]
    fn corrects_couldnt_safe() {
        assert_suggestion_result(
            "I couldn't safe the file.",
            SafeToSave::default(),
            "I couldn't save the file.",
        );
    }

    #[test]
    fn allows_safe_to_verb() {
        assert_no_lints("It is safe to assume.", SafeToSave::default());
    }

    #[test]
    fn allows_safe_noun() {
        assert_no_lints("Put the money in the safe today.", SafeToSave::default());
    }

    #[test]
    fn allows_correct_save() {
        assert_no_lints("You should save your work.", SafeToSave::default());
    }
}