harper-core 2.0.0

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

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

pub struct DoubleModal {
    expr: SequenceExpr,
}

impl Default for DoubleModal {
    fn default() -> Self {
        let expr = SequenceExpr::with(ModalVerb::default())
            .t_ws()
            .then(ModalVerb::default());

        Self { expr }
    }
}

impl ExprLinter for DoubleModal {
    type Unit = Chunk;

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

    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
        let first_chars = matched_tokens.first()?.get_ch(source);
        let second_chars = matched_tokens.last()?.get_ch(source);

        Some(Lint {
            span: matched_tokens.span()?,
            lint_kind: LintKind::Miscellaneous,
            suggestions: vec![
                Suggestion::ReplaceWith(first_chars.into()),
                Suggestion::ReplaceWith(second_chars.into()),
            ],
            message: "Two modal verbs in a row are rarely grammatical; remove one.".to_string(),
            priority: 31,
        })
    }

    fn description(&self) -> &'static str {
        "Two modal verbs in a row are rarely grammatical; remove one of them."
    }
}

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

    #[test]
    fn detects_might_could() {
        assert_lint_count(
            "They might could finish the project by Friday.",
            DoubleModal::default(),
            1,
        );
    }

    #[test]
    fn detects_should_ought() {
        assert_lint_count("You should ought to apologize.", DoubleModal::default(), 1);
    }

    #[test]
    fn allows_single_modal() {
        assert_lint_count("She must leave early.", DoubleModal::default(), 0);
    }

    #[test]
    fn detects_two_double_modals() {
        assert_lint_count(
            "He may can join us, and you might could too.",
            DoubleModal::default(),
            2,
        );
    }

    #[test]
    fn suggests_removing_second_modal_keeps_first() {
        assert_suggestion_result(
            "They might could finish the project by Friday.",
            DoubleModal::default(),
            "They might finish the project by Friday.",
        );
    }

    #[test]
    fn suggests_removing_second_modal_keeps_first_variant_order() {
        assert_suggestion_result(
            "You could might want to double-check that.",
            DoubleModal::default(),
            "You could want to double-check that.",
        );
    }

    #[test]
    fn suggests_removing_second_modal_keeps_first_capitalised() {
        assert_suggestion_result(
            "We Must Should be consistent.",
            DoubleModal::default(),
            "We Must be consistent.",
        );
    }

    #[test]
    fn allows_will_need() {
        assert_no_lints(
            "You will need administrator or editor level access",
            DoubleModal::default(),
        );
    }
}