Skip to content

Commit 21888aa

Browse files
feat: flag redundant series of "also", "as well", "too" (#1514)
This uncovered a bug in `SequenceExpr` when using `.then_optional()` which is hopefully now fixed. Co-authored-by: Elijah Potter <[email protected]>
1 parent d350add commit 21888aa

File tree

3 files changed

+181
-0
lines changed

3 files changed

+181
-0
lines changed

harper-core/src/linting/lint_group.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ use super::pronoun_contraction::PronounContraction;
7676
use super::pronoun_inflection_be::PronounInflectionBe;
7777
use super::pronoun_knew::PronounKnew;
7878
use super::proper_noun_capitalization_linters;
79+
use super::redundant_additive_adverbs::RedundantAdditiveAdverbs;
7980
use super::regionalisms::Regionalisms;
8081
use super::repeated_words::RepeatedWords;
8182
use super::save_to_safe::SaveToSafe;
@@ -437,6 +438,7 @@ impl LintGroup {
437438
insert_expr_rule!(PossessiveYour, true);
438439
insert_struct_rule!(PronounContraction, true);
439440
insert_struct_rule!(PronounKnew, true);
441+
insert_expr_rule!(RedundantAdditiveAdverbs, true);
440442
insert_struct_rule!(RepeatedWords, true);
441443
insert_struct_rule!(SaveToSafe, true);
442444
insert_expr_rule!(SinceDuration, true);

harper-core/src/linting/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ mod pronoun_contraction;
8484
mod pronoun_inflection_be;
8585
mod pronoun_knew;
8686
mod proper_noun_capitalization_linters;
87+
mod redundant_additive_adverbs;
8788
mod regionalisms;
8889
mod repeated_words;
8990
mod save_to_safe;
@@ -181,6 +182,7 @@ pub use possessive_noun::PossessiveNoun;
181182
pub use possessive_your::PossessiveYour;
182183
pub use pronoun_contraction::PronounContraction;
183184
pub use pronoun_inflection_be::PronounInflectionBe;
185+
pub use redundant_additive_adverbs::RedundantAdditiveAdverbs;
184186
pub use regionalisms::Regionalisms;
185187
pub use repeated_words::RepeatedWords;
186188
pub use save_to_safe::SaveToSafe;
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
use crate::{
2+
Lrc, Token, TokenStringExt,
3+
expr::{Expr, FirstMatchOf, FixedPhrase, SequenceExpr},
4+
linting::{ExprLinter, Lint, LintKind, Suggestion},
5+
patterns::WordSet,
6+
};
7+
8+
pub struct RedundantAdditiveAdverbs {
9+
expr: Box<dyn Expr>,
10+
}
11+
12+
impl Default for RedundantAdditiveAdverbs {
13+
fn default() -> Self {
14+
let also_too = WordSet::new(&["also", "too"]);
15+
let as_well = FixedPhrase::from_phrase("as well");
16+
17+
let additive_adverb = Lrc::new(FirstMatchOf::new(vec![
18+
Box::new(also_too),
19+
Box::new(as_well),
20+
]));
21+
22+
let multiple_additive_adverbs = SequenceExpr::default()
23+
.then(additive_adverb.clone())
24+
.then_one_or_more(
25+
SequenceExpr::default()
26+
.then_whitespace()
27+
.then(additive_adverb.clone()),
28+
)
29+
.then_optional(SequenceExpr::default().then_whitespace().t_aco("as"));
30+
31+
Self {
32+
expr: Box::new(multiple_additive_adverbs),
33+
}
34+
}
35+
}
36+
37+
impl ExprLinter for RedundantAdditiveAdverbs {
38+
fn expr(&self) -> &dyn Expr {
39+
self.expr.as_ref()
40+
}
41+
42+
fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
43+
let phrase_string = toks.span()?.get_content_string(src).to_lowercase();
44+
45+
// Rule out `also too` as in `This is also too slow`.
46+
if phrase_string.eq("also too") {
47+
return None;
48+
}
49+
50+
let mut toks = toks;
51+
52+
// Check for the `as well as` false positive at the end
53+
if phrase_string.ends_with(" as well as") {
54+
// three word tokens and three whitespace tokens
55+
if toks.len() >= 6 {
56+
toks = &toks[..toks.len() - 6];
57+
}
58+
}
59+
60+
let mut additive_adverbs: Vec<&[char]> = vec![];
61+
62+
for word in toks
63+
.iter()
64+
.filter(|tok| tok.kind.is_word())
65+
.map(|tok| tok.span.get_content(src))
66+
.collect::<Vec<_>>()
67+
{
68+
let term: &[char] = match word {
69+
['a', 's'] | ['w', 'e', 'l', 'l'] => &['a', 's', ' ', 'w', 'e', 'l', 'l'],
70+
_ => word,
71+
};
72+
if !additive_adverbs.contains(&term) {
73+
additive_adverbs.push(term);
74+
}
75+
}
76+
77+
// Because of the possible `as well as` false positive, we might only have one additive adverb left.
78+
if additive_adverbs.len() < 2 {
79+
return None;
80+
}
81+
82+
let suggestions = additive_adverbs
83+
.iter()
84+
.filter_map(|adverb| {
85+
Some(Suggestion::replace_with_match_case(
86+
adverb.to_vec(),
87+
toks.span()?.get_content(src),
88+
))
89+
})
90+
.collect::<Vec<_>>();
91+
92+
let message = format!(
93+
"Use just one of `{}`.",
94+
additive_adverbs
95+
.iter()
96+
.map(|s| s.iter().collect::<String>())
97+
.collect::<Vec<_>>()
98+
.join("` or `")
99+
);
100+
101+
Some(Lint {
102+
span: toks.span()?,
103+
lint_kind: LintKind::Redundancy,
104+
suggestions,
105+
message,
106+
priority: 31,
107+
})
108+
}
109+
110+
fn description(&self) -> &'static str {
111+
"Detects redundant additive adverbs."
112+
}
113+
}
114+
115+
#[cfg(test)]
116+
mod tests {
117+
use crate::linting::{
118+
RedundantAdditiveAdverbs,
119+
tests::{assert_lint_count, assert_top3_suggestion_result},
120+
};
121+
122+
// Basic unit tests
123+
124+
#[test]
125+
fn flag_as_well_too() {
126+
assert_top3_suggestion_result(
127+
"Yeah, we definitely miss him on this episode here, but you could probably get him on a podcast that's more focused on what Equinix is doing as well too, specifically.",
128+
RedundantAdditiveAdverbs::default(),
129+
"Yeah, we definitely miss him on this episode here, but you could probably get him on a podcast that's more focused on what Equinix is doing as well, specifically.",
130+
);
131+
}
132+
133+
#[test]
134+
fn flag_too_also() {
135+
assert_top3_suggestion_result(
136+
"The #1 uptime service with many servers and is easy to setup. It is free too also.",
137+
RedundantAdditiveAdverbs::default(),
138+
"The #1 uptime service with many servers and is easy to setup. It is free also.",
139+
);
140+
}
141+
142+
#[test]
143+
fn dont_flag_also_too() {
144+
assert_lint_count(
145+
"The version update is also too slow.",
146+
RedundantAdditiveAdverbs::default(),
147+
0,
148+
);
149+
}
150+
151+
#[test]
152+
fn dont_flag_also_as_well_as() {
153+
assert_lint_count(
154+
"Believe there are stable packages in the readme also as well as a link to an old version of forge in the ...",
155+
RedundantAdditiveAdverbs::default(),
156+
0,
157+
);
158+
}
159+
160+
#[test]
161+
fn do_flag_too_also_as_well_as() {
162+
assert_lint_count(
163+
"What would happen with a sentence that included too also as well as?",
164+
RedundantAdditiveAdverbs::default(),
165+
1,
166+
);
167+
}
168+
169+
#[test]
170+
fn flag_too_as_well() {
171+
assert_top3_suggestion_result(
172+
"Module name itself was changed too as well.",
173+
RedundantAdditiveAdverbs::default(),
174+
"Module name itself was changed as well.",
175+
);
176+
}
177+
}

0 commit comments

Comments
 (0)