use crate::{
Lint, Token,
char_ext::CharExt,
char_string::CharStringExt,
expr::{All, Expr, OwnedExprExt, SequenceExpr},
irregular_verbs::IrregularVerbs,
linting::{ExprLinter, LintKind, Suggestion, expr_linter::Chunk},
spell::Dictionary,
};
pub struct HelpedPast<D> {
expr: All,
dict: D,
}
impl<D: Dictionary> HelpedPast<D> {
pub fn new(dict: D) -> Self {
Self {
expr: SequenceExpr::any_capitalization_of("helped")
.t_ws()
.then_kind_where(|k| {
(k.is_verb_simple_past_form() || k.is_verb_past_form()) && !k.is_verb_lemma()
})
.but_not(SequenceExpr::anything().t_any().t_set(&["did", "got"])),
dict,
}
}
fn keep_suggestion_if_lemma(&self, suggs: &mut Vec<Vec<char>>, candidate: &[char]) {
if self
.dict
.get_word_metadata(candidate)
.is_some_and(|md| md.is_verb_lemma())
{
suggs.push(candidate.to_vec());
}
}
}
impl<D: Dictionary> ExprLinter for HelpedPast<D> {
type Unit = Chunk;
fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
let vspan = toks.last()?.span;
let vchars = vspan.get_content(src);
let vstr = vspan.get_content_string(src);
let mut suggs = vec![];
if vchars.ends_with_ignore_ascii_case_chars(&['d']) {
let without_d = &vchars[..vchars.len() - 1];
if without_d.ends_with_ignore_ascii_case_chars(&['e']) {
let without_ed = &without_d[..without_d.len() - 1];
self.keep_suggestion_if_lemma(&mut suggs, without_ed);
if without_ed.ends_with_ignore_ascii_case_chars(&['i']) {
let mut with_final_y = without_ed[..without_ed.len() - 1].to_vec();
with_final_y.push('y');
self.keep_suggestion_if_lemma(&mut suggs, &with_final_y);
}
if without_ed.last().is_some_and(|c| !c.is_vowel()) {
let without_doubled_consonant = without_ed[..without_ed.len() - 1].to_vec();
self.keep_suggestion_if_lemma(&mut suggs, &without_doubled_consonant);
}
}
self.keep_suggestion_if_lemma(&mut suggs, without_d);
}
if let Some(lemma) = IrregularVerbs::curated().get_lemma_for_preterite(&vstr) {
suggs.push(lemma.chars().collect());
}
if !suggs.is_empty() {
Some(Lint {
span: vspan,
lint_kind: LintKind::Grammar,
suggestions: suggs
.into_iter()
.map(|s| Suggestion::replace_with_match_case(s, vchars))
.collect(),
message: "Use the base form of the verb after \"helped\".".to_owned(),
..Default::default()
})
} else {
None
}
}
fn expr(&self) -> &dyn Expr {
&self.expr
}
fn description(&self) -> &str {
"Corrects past forms of verbs to their base form, when used after \"helped\"."
}
}
#[cfg(test)]
mod tests {
use crate::{
linting::tests::{assert_no_lints, assert_suggestion_result},
spell::FstDictionary,
};
use super::HelpedPast;
#[test]
fn that_i_helped_built() {
assert_suggestion_result(
"... as I have moved from CraftCMS into a startup that I helped built",
HelpedPast::new(FstDictionary::curated()),
"... as I have moved from CraftCMS into a startup that I helped build",
);
}
#[test]
fn i_helped_built_some() {
assert_suggestion_result(
"Strangely, thru some form of \"magic\" I helped built some hundreds of commercial T2 targets",
HelpedPast::new(FstDictionary::curated()),
"Strangely, thru some form of \"magic\" I helped build some hundreds of commercial T2 targets",
);
}
#[test]
fn has_helped_built() {
assert_suggestion_result(
"Dify is an LLM application development platform that has helped built over 100,000 applications.",
HelpedPast::new(FstDictionary::curated()),
"Dify is an LLM application development platform that has helped build over 100,000 applications.",
);
}
#[test]
fn who_helped_made() {
assert_suggestion_result(
"our many amazing contributors who helped made this possible",
HelpedPast::new(FstDictionary::curated()),
"our many amazing contributors who helped make this possible",
);
}
#[test]
fn has_helped_made() {
assert_suggestion_result(
"everyone who says that something I've worked on has helped made their lives easier",
HelpedPast::new(FstDictionary::curated()),
"everyone who says that something I've worked on has helped make their lives easier",
);
}
#[test]
fn i_helped_made() {
assert_suggestion_result(
"This is a a video game I helped made for a national competition in high school.",
HelpedPast::new(FstDictionary::curated()),
"This is a a video game I helped make for a national competition in high school.",
);
}
#[test]
fn helped_made_at_start() {
assert_suggestion_result(
"Spinner Tutorial - Helped made the roulette wheel come to life.",
HelpedPast::new(FstDictionary::curated()),
"Spinner Tutorial - Helped make the roulette wheel come to life.",
);
}
#[test]
fn helped_chose() {
assert_suggestion_result(
"The 2018 analysis helped chose tires (as well as validated prior choices)",
HelpedPast::new(FstDictionary::curated()),
"The 2018 analysis helped choose tires (as well as validated prior choices)",
);
}
#[test]
fn helped_created() {
assert_suggestion_result(
"React helped created a light weight dynamic webpage that can be implmented in various different applications.",
HelpedPast::new(FstDictionary::curated()),
"React helped create a light weight dynamic webpage that can be implmented in various different applications.",
);
}
#[test]
fn helped_debugged() {
assert_suggestion_result(
"Someone helped debugged this issue.",
HelpedPast::new(FstDictionary::curated()),
"Someone helped debug this issue.",
);
}
#[test]
fn helped_designed() {
assert_suggestion_result(
"when speaking with Ka Ming who has helped designed many of the UX/UI elements for the Security Plugin",
HelpedPast::new(FstDictionary::curated()),
"when speaking with Ka Ming who has helped design many of the UX/UI elements for the Security Plugin",
);
}
#[test]
fn helped_implemented() {
assert_suggestion_result(
"The way to do that, that I have helped implemented in some other addons already",
HelpedPast::new(FstDictionary::curated()),
"The way to do that, that I have helped implement in some other addons already",
);
}
#[test]
fn also_helped_improved() {
assert_suggestion_result(
"Also helped improved and scale billing infrastructure as customer growth accelerated especially in the enterprise space.",
HelpedPast::new(FstDictionary::curated()),
"Also helped improve and scale billing infrastructure as customer growth accelerated especially in the enterprise space.",
);
}
#[test]
fn has_helped_improved() {
assert_suggestion_result(
"Thanks for making Psalm, it's a fantastic tool and has helped improved our code quality massively.",
HelpedPast::new(FstDictionary::curated()),
"Thanks for making Psalm, it's a fantastic tool and has helped improve our code quality massively.",
);
}
#[test]
fn helped_picked() {
assert_suggestion_result(
"you helped picked the default",
HelpedPast::new(FstDictionary::curated()),
"you helped pick the default",
);
}
#[test]
fn helped_wrote() {
assert_suggestion_result(
"Disclosure: I didn't find the bugs. I helped wrote the blog post.",
HelpedPast::new(FstDictionary::curated()),
"Disclosure: I didn't find the bugs. I helped write the blog post.",
);
}
#[test]
fn dont_flag_shouldve_helped_given_that() {
assert_no_lints(
"However, it seems like 1 minute timeout should've helped given that default idle timeout is 10 minutes.",
HelpedPast::new(FstDictionary::curated()),
);
}
#[test]
fn dont_flag_be_helped_given_the() {
assert_no_lints(
"Major API breakages can't be helped given the nature of code generation",
HelpedPast::new(FstDictionary::curated()),
);
}
#[test]
fn dont_flag_helped_got() {
assert_no_lints(
"but along the way everyone who helped got +1 from me",
HelpedPast::new(FstDictionary::curated()),
);
}
#[test]
fn dont_flag_helped_did() {
assert_no_lints(
"So, that helped did it?",
HelpedPast::new(FstDictionary::curated()),
);
}
}