harper-core 2.0.0

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

pub struct RightClick {
    expr: ExprMap<usize>,
}

impl Default for RightClick {
    fn default() -> Self {
        let mut map = ExprMap::default();

        map.insert(
            SequenceExpr::word_set(&["right", "left", "middle"])
                .t_ws()
                .then(DerivedFrom::new_from_str("click")),
            0,
        );

        Self { expr: map }
    }
}

impl ExprLinter for RightClick {
    type Unit = Chunk;

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

    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
        let start_idx = *self.expr.lookup(0, matched_tokens, source)?;
        let click_idx = matched_tokens.len().checked_sub(1)?;
        let span = matched_tokens.get(start_idx..=click_idx)?.span()?;
        let template = span.get_content(source);

        let direction = matched_tokens.get(start_idx)?.get_ch(source);
        let click = matched_tokens.get(click_idx)?.get_ch(source);

        let replacement: Vec<char> = direction
            .iter()
            .copied()
            .chain(['-'])
            .chain(click.iter().copied())
            .collect();

        Some(Lint {
            span,
            lint_kind: LintKind::Punctuation,
            suggestions: vec![Suggestion::replace_with_match_case(replacement, template)],
            message: "Hyphenate this mouse command.".to_owned(),
            priority: 40,
        })
    }

    fn description(&self) -> &'static str {
        "Hyphenates right-click style mouse commands."
    }
}

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

    #[test]
    fn hyphenates_basic_command() {
        assert_suggestion_result(
            "Right click the icon.",
            RightClick::default(),
            "Right-click the icon.",
        );
    }

    #[test]
    fn hyphenates_with_preposition() {
        assert_suggestion_result(
            "Please right click on the link.",
            RightClick::default(),
            "Please right-click on the link.",
        );
    }

    #[test]
    fn hyphenates_past_tense() {
        assert_suggestion_result(
            "They right clicked the submit button.",
            RightClick::default(),
            "They right-clicked the submit button.",
        );
    }

    #[test]
    fn hyphenates_gerund() {
        assert_suggestion_result(
            "Right clicking the item highlights it.",
            RightClick::default(),
            "Right-clicking the item highlights it.",
        );
    }

    #[test]
    fn hyphenates_plural_noun() {
        assert_suggestion_result(
            "Right clicks are tracked in the log.",
            RightClick::default(),
            "Right-clicks are tracked in the log.",
        );
    }

    #[test]
    fn hyphenates_all_caps() {
        assert_suggestion_result(
            "He RIGHT CLICKED the file.",
            RightClick::default(),
            "He RIGHT-CLICKED the file.",
        );
    }

    #[test]
    fn hyphenates_left_click() {
        assert_suggestion_result(
            "Left click the checkbox.",
            RightClick::default(),
            "Left-click the checkbox.",
        );
    }

    #[test]
    fn hyphenates_middle_click() {
        assert_suggestion_result(
            "Middle click to open in a new tab.",
            RightClick::default(),
            "Middle-click to open in a new tab.",
        );
    }

    #[test]
    fn allows_hyphenated_form() {
        assert_lint_count("Right-click the icon.", RightClick::default(), 0);
    }

    #[test]
    fn ignores_unrelated_right_and_click() {
        assert_lint_count(
            "Click the right button to continue.",
            RightClick::default(),
            0,
        );
    }
}