use harper_brill::UPOS;
use crate::linting::expr_linter::Chunk;
use crate::{
Token, TokenKind,
expr::{All, Expr, OwnedExprExt, SequenceExpr},
linting::{ExprLinter, Lint, LintKind, Suggestion},
patterns::{InflectionOfBe, NominalPhrase, UPOSSet},
};
pub struct HowTo {
expr: All,
}
impl Default for HowTo {
fn default() -> Self {
let mut pattern = All::default();
let pos_pattern = SequenceExpr::anything()
.then_anything()
.t_aco("how")
.then_whitespace()
.then_verb_lemma();
pattern.add(pos_pattern);
let finite_clause_verb = SequenceExpr::whitespace().then_kind_any(&[
TokenKind::is_auxiliary_verb,
TokenKind::is_verb_third_person_singular_present_form,
TokenKind::is_verb_simple_past_form,
] as &[_]);
let noun_led_clause = SequenceExpr::default()
.then_kind_either(TokenKind::is_nominal, TokenKind::is_proper_noun)
.then_any_of(vec![
Box::new(finite_clause_verb),
Box::new(
SequenceExpr::whitespace()
.then_kind_any(&[
TokenKind::is_noun,
TokenKind::is_proper_noun,
TokenKind::is_verb_progressive_form,
] as &[_])
.then_seq(SequenceExpr::whitespace().then(
InflectionOfBe::new().or(SequenceExpr::default().then_auxiliary_verb()),
)),
),
Box::new(
SequenceExpr::whitespace()
.t_aco("to")
.then_whitespace()
.then(NominalPhrase)
.then_seq(SequenceExpr::whitespace().then_kind_any(&[
TokenKind::is_auxiliary_verb,
TokenKind::is_verb_third_person_singular_present_form,
TokenKind::is_verb_simple_past_form,
]
as &[_])),
),
]);
let exceptions = SequenceExpr::unless(UPOSSet::new(&[UPOS::PART]))
.then_anything()
.then_unless(|tok: &Token, _: &[char]| tok.kind.is_np_member())
.then_anything()
.then_unless(
InflectionOfBe::new()
.or(SequenceExpr::default().then_kind_any_or_words(
&[
TokenKind::is_auxiliary_verb,
TokenKind::is_adjective,
TokenKind::is_conjunction,
TokenKind::is_proper_noun,
] as &[_],
&["did", "come", "does"],
))
.or(noun_led_clause),
);
pattern.add(exceptions);
Self { expr: pattern }
}
}
impl ExprLinter for HowTo {
type Unit = Chunk;
fn expr(&self) -> &dyn Expr {
&self.expr
}
fn match_to_lint(&self, toks: &[Token], _src: &[char]) -> Option<Lint> {
let fix: Vec<char> = " to".chars().collect();
Some(Lint {
span: toks[2].span,
lint_kind: LintKind::WordChoice,
suggestions: vec![Suggestion::InsertAfter(fix)],
message: "Insert `to` after `how` (e.g., `how to clone`).".into(),
priority: 63,
})
}
fn description(&self) -> &str {
"Detects the omission of `to` in constructions like `how clone / how install` and suggests `how to …`."
}
}
#[cfg(test)]
mod tests {
use super::HowTo;
use crate::linting::tests::{assert_lint_count, assert_no_lints, assert_suggestion_result};
#[test]
fn flags_missing_to() {
assert_suggestion_result(
"Here's how clone the repository.",
HowTo::default(),
"Here's how to clone the repository.",
);
}
#[test]
fn ignores_correct_phrase() {
assert_lint_count("Here's how to clone the repository.", HowTo::default(), 0);
}
#[test]
fn flags_other_verbs() {
assert_suggestion_result(
"Learn how install Rust.",
HowTo::default(),
"Learn how to install Rust.",
);
}
#[test]
fn ros_package_install() {
assert_suggestion_result(
"Can someone explain how install this ROS package on Humble?",
HowTo::default(),
"Can someone explain how to install this ROS package on Humble?",
);
}
#[test]
fn extract_and_install_app() {
assert_suggestion_result(
"Here’s a quick guide on how install an app you’ve extracted from a tarball.",
HowTo::default(),
"Here’s a quick guide on how to install an app you’ve extracted from a tarball.",
);
}
#[test]
fn dll_files() {
assert_suggestion_result(
"This video shows how fix missing DLL files on Windows.",
HowTo::default(),
"This video shows how to fix missing DLL files on Windows.",
);
}
#[test]
fn dofus_on_ubuntu() {
assert_suggestion_result(
"Full tutorial on how install Dofus under Ubuntu.",
HowTo::default(),
"Full tutorial on how to install Dofus under Ubuntu.",
);
}
#[test]
fn tar_gz_install() {
assert_suggestion_result(
"Find out how install software shipped as a .tar.gz archive.",
HowTo::default(),
"Find out how to install software shipped as a .tar.gz archive.",
);
}
#[test]
fn thrift_libraries() {
assert_suggestion_result(
"Anyone know how install the Thrift libraries from source?",
HowTo::default(),
"Anyone know how to install the Thrift libraries from source?",
);
}
#[test]
fn windows_adk() {
assert_suggestion_result(
"Lost the Windows ADK again—remind me how install it?",
HowTo::default(),
"Lost the Windows ADK again—remind me how to install it?",
);
}
#[test]
fn accounting_errors() {
assert_suggestion_result(
"Eight common accounting errors and how fix them.",
HowTo::default(),
"Eight common accounting errors and how to fix them.",
);
}
#[test]
fn sentence_fragments() {
assert_suggestion_result(
"Here’s what sentence fragments are and how fix them.",
HowTo::default(),
"Here’s what sentence fragments are and how to fix them.",
);
}
#[test]
fn zipper_slider() {
assert_suggestion_result(
"Quick demo on how fix a broken zipper slider.",
HowTo::default(),
"Quick demo on how to fix a broken zipper slider.",
);
}
#[test]
fn door_lock() {
assert_suggestion_result(
"Tips on how fix a door that won’t lock.",
HowTo::default(),
"Tips on how to fix a door that won’t lock.",
);
}
#[test]
fn already_correct_install() {
assert_lint_count(
"See how to install the package with apt.",
HowTo::default(),
0,
);
}
#[test]
fn already_correct_fix() {
assert_lint_count(
"He showed me how to fix the zipper in ten minutes.",
HowTo::default(),
0,
);
}
#[test]
fn how_are_you() {
assert_lint_count("How are you?", HowTo::default(), 0);
}
#[test]
fn how_calm_you_are() {
assert_lint_count("I like how calm you are.", HowTo::default(), 0);
}
#[test]
fn how_will_you_make_up() {
assert_lint_count(
"How will you make up for your mistakes?",
HowTo::default(),
0,
);
}
#[test]
fn storytelling_clause() {
assert_lint_count(
"I will tell about how leaving my husband led to my dog winning a Nobel Prize.",
HowTo::default(),
0,
);
}
#[test]
fn dont_flag_how_did_you() {
assert_lint_count("How did you get to school every day?", HowTo::default(), 0);
}
#[test]
fn dont_flag_how_come() {
assert_lint_count(
"How come this has to be a special case?",
HowTo::default(),
0,
);
}
#[test]
fn allows_how_has() {
assert_lint_count("How Has This Been Tested?", HowTo::default(), 0);
}
#[test]
fn issue_1492() {
assert_no_lints(
"I hope to provide some insight into correct HTML formatting, in addition to how authors can avoid these issues.",
HowTo::default(),
);
assert_no_lints("But how does something like this...", HowTo::default());
}
#[test]
fn allow_issue_1298() {
assert_no_lints(
"The story of how and why things came to this point.",
HowTo::default(),
);
}
#[test]
fn dont_flag_false_positive_pr_1846() {
assert_no_lints(
"About how Microsoft, Google, and others are training people in Rust.",
HowTo::default(),
)
}
#[test]
fn dont_flag_false_positives_1492_how_indexes() {
assert_no_lints(
"controls how indexes will be added to unwrapped keys of flat array-like objects",
HowTo::default(),
);
}
#[test]
fn dont_flag_how_proper_noun_handles() {
assert_no_lints("rewrites how Wine handles file locking", HowTo::default());
}
#[test]
fn dont_flag_how_work_is_structured() {
assert_no_lints(
"They need to rethink how work is structured and valued.",
HowTo::default(),
);
}
#[test]
fn dont_flag_how_form_submissions_are_delivered() {
assert_no_lints(
"The Mail tab dictates how form submissions are delivered to you.",
HowTo::default(),
);
}
#[test]
fn dont_flag_how_access_to_food_shaped_revolutions() {
assert_no_lints(
"We analyze how access to food shaped political ideologies.",
HowTo::default(),
);
}
#[test]
fn issue_2124() {
assert_no_lints(
"I like how discord shows Spotify status on your profile.",
HowTo::default(),
);
assert_no_lints(
"To be determined based on how error handling is done in new paradigm.",
HowTo::default(),
);
}
}