use crate::{
CharStringExt, Lint, Token, TokenKind,
expr::{Expr, SequenceExpr},
linting::{
ExprLinter, Suggestion,
expr_linter::{Chunk, followed_by_word},
},
patterns::ModalVerb,
};
pub struct ModalBeAdjective {
expr: SequenceExpr,
}
impl Default for ModalBeAdjective {
fn default() -> Self {
Self {
expr: SequenceExpr::with(ModalVerb::default())
.t_ws()
.then_kind_is_but_isnt_any_of_except(
TokenKind::is_adjective,
&[
TokenKind::is_verb_lemma, TokenKind::is_adverb, TokenKind::is_preposition, TokenKind::is_determiner, TokenKind::is_pronoun, ] as &[_],
&[
"backup", "likely", ] as &[_],
),
}
}
}
impl ExprLinter for ModalBeAdjective {
type Unit = Chunk;
fn expr(&self) -> &dyn Expr {
&self.expr
}
fn match_to_lint_with_context(
&self,
toks: &[Token],
src: &[char],
ctx: Option<(&[Token], &[Token])>,
) -> Option<Lint> {
if followed_by_word(ctx, |nw| {
(nw.kind.is_noun()
&& !nw
.get_ch(src)
.eq_any_ignore_ascii_case_str(&["at", "by", "if"]))
|| (toks.last().unwrap().get_ch(src).eq_str("kind") && nw.get_ch(src).eq_str("of"))
}) {
return None;
}
Some(Lint {
span: toks[0].span,
suggestions: vec![Suggestion::InsertAfter(" be".chars().collect())],
message: "You may be missing the word `be` between this modal verb and adjective."
.to_string(),
..Default::default()
})
}
fn description(&self) -> &str {
"Looks for `be` missing between a modal verb and adjective."
}
}
#[cfg(test)]
mod tests {
use crate::linting::tests::{assert_no_lints, assert_suggestion_result};
use super::ModalBeAdjective;
#[test]
fn fix_would_nice() {
assert_suggestion_result(
"It would nice if Harper could detect this.",
ModalBeAdjective::default(),
"It would be nice if Harper could detect this.",
);
}
#[test]
fn fix_could_configured() {
assert_suggestion_result(
"It could configured by parameters and the commands above effectively disable it.",
ModalBeAdjective::default(),
"It could be configured by parameters and the commands above effectively disable it.",
);
}
#[test]
fn fix_will_accessible() {
assert_suggestion_result(
"Your WordPress site will accessible at http://localhost",
ModalBeAdjective::default(),
"Your WordPress site will be accessible at http://localhost",
);
}
#[test]
fn ignore_would_external_traffic() {
assert_no_lints(
"And why would external traffic be trying to access my server if I don't know who or what it is?",
ModalBeAdjective::default(),
)
}
#[test]
fn ignore_could_kind_of() {
assert_no_lints("you could kind of see the ...", ModalBeAdjective::default())
}
#[test]
#[ignore = "false positive: 'backup' is an adjective but also a spello for the verb 'back up'"]
fn ignore_you_can_backup() {
assert_no_lints("You can backup Userdata.", ModalBeAdjective::default());
}
#[test]
#[ignore = "false positive: 'incorrect' should be 'incorrectly'."]
fn ignore_would_incorrect() {
assert_no_lints(
"Bug in versions 4.0 and 4.1 would incorrect list the address module",
ModalBeAdjective::default(),
);
}
#[test]
#[ignore = "false positive: 'upper-bound' is an ad-hoc verb here."]
fn ignore_should_upper() {
assert_no_lints(
"we should upper-bound it to the next MAJOR version.",
ModalBeAdjective::default(),
);
assert_no_lints(
"some older software (filezilla on debian-stable) cannot passive-mode with TLS",
ModalBeAdjective::default(),
);
}
}