use super::{Lint, LintKind, Linter, Suggestion};
use crate::{Document, Span, TokenStringExt};
#[derive(Debug, Clone, Copy, Default)]
pub struct AdjectiveOfA;
const ADJECTIVE_WHITELIST: &[&str] = &["bad", "big", "good", "large", "long", "vague"];
const CONTEXT_WORDS: &[&str] = &[
"as", "how", "that", "this", "too",
];
const ADJECTIVE_BLACKLIST: &[&str] = &["much", "part"];
fn has_context_word(document: &Document, adj_idx: usize) -> bool {
if adj_idx < 2 {
return false;
}
if let Some(space_token) = document.get_token(adj_idx - 1) {
if !space_token.kind.is_whitespace() {
return false;
}
if let Some(word_token) = document.get_token(adj_idx - 2) {
if !word_token.kind.is_word() {
return false;
}
let word = document.get_span_content_str(&word_token.span);
return CONTEXT_WORDS.iter().any(|&w| w.eq_ignore_ascii_case(&word));
}
}
false
}
fn is_good_adjective(word: &str) -> bool {
ADJECTIVE_WHITELIST
.iter()
.any(|&adj| word.eq_ignore_ascii_case(adj))
}
fn is_bad_adjective(word: &str) -> bool {
ADJECTIVE_BLACKLIST
.iter()
.any(|&adj| word.eq_ignore_ascii_case(adj))
}
impl Linter for AdjectiveOfA {
fn lint(&mut self, document: &Document) -> Vec<Lint> {
let mut lints = Vec::new();
for i in document.iter_adjective_indices() {
let adjective = document.get_token(i).unwrap();
let space_1 = document.get_token(i + 1);
let word_of = document.get_token(i + 2);
let space_2 = document.get_token(i + 3);
let a_or_an = document.get_token(i + 4);
let adj_str = document
.get_span_content_str(&adjective.span)
.to_lowercase();
if !is_good_adjective(&adj_str) && !has_context_word(document, i) {
continue;
}
if is_bad_adjective(&adj_str) {
continue;
}
if adj_str.ends_with("er") || adj_str.ends_with("st") {
continue;
}
if adj_str.ends_with("ing") && (adjective.kind.is_noun() || adjective.kind.is_verb()) {
continue;
}
if space_1.is_none() || word_of.is_none() || space_2.is_none() || a_or_an.is_none() {
continue;
}
let space_1 = space_1.unwrap();
if !space_1.kind.is_whitespace() {
continue;
}
let word_of = word_of.unwrap();
if !word_of.kind.is_word() {
continue;
}
let word_of = document.get_span_content_str(&word_of.span).to_lowercase();
if word_of != "of" {
continue;
}
let space_2 = space_2.unwrap();
if !space_2.kind.is_whitespace() {
continue;
}
let a_or_an = a_or_an.unwrap();
if !a_or_an.kind.is_word() {
continue;
}
let a_or_an_str = document.get_span_content_str(&a_or_an.span).to_lowercase();
if a_or_an_str != "a" && a_or_an_str != "an" {
continue;
}
let mut sugg_1 = Vec::new();
sugg_1.extend_from_slice(document.get_span_content(&adjective.span));
sugg_1.extend_from_slice(document.get_span_content(&space_1.span));
sugg_1.extend_from_slice(document.get_span_content(&a_or_an.span));
let mut sugg_2 = Vec::new();
sugg_2.extend_from_slice(document.get_span_content(&adjective.span));
sugg_2.extend_from_slice(document.get_span_content(&space_2.span));
sugg_2.extend_from_slice(document.get_span_content(&a_or_an.span));
let mut suggestions = vec![Suggestion::ReplaceWith(sugg_1.clone())];
if sugg_1 != sugg_2 {
suggestions.push(Suggestion::ReplaceWith(sugg_2));
}
lints.push(Lint {
span: Span::new(adjective.span.start, a_or_an.span.end),
lint_kind: LintKind::Style,
suggestions,
message: "The word `of` is not needed here.".to_string(),
priority: 63,
});
}
lints
}
fn description(&self) -> &str {
"This rule looks for sequences of words of the form `adjective of a`."
}
}
#[cfg(test)]
mod tests {
use super::AdjectiveOfA;
use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
#[test]
fn correct_large_of_a() {
assert_suggestion_result(
"Yeah I'm using as large of a batch size as I can on this machine",
AdjectiveOfA,
"Yeah I'm using as large a batch size as I can on this machine",
)
}
#[test]
fn correct_bad_of_an() {
assert_suggestion_result(
"- If forking is really that bad of an option, let's first decide where to put this.",
AdjectiveOfA,
"- If forking is really that bad an option, let's first decide where to put this.",
);
}
#[test]
fn dont_flag_comparative() {
assert_lint_count(
"I only worked with custom composer installers for the better of a day, so please excuse me if I missed a thing.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_superlative() {
assert_lint_count(
"I am trying to use composites to visualize the worst of a set of metrics.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_kind() {
assert_lint_count(
"Log.txt file automatic creation in PWD is kind of an anti-feature",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_part() {
assert_lint_count(
"cannot delete a food that is no longer part of a recipe",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_much() {
assert_lint_count(
"How much of a performance impact when switching from rails to rails-api ?",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_part_uppercase() {
assert_lint_count(
"Quarkus Extension as Part of a Project inside a Monorepo?",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_all_of() {
assert_lint_count(
"This repository is deprecated. All of its content and history has been moved.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_inside() {
assert_lint_count(
"Michael and Brock sat inside of a diner in Brandon",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_out() {
assert_lint_count(
"not only would he potentially be out of a job and back to sort of poverty",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_full() {
assert_lint_count(
"fortunately I happen to have this Tupperware full of an unceremoniously disassembled LED Mac Mini",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_something() {
assert_lint_count(
"Well its popularity seems to be taking something of a dip right now.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_short() {
assert_lint_count(
"I found one Youtube short of an indonesian girl.",
AdjectiveOfA,
0,
)
}
#[test]
fn dont_flag_bottom() {
assert_lint_count(
"When leaves are just like coming out individually from the bottom of a fruit.",
AdjectiveOfA,
0,
)
}
#[test]
fn dont_flag_left() {
assert_lint_count("and what is left of a 12vt coil", AdjectiveOfA, 0)
}
#[test]
fn dont_flag_full_uppercase() {
assert_lint_count("Full of a bunch varnish like we get.", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_head() {
assert_lint_count(
"You need to get out if you're the head of an education department and you're not using AI",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_middle() {
assert_lint_count(
"just to get to that part in the middle of a blizzard",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_chance() {
assert_lint_count(
"products that you overpay for because there are subtle details in the terms and conditions that reduce the size or chance of a payout.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_potential() {
assert_lint_count(
"People that are happy to accept it for the potential of a reward.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_sound() {
assert_lint_count("the sound of an approaching Krampus", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_rid() {
assert_lint_count("I need to get rid of a problem", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_precision() {
assert_lint_count(
"a man whose crew cut has the precision of a targeted drone strike",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_back() {
assert_lint_count(
"a man whose crew cut has the back of a targeted drone strike",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_emblematic() {
assert_lint_count(
"... situation was emblematic of a publication that ...",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_half() {
assert_lint_count("And now I only have half of a CyberTruck", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_bit() {
assert_lint_count("we ran into a bit of an issue", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_dream() {
assert_lint_count("When the dream of a united Europe began", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_beginning() {
assert_lint_count("That's the beginning of a conversation.", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_side() {
assert_lint_count(
"it hit the barrier on the side of a highway",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_derivative() {
assert_lint_count(
"Techniques for evaluating the *partial derivative of a function",
AdjectiveOfA,
0,
)
}
#[test]
fn dont_flag_equivalent() {
assert_lint_count(
"Rust's equivalent of a switch statement is a match expression",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_up() {
assert_lint_count(
"Yeah gas is made up of a bunch of teenytiny particles all moving around.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_eighth() {
assert_lint_count(
"It's about an eighth of an inch or whatever",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_shy() {
assert_lint_count(
"... or just shy of a third of the country's total trade deficit.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_fun() {
assert_lint_count(
"Remember that $4,000 Hermes horse bag I was making fun of a little while ago.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_off() {
assert_lint_count(
"can't identify a person based off of an IP from 10 years ago",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_borderline_of() {
assert_lint_count(
"it's very very on the borderline of a rock pop ballad",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_light() {
assert_lint_count("The light of a star.", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_multiple() {
assert_lint_count(
"The image needs to be a multiple of a certain size.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_red() {
assert_lint_count("The red of a drop of blood.", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_top() {
assert_lint_count("The top of a hill.", AdjectiveOfA, 0);
}
#[test]
fn dont_flag_slack() {
assert_lint_count(
"They've been picking up the slack of a federal government mostly dominated by whatever this is.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_illustrative() {
assert_lint_count(
"Yet, the fact that they clearly give a one-sided account of most of their case studies is illustrative of a bias.",
AdjectiveOfA,
0,
);
}
#[test]
fn dont_flag_perspective() {
assert_lint_count(
"I always assess software by looking at it from the perspective of a new user.",
AdjectiveOfA,
0,
);
}
#[test]
fn correct_too_large_of_a() {
assert_suggestion_result(
"Warn users if setting too large of a session object",
AdjectiveOfA,
"Warn users if setting too large a session object",
)
}
#[test]
fn correct_too_long_of_a() {
assert_suggestion_result(
"An Org Role with Too Long of a Name Hides Delete Option",
AdjectiveOfA,
"An Org Role with Too Long a Name Hides Delete Option",
)
}
#[test]
fn correct_too_big_of_a() {
assert_suggestion_result(
"StepButton has too big of a space to click",
AdjectiveOfA,
"StepButton has too big a space to click",
)
}
#[test]
fn correct_too_vague_of_a() {
assert_suggestion_result(
"\"No Speech provider is registered.\" is too vague of an error",
AdjectiveOfA,
"\"No Speech provider is registered.\" is too vague an error",
)
}
#[test]
fn correct_too_dumb_of_a() {
assert_suggestion_result(
"Hopefully this isn't too dumb of a question.",
AdjectiveOfA,
"Hopefully this isn't too dumb a question.",
)
}
#[test]
fn correct_how_important_of_a() {
assert_suggestion_result(
"This should tell us how important of a use case that is and how often writing a type literal in a case is deliberate.",
AdjectiveOfA,
"This should tell us how important a use case that is and how often writing a type literal in a case is deliberate.",
)
}
#[test]
fn correct_that_rare_of_an() {
assert_suggestion_result(
"so making changes isn't that rare of an occurrence for me.",
AdjectiveOfA,
"so making changes isn't that rare an occurrence for me.",
)
}
#[test]
fn correct_as_important_of_a() {
assert_suggestion_result(
"Might be nice to have it draggable from other places as well, but not as important of a bug anymore.",
AdjectiveOfA,
"Might be nice to have it draggable from other places as well, but not as important a bug anymore.",
)
}
#[test]
fn correct_too_short_of_a() {
assert_suggestion_result(
"I login infrequently as well and 6 months is too short of a time.",
AdjectiveOfA,
"I login infrequently as well and 6 months is too short a time.",
)
}
#[test]
fn correct_that_common_of_a() {
assert_suggestion_result(
"that common of a name for a cluster role its hard to rule out",
AdjectiveOfA,
"that common a name for a cluster role its hard to rule out",
)
}
#[test]
fn correct_as_great_of_an() {
assert_suggestion_result(
"the w factor into the u factor to as great of an extent as possible.",
AdjectiveOfA,
"the w factor into the u factor to as great an extent as possible.",
)
}
#[test]
fn correct_too_uncommon_of_a() {
assert_suggestion_result(
"but this is probably too uncommon of a practice to be the default",
AdjectiveOfA,
"but this is probably too uncommon a practice to be the default",
)
}
}