Revert "feat(core): create rule to detect duplicate punctuation (#1694)"

This reverts commit 50ace25d12.
This commit is contained in:
Elijah Potter
2025-09-30 12:30:33 -06:00
parent fcd897c79e
commit 4a16b92aed
11 changed files with 5 additions and 312 deletions

View File

@@ -34,7 +34,7 @@ fn without_initiators(source: &[char]) -> Span<char> {
}
fn is_comment_character(c: char) -> bool {
matches!(c, '#' | '-' | '/' | '*' | '!' | ';')
matches!(c, '#' | '-' | '/' | '*' | '!')
}
#[cfg(test)]

View File

@@ -4,8 +4,7 @@ use harper_core::{Span, Token};
use super::without_initiators;
/// A comment parser that strips starting `/` or `*` characters.
/// See [without_initiators] for a more complete list.
/// A comment parser that strips starting `/` and `*` characters.
///
/// It is meant to cover _most_ cases in _most_ programming languages.
///

View File

@@ -61,7 +61,7 @@ create_test!(ignore_shebang_3.sh, 0);
create_test!(ignore_shebang_4.sh, 1);
create_test!(common.mill, 1);
create_test!(basic_kotlin.kt, 0);
create_test!(basic.clj, 8);
create_test!(basic.clj, 12);
// Checks that some comments are masked out
create_test!(ignore_comments.rs, 1);

View File

@@ -101,7 +101,6 @@ use super::pronoun_contraction::PronounContraction;
use super::pronoun_inflection_be::PronounInflectionBe;
use super::pronoun_knew::PronounKnew;
use super::proper_noun_capitalization_linters;
use super::punctuation_clusters::PunctuationClusters;
use super::quantifier_needs_of::QuantifierNeedsOf;
use super::quite_quiet::QuiteQuiet;
use super::redundant_additive_adverbs::RedundantAdditiveAdverbs;
@@ -510,7 +509,6 @@ impl LintGroup {
insert_struct_rule!(PronounContraction, true);
insert_expr_rule!(PronounInflectionBe, true);
insert_struct_rule!(PronounKnew, true);
insert_struct_rule!(PunctuationClusters, true);
insert_expr_rule!(QuantifierNeedsOf, true);
insert_expr_rule!(QuiteQuiet, true);
insert_expr_rule!(RedundantAdditiveAdverbs, true);

View File

@@ -113,7 +113,6 @@ mod pronoun_contraction;
mod pronoun_inflection_be;
mod pronoun_knew;
mod proper_noun_capitalization_linters;
mod punctuation_clusters;
mod quantifier_needs_of;
mod quite_quiet;
mod redundant_additive_adverbs;
@@ -251,7 +250,6 @@ pub use possessive_your::PossessiveYour;
pub use progressive_needs_be::ProgressiveNeedsBe;
pub use pronoun_contraction::PronounContraction;
pub use pronoun_inflection_be::PronounInflectionBe;
pub use punctuation_clusters::PunctuationClusters;
pub use quantifier_needs_of::QuantifierNeedsOf;
pub use quite_quiet::QuiteQuiet;
pub use redundant_additive_adverbs::RedundantAdditiveAdverbs;

View File

@@ -1,274 +0,0 @@
use std::collections::BTreeSet;
use crate::{
Document, Punctuation, Span, TokenKind,
linting::{Lint, LintKind, Linter, Suggestion},
};
/// Flags clusters of punctuation that should be collapsed to a single mark
/// (e.g. `!!`, `?!?`, `//`, `.,`, `; :`, etc.).
#[derive(Debug, Default)]
pub struct PunctuationClusters;
impl PunctuationClusters {
/// Punctuation kinds were willing to condense.
fn is_candidate(kind: &TokenKind) -> bool {
matches!(
kind,
TokenKind::Punctuation(
Punctuation::Comma
| Punctuation::Semicolon
| Punctuation::Colon
| Punctuation::ForwardSlash
| Punctuation::Bang
| Punctuation::Question
| Punctuation::Period
| Punctuation::Ampersand
)
)
}
/// Map a candidate punctuation token to its canonical char.
fn char_of(kind: &TokenKind) -> char {
match kind {
TokenKind::Punctuation(Punctuation::Comma) => ',',
TokenKind::Punctuation(Punctuation::Semicolon) => ';',
TokenKind::Punctuation(Punctuation::Colon) => ':',
TokenKind::Punctuation(Punctuation::ForwardSlash) => '/',
TokenKind::Punctuation(Punctuation::Bang) => '!',
TokenKind::Punctuation(Punctuation::Question) => '?',
TokenKind::Punctuation(Punctuation::Period) => '.',
TokenKind::Punctuation(Punctuation::Ampersand) => '&',
_ => unreachable!("`char_of` called on non-candidate punctuation"),
}
}
}
impl Linter for PunctuationClusters {
fn lint(&mut self, document: &Document) -> Vec<Lint> {
let toks = document.get_tokens();
let mut lints = Vec::new();
let mut i = 0;
let mut uniq = BTreeSet::<char>::new();
while i < toks.len() {
if !Self::is_candidate(&toks[i].kind) {
i += 1;
continue;
}
let start = i;
let mut last_punct = i; // last index that is a candidate punctuation
uniq.clear();
uniq.insert(Self::char_of(&toks[i].kind));
// scan forward, only consuming spaces if they are *between* punctuation
let mut j = i + 1;
while j < toks.len() {
match &toks[j].kind {
k if Self::is_candidate(k) => {
uniq.insert(Self::char_of(k));
last_punct = j;
j += 1;
}
TokenKind::Space(_) | TokenKind::Newline(_) => {
// Peek past contiguous whitespace.
let mut k = j;
while k < toks.len()
&& matches!(toks[k].kind, TokenKind::Space(_) | TokenKind::Newline(_))
{
k += 1;
}
// If whitespace is followed by more punctuation, it's *internal* → consume it.
if k < toks.len() && Self::is_candidate(&toks[k].kind) {
uniq.insert(Self::char_of(&toks[k].kind));
last_punct = k;
j = k + 1;
} else {
// Whitespace is *external* (before a word/newline/end) → stop BEFORE it.
break;
}
}
_ => break,
}
}
// Count punctuation within the cluster range (no external trailing whitespace).
let count = (start..=last_punct)
.filter(|idx| Self::is_candidate(&toks[*idx].kind))
.count();
if count >= 2 {
let span = Span::new(toks[start].span.start, toks[last_punct].span.end);
// One suggestion per distinct glyph, external whitespace preserved outside span.
let suggestions = uniq
.iter()
.map(|c| Suggestion::ReplaceWith(vec![*c]))
.collect::<Vec<_>>();
lints.push(Lint {
span,
lint_kind: LintKind::Formatting,
suggestions,
message: "Condense this punctuation cluster to a single mark.".into(),
priority: 63,
});
i = last_punct + 1;
} else {
i += 1;
}
}
lints
}
fn description(&self) -> &str {
"Detects consecutive or mixed punctuation marks that should be reduced \
to a single comma, semicolon, colon, slash, question mark, \
exclamation mark, period, or ampersand."
}
}
#[cfg(test)]
mod tests {
use crate::linting::tests::{assert_suggestion_result, assert_top3_suggestion_result};
use super::PunctuationClusters;
#[test]
fn flags_double_comma() {
assert_suggestion_result(
"Wait,, what happened?",
PunctuationClusters,
"Wait, what happened?",
);
}
#[test]
fn flags_double_semicolon() {
assert_suggestion_result(
"He hesitated;; then spoke.",
PunctuationClusters,
"He hesitated; then spoke.",
);
}
#[test]
fn flags_double_colon() {
assert_suggestion_result("Choices:: A or B.", PunctuationClusters, "Choices: A or B.");
}
#[test]
fn flags_double_bang() {
assert_suggestion_result("Stop!!", PunctuationClusters, "Stop!");
}
#[test]
fn flags_double_question() {
assert_suggestion_result("Really??", PunctuationClusters, "Really?");
}
#[test]
fn flags_mixed_qbang_pair() {
assert_top3_suggestion_result("What?!", PunctuationClusters, "What?");
}
#[test]
fn flags_triple_bang() {
assert_suggestion_result(
"No!!! Absolutely not.",
PunctuationClusters,
"No! Absolutely not.",
);
}
#[test]
fn flags_q_bang_bang() {
assert_top3_suggestion_result("Really?!!", PunctuationClusters, "Really?");
}
#[test]
fn flags_double_slash() {
assert_suggestion_result(
"This // is a typo.",
PunctuationClusters,
"This / is a typo.",
);
}
#[test]
fn flags_triple_question() {
assert_suggestion_result("Why???", PunctuationClusters, "Why?");
}
#[test]
fn flags_quadruple_bang() {
assert_suggestion_result("Stop!!!!", PunctuationClusters, "Stop!");
}
#[test]
fn flags_question_bang_question() {
assert_top3_suggestion_result("You did what?!?", PunctuationClusters, "You did what?");
}
#[test]
fn flags_bang_question_bang() {
assert_top3_suggestion_result("No way!?!", PunctuationClusters, "No way!");
}
#[test]
fn flags_question_bang_bang_question() {
assert_top3_suggestion_result("Seriously?!!?", PunctuationClusters, "Seriously?");
}
#[test]
fn flags_with_intervening_whitespace() {
assert_top3_suggestion_result("Why?! ?", PunctuationClusters, "Why?");
}
#[test]
fn flags_double_ampersand() {
assert_suggestion_result("This && that.", PunctuationClusters, "This & that.");
}
#[test]
fn flags_period_comma_cluster() {
assert_top3_suggestion_result("Oops., excuse me.", PunctuationClusters, "Oops, excuse me.");
}
#[test]
fn flags_colon_comma_cluster() {
assert_top3_suggestion_result(
"Delay:, we must wait.",
PunctuationClusters,
"Delay: we must wait.",
);
}
#[test]
fn flags_semicolon_colon_cluster() {
assert_top3_suggestion_result("Choices;: A or B.", PunctuationClusters, "Choices: A or B.");
}
#[test]
fn flags_comma_period_cluster() {
assert_top3_suggestion_result(
"Hold on,. actually…",
PunctuationClusters,
"Hold on, actually…",
);
}
#[test]
fn flags_question_period_cluster() {
assert_top3_suggestion_result("Really?.", PunctuationClusters, "Really?");
assert_top3_suggestion_result("Really?.", PunctuationClusters, "Really.");
}
#[test]
fn flags_bang_period_cluster() {
assert_suggestion_result("Stop!.", PunctuationClusters, "Stop!");
}
}

View File

@@ -105,14 +105,6 @@ impl<T> Span<T> {
}
}
/// Return an expanded span by either modifying [`Self::start`] or [`Self::end`] to include the target
/// index.
pub fn expanded_to_include(&self, target: usize) -> Self {
let mut clone = *self;
clone.expand_to_include(target);
clone
}
/// Get the associated content. Will panic if any aspect is invalid.
pub fn get_content<'a>(&self, source: &'a [T]) -> &'a [T] {
match self.try_get_content(source) {

View File

@@ -92,4 +92,4 @@ create_test!(yogurt_british_clean.md, 0, Dialect::British);
create_test!(lukas_homework.md, 3, Dialect::American);
// Org mode tests
create_org_test!(index.org, 43, Dialect::American);
create_org_test!(index.org, 32, Dialect::American);

View File

@@ -985,16 +985,6 @@ Suggest:
Lint: Formatting (63 priority)
Message: |
394 | Johnson and Frederick P. Brooks Jr., members of the Machine Organization
| ^~ Condense this punctuation cluster to a single mark.
Suggest:
- Replace with: “,”
- Replace with: “.”
Lint: Spelling (63 priority)
Message: |
401 | mathematical models have been developed for general concurrent computation

View File

@@ -16,16 +16,6 @@ Suggest:
Lint: Formatting (63 priority)
Message: |
51 | Be back by ten o'clock!.
| ^~ Condense this punctuation cluster to a single mark.
Suggest:
- Replace with: “!”
- Replace with: “.”
Lint: Spelling (63 priority)
Message: |
56 | He was protected by his body armour.

View File

@@ -38,4 +38,4 @@ macro_rules! create_test {
}
create_test!(good.ink, 0);
create_test!(bad.ink, 6);
create_test!(bad.ink, 5);