use crate::linting::expr_linter::Chunk;
use crate::{
Token, TokenKind, TokenStringExt,
expr::{Expr, ExprMap, SequenceExpr},
linting::{ExprLinter, Lint, LintKind, Suggestion},
};
pub struct DoubleClick {
expr: ExprMap<usize>,
}
impl DoubleClick {
fn double_click_sequence() -> SequenceExpr {
SequenceExpr::default()
.t_aco("double")
.t_ws()
.then_word_set(&["click", "clicked", "clicking", "clicks"])
}
}
impl Default for DoubleClick {
fn default() -> Self {
let mut map = ExprMap::default();
map.insert(
SequenceExpr::default()
.then_seq(Self::double_click_sequence())
.t_ws()
.then_any_word(),
0,
);
map.insert(
SequenceExpr::default()
.then_seq(Self::double_click_sequence())
.then_punctuation(),
0,
);
map.insert(
SequenceExpr::default()
.then_seq(Self::double_click_sequence())
.t_ws()
.then_kind_is_but_is_not(TokenKind::is_word, TokenKind::is_verb),
0,
);
Self { expr: map }
}
}
impl ExprLinter for DoubleClick {
type Unit = Chunk;
fn expr(&self) -> &dyn Expr {
&self.expr
}
fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
let double_idx = *self.expr.lookup(0, matched_tokens, source)?;
let click_idx = 2;
let span = matched_tokens.get(double_idx..=click_idx)?.span()?;
let template = span.get_content(source);
let double_word = matched_tokens.get(double_idx)?.get_ch(source);
let click_word = matched_tokens.get(click_idx)?.get_ch(source);
let replacement: Vec<char> = double_word
.iter()
.copied()
.chain(['-'])
.chain(click_word.iter().copied())
.collect();
Some(Lint {
span,
lint_kind: LintKind::Punctuation,
suggestions: vec![Suggestion::replace_with_match_case(replacement, template)],
message: "Add a hyphen to this command.".to_owned(),
priority: 40,
})
}
fn description(&self) -> &'static str {
"Encourages hyphenating `double-click` and its inflections."
}
}
#[cfg(test)]
mod tests {
use super::DoubleClick;
use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
#[test]
fn corrects_basic_command() {
assert_suggestion_result(
"Double click the icon.",
DoubleClick::default(),
"Double-click the icon.",
);
}
#[test]
fn corrects_with_preposition() {
assert_suggestion_result(
"Please double click on the link.",
DoubleClick::default(),
"Please double-click on the link.",
);
}
#[test]
fn corrects_with_pronoun() {
assert_suggestion_result(
"You should double click it to open.",
DoubleClick::default(),
"You should double-click it to open.",
);
}
#[test]
fn corrects_plural_form() {
assert_suggestion_result(
"Double clicks are recorded in the log.",
DoubleClick::default(),
"Double-clicks are recorded in the log.",
);
}
#[test]
fn corrects_past_tense() {
assert_suggestion_result(
"They double clicked the submit button.",
DoubleClick::default(),
"They double-clicked the submit button.",
);
}
#[test]
fn corrects_gerund() {
assert_suggestion_result(
"Double clicking the item highlights it.",
DoubleClick::default(),
"Double-clicking the item highlights it.",
);
}
#[test]
fn corrects_with_caps() {
assert_suggestion_result(
"He DOUBLE CLICKED the file.",
DoubleClick::default(),
"He DOUBLE-CLICKED the file.",
);
}
#[test]
fn corrects_multiline() {
assert_suggestion_result(
"Double\nclick the checkbox.",
DoubleClick::default(),
"Double-click the checkbox.",
);
}
#[test]
fn corrects_at_sentence_end() {
assert_suggestion_result(
"Just double click.",
DoubleClick::default(),
"Just double-click.",
);
}
#[test]
fn allows_hyphenated_form() {
assert_lint_count("Double-click the icon.", DoubleClick::default(), 0);
}
#[test]
fn ignores_other_double_words() {
assert_lint_count(
"She said the double rainbow was beautiful.",
DoubleClick::default(),
0,
);
}
}