harper-core 2.0.0

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

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

pub struct SomethingIs {
    expr: SequenceExpr,
}

impl Default for SomethingIs {
    fn default() -> Self {
        let forms = WordSet::new(&["somethings", "anythings", "everythings", "nothings"]);

        let expr = SequenceExpr::with(forms)
            .t_ws()
            .then_optional(SequenceExpr::default().then_one_or_more_adverbs().t_ws())
            .then_kind_any(&[TokenKind::is_verb_progressive_form]);

        Self { expr }
    }
}

impl ExprLinter for SomethingIs {
    type Unit = Chunk;

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

    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
        let offender = matched_tokens.first()?;
        let original = offender.get_ch(source);
        let stem_len = original.len().checked_sub(1)?;
        let stem = original[..stem_len].to_vec();

        let mut contraction = stem.clone();
        contraction.extend(['\'', 's']);

        let mut expanded = stem;
        expanded.push(' ');
        expanded.extend(['i', 's']);

        Some(Lint {
            span: offender.span,
            lint_kind: LintKind::WordChoice,
            suggestions: vec![
                Suggestion::replace_with_match_case(contraction, original),
                Suggestion::replace_with_match_case(expanded, original),
            ],
            message: "Prefer the contraction or full `is` rather than pluralizing this pronoun."
                .into(),
            priority: 31,
        })
    }

    fn description(&self) -> &str {
        "Flags forms like `somethings` before progressive verbs and suggests using `something's` or `something is`."
    }
}

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

    #[test]
    fn fixes_somethings_going() {
        assert_suggestion_result(
            "Somethings going well today.",
            SomethingIs::default(),
            "Something's going well today.",
        );
    }

    #[test]
    fn fixes_anythings_happening() {
        assert_suggestion_result(
            "Anythings happening tonight?",
            SomethingIs::default(),
            "Anything's happening tonight?",
        );
    }

    #[test]
    fn fixes_everythings_working() {
        assert_suggestion_result(
            "Everythings working smoothly.",
            SomethingIs::default(),
            "Everything's working smoothly.",
        );
    }

    #[test]
    fn fixes_nothings_changing() {
        assert_suggestion_result(
            "Nothings changing around here.",
            SomethingIs::default(),
            "Nothing's changing around here.",
        );
    }

    #[test]
    fn fixes_with_adverb() {
        assert_suggestion_result(
            "Somethings really happening now.",
            SomethingIs::default(),
            "Something's really happening now.",
        );
    }

    #[test]
    fn fixes_uppercase() {
        assert_suggestion_result(
            "SOMETHINGS HAPPENING NOW!",
            SomethingIs::default(),
            "SOMETHING'S HAPPENING NOW!",
        );
    }

    #[test]
    fn offers_is_expansion() {
        assert_suggestion_result(
            "Somethings going wrong.",
            SomethingIs::default(),
            "Something is going wrong.",
        );
    }

    #[test]
    fn no_lint_when_contracted() {
        assert_no_lints("Something's going well today.", SomethingIs::default());
    }

    #[test]
    fn no_lint_when_plural_noun() {
        assert_lint_count(
            "Somethings in the attic kept us awake.",
            SomethingIs::default(),
            0,
        );
    }

    #[test]
    fn no_lint_at_sentence_end() {
        assert_no_lints("Somethings.", SomethingIs::default());
    }
}