diff --git a/harper-core/src/linting/in_adjective_condition.rs b/harper-core/src/linting/in_adjective_condition.rs new file mode 100644 index 000000000..40f77e250 --- /dev/null +++ b/harper-core/src/linting/in_adjective_condition.rs @@ -0,0 +1,112 @@ +//! Linter for correcting common errors with the phrase "in [adjective] condition". + +use crate::{ + Token, + expr::{Expr, SequenceExpr}, + linting::{ExprLinter, Lint, LintKind, Suggestion}, + token_string_ext::TokenStringExt, +}; + +/// Linter that corrects common errors with phrases like "in good condition". +/// +/// Handles two cases: +/// 1. "in [a/an] [adjective] condition" -> "in [adjective] condition" +/// 2. "in [adjective] conditions" -> "in [adjective] condition" +pub struct InAdjectiveCondition { + expr: Box, +} + +impl Default for InAdjectiveCondition { + fn default() -> Self { + let singular = SequenceExpr::default() + .then_indefinite_article() + .t_ws() + .then_adjective() + .t_ws() + .t_aco("condition"); + + let plural = SequenceExpr::default() + .then_adjective() + .t_ws() + .t_aco("conditions"); + + Self { + expr: Box::new( + SequenceExpr::default() + .t_aco("in") + .t_ws() + .then_any_of(vec![Box::new(singular), Box::new(plural)]), + ), + } + } +} + +impl ExprLinter for InAdjectiveCondition { + fn expr(&self) -> &dyn Expr { + self.expr.as_ref() + } + + fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option { + let (span, sugg, msg) = match toks.len() { + 5 => ( + toks.last()?.span, + Suggestion::replace_with_match_case( + "condition".chars().collect(), + toks.last()?.span.get_content(src), + ), + "`Condition` should be singular.", + ), + 7 => ( + toks[1..3].span()?, + Suggestion::Remove, + "An indefinite article should not be used here.", + ), + _ => return None, + }; + + Some(Lint { + span, + lint_kind: LintKind::Grammar, + suggestions: vec![sugg], + message: msg.to_string(), + ..Default::default() + }) + } + + fn description(&self) -> &str { + "Corrects incorrect variants of `in good condition` with an indefinite article or plural." + } +} + +#[cfg(test)] +mod tests { + use super::InAdjectiveCondition; + use crate::linting::tests::assert_suggestion_result; + + #[test] + fn fix_in_good_conditions() { + assert_suggestion_result( + "in good conditions", + InAdjectiveCondition::default(), + "in good condition", + ); + } + + #[test] + fn fix_in_a_bad_condition() { + assert_suggestion_result( + "in a bad condition", + InAdjectiveCondition::default(), + "in bad condition", + ); + } + + #[test] + fn fix_great_condition_all_caps() { + assert_suggestion_result( + "YEAH IT'S IN GREAT CONDITIONS REALLY!", + InAdjectiveCondition::default(), + "YEAH IT'S IN GREAT CONDITION REALLY!", + ) + } +} diff --git a/harper-core/src/linting/lint_group.rs b/harper-core/src/linting/lint_group.rs index 5cd9c9a93..a57681f91 100644 --- a/harper-core/src/linting/lint_group.rs +++ b/harper-core/src/linting/lint_group.rs @@ -54,6 +54,7 @@ use super::hop_hope::HopHope; use super::how_to::HowTo; use super::hyphenate_number_day::HyphenateNumberDay; use super::i_am_agreement::IAmAgreement; +use super::in_adjective_condition::InAdjectiveCondition; use super::in_on_the_cards::InOnTheCards; use super::inflected_verb_after_to::InflectedVerbAfterTo; use super::interested_in::InterestedIn; @@ -446,6 +447,7 @@ impl LintGroup { insert_struct_rule!(HowTo, true); insert_expr_rule!(HyphenateNumberDay, true); insert_expr_rule!(IAmAgreement, true); + insert_expr_rule!(InAdjectiveCondition, true); insert_expr_rule!(InterestedIn, true); insert_struct_rule!(ItsContraction, true); insert_struct_rule!(ItsPossessive, true); diff --git a/harper-core/src/linting/mod.rs b/harper-core/src/linting/mod.rs index 20ff0a23e..ae49b9b02 100644 --- a/harper-core/src/linting/mod.rs +++ b/harper-core/src/linting/mod.rs @@ -49,6 +49,7 @@ mod hope_youre; mod how_to; mod hyphenate_number_day; mod i_am_agreement; +mod in_adjective_condition; mod in_on_the_cards; mod inflected_verb_after_to; mod initialism_linter; @@ -182,6 +183,7 @@ pub use hop_hope::HopHope; pub use how_to::HowTo; pub use hyphenate_number_day::HyphenateNumberDay; pub use i_am_agreement::IAmAgreement; +pub use in_adjective_condition::InAdjectiveCondition; pub use in_on_the_cards::InOnTheCards; pub use inflected_verb_after_to::InflectedVerbAfterTo; pub use initialism_linter::InitialismLinter;