harper-core 2.13.0

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

pub struct SuchShame {
    expr: SequenceExpr,
}

impl Default for SuchShame {
    fn default() -> Self {
        Self {
            expr: SequenceExpr::word_set(["is", "it's", "its", "that's", "thats", "was"])
                .t_ws()
                .then_word_seq(&["such", "shame"]),
        }
    }
}

impl ExprLinter for SuchShame {
    type Unit = Chunk;

    fn match_to_lint_with_context(
        &self,
        toks: &[Token],
        src: &[char],
        ctx: Option<(&[Token], &[Token])>,
    ) -> Option<Lint> {
        let (first, such, ws) = (0, 2, 3);
        let (first, such, ws) = (toks.get(first)?, toks.get(such)?, toks.get(ws)?);

        if first
            .get_ch(src)
            .eq_any_ignore_ascii_case_chars(&[&['i', 's'], &['w', 'a', 's']])
            && preceded_by_word(ctx, |t| {
                t.get_ch(src).eq_any_ignore_ascii_case_chars(&[
                    &['t', 'h', 'e', 'r', 'e'],
                    &['t', 'h', 'e', 'i', 'r'],
                ])
            })
        {
            return None;
        }

        let c = *such.get_ch(src).get(2)?;
        let a = (c as u8 - 2) as char;

        let mut new_chars = vec![a];
        new_chars.extend(ws.get_ch(src));

        Some(Lint {
            span: ws.span,
            lint_kind: LintKind::Usage,
            suggestions: vec![Suggestion::InsertAfter(new_chars)],
            message: "This expression requires `a shame`.".to_owned(),
            ..Default::default()
        })
    }

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

    fn description(&self) -> &str {
        "Corrects `such shame` to `such a shame`."
    }
}

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

    use super::SuchShame;

    #[test]
    fn its_apostrope() {
        assert_suggestion_result(
            "it's such shame that for large datasets the usage of this wonderful package becomes not pracitcal",
            SuchShame::default(),
            "it's such a shame that for large datasets the usage of this wonderful package becomes not pracitcal",
        );
    }

    #[test]
    fn it_is() {
        assert_suggestion_result(
            "It is such shame that the TV adaptation changed this fundamental part of the story for no reason",
            SuchShame::default(),
            "It is such a shame that the TV adaptation changed this fundamental part of the story for no reason",
        );
    }

    #[test]
    fn dont_flag_there_was() {
        assert_no_lints(
            "but I think there was such shame in that at the time most conversations on the subject would be ...",
            SuchShame::default(),
        );
    }

    #[test]
    fn dont_flag_there_is() {
        assert_no_lints(
            "There is such shame that seems to come from not being able to handle your house.",
            SuchShame::default(),
        );
    }

    #[test]
    fn it_was() {
        assert_suggestion_result(
            "It was such shame to lose such a talented composer.",
            SuchShame::default(),
            "It was such a shame to lose such a talented composer.",
        );
    }

    #[test]
    fn its_no_apostrophe() {
        assert_suggestion_result(
            "Its such shame to because this update had such potential",
            SuchShame::default(),
            "Its such a shame to because this update had such potential",
        );
    }

    #[test]
    fn dont_flag_their_was() {
        assert_no_lints(
            "Partially because their was such shame among black people if they had darker skin. ",
            SuchShame::default(),
        );
    }
}