Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
81dfd51
refactor: towards regex-style captures
hippietrail Jul 8, 2025
2051aae
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 12, 2025
8d010dc
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 15, 2025
7a968e1
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 15, 2025
2929d09
chore: merge w/ upstream
hippietrail Jul 15, 2025
b82f297
Merge branch 'master' into captures-clean-branch
hippietrail Jul 17, 2025
8660e22
fix: update types
hippietrail Jul 17, 2025
0272659
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 18, 2025
ab8d53b
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 22, 2025
e17c2f7
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 23, 2025
8f6b15f
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 23, 2025
1590c08
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 23, 2025
f00aa50
chore: merge with upstream
hippietrail Jul 23, 2025
b033f68
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 26, 2025
805c924
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 29, 2025
8ba441a
chore: merge upstream
hippietrail Jul 29, 2025
efb2cd0
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Jul 29, 2025
8315e6e
chore: merge upstream
hippietrail Jul 29, 2025
45aab8b
fix: `cargo fmt`
hippietrail Jul 29, 2025
60120ea
Merge branch 'master' into captures-clean-branch
hippietrail Aug 1, 2025
76900fc
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Aug 1, 2025
8fa182c
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Aug 9, 2025
437f2d9
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Aug 12, 2025
f241e43
chore: merge upstream
hippietrail Aug 12, 2025
b8d09a0
chore: appease precommit
hippietrail Aug 12, 2025
3d0ed6f
Merge branch 'master' into captures-clean-branch
hippietrail Aug 17, 2025
c0732ad
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Aug 18, 2025
0d1d180
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Aug 26, 2025
109074f
Merge branch 'master' into captures-clean-branch
hippietrail Aug 28, 2025
ed4b78d
chore: merge upstream
hippietrail Aug 28, 2025
0003a85
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Sep 3, 2025
793142b
fix: merge w/ upstream
hippietrail Sep 3, 2025
2c027de
Merge branch 'master' of https://github.com/Automattic/harper into ca…
hippietrail Sep 4, 2025
a1b7ef2
Merge branch 'master' into captures-clean-branch
hippietrail Sep 4, 2025
7325cc6
fix: error slipped through merge
hippietrail Sep 4, 2025
41b12bc
fix: unused import
hippietrail Sep 4, 2025
d950a5a
Merge branch 'master' into captures-clean-branch
hippietrail Sep 6, 2025
0386a3b
fix: merge upstream
hippietrail Sep 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions harper-core/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,7 @@ where
LongestMatchOf::new(vec![Box::new(self), Box::new(other)])
}
}

pub struct MatchInfo<'a> {
pub matched_tokens: &'a [Token],
}
6 changes: 4 additions & 2 deletions harper-core/src/linting/a_part.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::expr::Expr;
use crate::expr::FirstMatchOf;
use crate::expr::FixedPhrase;
use crate::expr::MatchInfo;
use crate::{
Token, TokenStringExt,
TokenStringExt,
linting::{ExprLinter, Lint, LintKind, Suggestion},
};

Expand Down Expand Up @@ -30,7 +31,8 @@ impl ExprLinter for APart {
self.expr.as_ref()
}

fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo, source: &[char]) -> Option<Lint> {
let matched_tokens = match_info.matched_tokens;
let span = matched_tokens.span()?;
let text: String = span.get_content(source).iter().collect();
let text_lower = text.to_lowercase();
Expand Down
5 changes: 3 additions & 2 deletions harper-core/src/linting/adjective_double_degree.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
CharStringExt, Token, TokenStringExt,
expr::{Expr, SequenceExpr},
expr::{Expr, MatchInfo, SequenceExpr},
linting::{ExprLinter, Lint, LintKind, Suggestion},
};

Expand All @@ -25,7 +25,8 @@ impl ExprLinter for AdjectiveDoubleDegree {
self.expr.as_ref()
}

fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, src: &[char]) -> Option<Lint> {
let toks = match_info.matched_tokens;
let phrase_span = toks.span()?;
let phrase_chars = phrase_span.get_content(src);

Expand Down
7 changes: 4 additions & 3 deletions harper-core/src/linting/am_in_the_morning.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
Span, Token, TokenStringExt,
expr::{Expr, FixedPhrase, LongestMatchOf, SequenceExpr},
Span, TokenStringExt,
expr::{Expr, FixedPhrase, LongestMatchOf, MatchInfo, SequenceExpr},
linting::{ExprLinter, Lint, LintKind, Suggestion},
patterns::WordSet,
};
Expand Down Expand Up @@ -44,7 +44,8 @@ impl ExprLinter for AmInTheMorning {
self.expr.as_ref()
}

fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, src: &[char]) -> Option<Lint> {
let toks = match_info.matched_tokens;
let all_after_number_span = toks[0..].span()?;
let am_pm_idx = if toks[0].kind.is_whitespace() { 1 } else { 0 };

Expand Down
6 changes: 4 additions & 2 deletions harper-core/src/linting/amounts_for.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::expr::Expr;
use crate::expr::FirstMatchOf;
use crate::expr::FixedPhrase;
use crate::expr::MatchInfo;
use crate::expr::SequenceExpr;
use crate::{Token, TokenStringExt, patterns::WordSet};
use crate::{TokenStringExt, patterns::WordSet};

use super::{ExprLinter, Lint, LintKind, Suggestion};

Expand Down Expand Up @@ -42,7 +43,8 @@ impl ExprLinter for AmountsFor {
self.expr.as_ref()
}

fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, src: &[char]) -> Option<Lint> {
let toks = match_info.matched_tokens;
let content = toks.span()?.get_content_string(src).to_lowercase();

if content.ends_with("amounts for") {
Expand Down
7 changes: 4 additions & 3 deletions harper-core/src/linting/another_thing_coming.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
Token, TokenStringExt,
expr::{Expr, FixedPhrase, SequenceExpr},
TokenStringExt,
expr::{Expr, FixedPhrase, MatchInfo, SequenceExpr},
linting::{ExprLinter, Lint, LintKind, Suggestion},
patterns::WordSet,
};
Expand All @@ -27,7 +27,8 @@ impl ExprLinter for AnotherThingComing {
self.expr.as_ref()
}

fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, src: &[char]) -> Option<Lint> {
let toks = match_info.matched_tokens;
Some(Lint {
span: toks[2..].span()?,
lint_kind: LintKind::WordChoice,
Expand Down
7 changes: 4 additions & 3 deletions harper-core/src/linting/another_think_coming.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
Token, TokenStringExt,
expr::{Expr, FixedPhrase, SequenceExpr},
TokenStringExt,
expr::{Expr, FixedPhrase, MatchInfo, SequenceExpr},
linting::{ExprLinter, Lint, LintKind, Suggestion},
patterns::WordSet,
};
Expand All @@ -27,7 +27,8 @@ impl ExprLinter for AnotherThinkComing {
self.expr.as_ref()
}

fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, src: &[char]) -> Option<Lint> {
let toks = match_info.matched_tokens;
Some(Lint {
span: toks[2..].span()?,
lint_kind: LintKind::WordChoice,
Expand Down
6 changes: 4 additions & 2 deletions harper-core/src/linting/ask_no_preposition.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::expr::Expr;
use crate::expr::MatchInfo;
use crate::expr::SequenceExpr;
use crate::{
Span, Token,
Span,
linting::{ExprLinter, Lint, LintKind, Suggestion},
patterns::WordSet,
};
Expand Down Expand Up @@ -36,7 +37,8 @@ impl ExprLinter for AskNoPreposition {
self.expr.as_ref()
}

fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, src: &[char]) -> Option<Lint> {
let toks = match_info.matched_tokens;
if toks.len() < 5 {
return None;
}
Expand Down
6 changes: 3 additions & 3 deletions harper-core/src/linting/avoid_curses.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::Token;
use crate::expr::{Expr, SequenceExpr};
use crate::expr::{Expr, MatchInfo, SequenceExpr};
use crate::linting::{LintKind, Suggestion};

use super::{ExprLinter, Lint};
Expand All @@ -21,7 +20,8 @@ impl ExprLinter for AvoidCurses {
self.expr.as_ref()
}

fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, src: &[char]) -> Option<Lint> {
let toks = match_info.matched_tokens;
if toks.len() != 1 {
return None;
}
Expand Down
6 changes: 4 additions & 2 deletions harper-core/src/linting/back_in_the_day.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::expr::Expr;
use crate::expr::FixedPhrase;
use crate::expr::MatchInfo;
use crate::expr::OwnedExprExt;
use crate::expr::SequenceExpr;
use crate::{
Lrc, Token, TokenStringExt,
Lrc, TokenStringExt,
patterns::{Pattern, WordSet},
};

Expand Down Expand Up @@ -38,7 +39,8 @@ impl ExprLinter for BackInTheDay {
self.expr.as_ref()
}

fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, source: &[char]) -> Option<Lint> {
let matched_tokens = match_info.matched_tokens;
if let Some(tail) = matched_tokens.get(8..)
&& self.exceptions.matches(tail, source).is_some()
{
Expand Down
6 changes: 3 additions & 3 deletions harper-core/src/linting/best_of_all_time.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::Token;
use crate::expr::{Expr, SequenceExpr};
use crate::expr::{Expr, MatchInfo, SequenceExpr};
use crate::patterns::WhitespacePattern;

use super::{ExprLinter, Lint, LintKind, Suggestion};
Expand Down Expand Up @@ -41,7 +40,8 @@ impl ExprLinter for BestOfAllTime {
self.expr.as_ref()
}

fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, src: &[char]) -> Option<Lint> {
let toks = match_info.matched_tokens;
let times_span = toks.last()?.span;

if let Some((_, time_singular)) = times_span.get_content(src).split_last() {
Expand Down
7 changes: 4 additions & 3 deletions harper-core/src/linting/boring_words.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::expr::{Expr, WordExprGroup};
use crate::{Token, TokenStringExt};
use crate::TokenStringExt;
use crate::expr::{Expr, MatchInfo, WordExprGroup};

use super::{ExprLinter, Lint, LintKind};

Expand Down Expand Up @@ -28,7 +28,8 @@ impl ExprLinter for BoringWords {
self.expr.as_ref()
}

fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, source: &[char]) -> Option<Lint> {
let matched_tokens = match_info.matched_tokens;
let matched_word = matched_tokens.span()?.get_content_string(source);

Some(Lint {
Expand Down
5 changes: 3 additions & 2 deletions harper-core/src/linting/cant.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{ExprLinter, Suggestion};
use crate::Lint;
use crate::expr::{Expr, LongestMatchOf, SequenceExpr};
use crate::expr::{Expr, LongestMatchOf, MatchInfo, SequenceExpr};
use crate::linting::LintKind;
use crate::linting::expr_linter::find_the_only_token_matching;
use crate::{CharStringExt, Token};
Expand Down Expand Up @@ -52,7 +52,8 @@ impl ExprLinter for Cant {
self.expr.as_ref()
}

fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, src: &[char]) -> Option<Lint> {
let toks = match_info.matched_tokens;
let token = find_the_only_token_matching(toks, src, |tok, src| {
tok.span
.get_content(src)
Expand Down
6 changes: 4 additions & 2 deletions harper-core/src/linting/chock_full.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::expr::Expr;
use crate::expr::MatchInfo;
use crate::expr::SequenceExpr;
use crate::expr::SpaceOrHyphen;
use crate::{Token, TokenStringExt, patterns::WordSet};
use crate::{TokenStringExt, patterns::WordSet};

use super::{ExprLinter, Lint, LintKind, Suggestion};

Expand All @@ -27,7 +28,8 @@ impl ExprLinter for ChockFull {
self.expr.as_ref()
}

fn match_to_lint(&self, matched_toks: &[Token], source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, source: &[char]) -> Option<Lint> {
let matched_toks = match_info.matched_tokens;
let span = matched_toks.span()?;

Some(Lint {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::expr::All;
use crate::expr::Expr;
use crate::expr::MatchInfo;
use crate::expr::MergeableWords;
use crate::expr::SequenceExpr;
use crate::{CharStringExt, TokenStringExt, linting::ExprLinter};
Expand Down Expand Up @@ -57,7 +58,8 @@ impl ExprLinter for CompoundNounAfterDetAdj {
self.expr.as_ref()
}

fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, source: &[char]) -> Option<Lint> {
let matched_tokens = match_info.matched_tokens;
if matched_tokens
.first()?
.span
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use crate::expr::All;
use crate::expr::Expr;
use crate::expr::MatchInfo;
use crate::expr::MergeableWords;
use crate::expr::SequenceExpr;
use crate::patterns::AnyPattern;
use crate::{CharStringExt, Lrc, TokenStringExt, linting::ExprLinter};

use super::{Lint, LintKind, Suggestion, is_content_word, predicate};

use crate::Token;

/// Looks for closed compound nouns which can be condensed due to their position after a
/// possessive noun (which implies ownership).
/// See also:
Expand Down Expand Up @@ -56,7 +55,8 @@ impl ExprLinter for CompoundNounAfterPossessive {
self.expr.as_ref()
}

fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, source: &[char]) -> Option<Lint> {
let matched_tokens = match_info.matched_tokens;
// "Let's" can technically be a possessive noun (of a lease, or a let in tennis, etc.)
// but in practice it's almost always a contraction of "let us" before a verb
// or a mistake for "lets", the 3rd person singular present form of "to let".
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use crate::expr::All;
use crate::expr::Expr;
use crate::expr::MatchInfo;
use crate::expr::MergeableWords;
use crate::expr::SequenceExpr;
use crate::patterns::AnyPattern;
use crate::{CharStringExt, Lrc, TokenStringExt, linting::ExprLinter};

use super::{Lint, LintKind, Suggestion, is_content_word, predicate};

use crate::Token;

/// Two adjacent words separated by whitespace that if joined would be a valid noun.
pub struct CompoundNounBeforeAuxVerb {
expr: Box<dyn Expr>,
Expand Down Expand Up @@ -48,7 +47,8 @@ impl ExprLinter for CompoundNounBeforeAuxVerb {
self.expr.as_ref()
}

fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, source: &[char]) -> Option<Lint> {
let matched_tokens = match_info.matched_tokens;
let span = matched_tokens[0..3].span()?;
let orig = span.get_content(source);
// If the pattern matched, this will not return `None`.
Expand Down
4 changes: 3 additions & 1 deletion harper-core/src/linting/confident.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::expr::Expr;
use crate::expr::MatchInfo;
use crate::expr::OwnedExprExt;
use crate::expr::SequenceExpr;
use crate::{Token, patterns::Word};
Expand Down Expand Up @@ -32,7 +33,8 @@ impl ExprLinter for Confident {
self.expr.as_ref()
}

fn match_to_lint(&self, matched_tokens: &[Token], _source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, _source: &[char]) -> Option<Lint> {
let matched_tokens = match_info.matched_tokens;
let span = matched_tokens.last()?.span;

Some(Lint {
Expand Down
6 changes: 4 additions & 2 deletions harper-core/src/linting/dashes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::TokenStringExt;
use crate::expr::Expr;
use crate::expr::LongestMatchOf;
use crate::expr::MatchInfo;
use crate::expr::SequenceExpr;
use crate::{Token, TokenStringExt};

use super::{ExprLinter, Lint, LintKind, Suggestion};

Expand Down Expand Up @@ -33,7 +34,8 @@ impl ExprLinter for Dashes {
self.expr.as_ref()
}

fn match_to_lint(&self, matched_tokens: &[Token], _source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, _source: &[char]) -> Option<Lint> {
let matched_tokens = match_info.matched_tokens;
let span = matched_tokens.span()?;
let lint_kind = LintKind::Formatting;

Expand Down
6 changes: 4 additions & 2 deletions harper-core/src/linting/despite_of.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::TokenStringExt;
use crate::expr::Expr;
use crate::expr::MatchInfo;
use crate::expr::SequenceExpr;
use crate::{Token, TokenStringExt};

use super::{ExprLinter, Lint, LintKind, Suggestion};

Expand All @@ -25,7 +26,8 @@ impl ExprLinter for DespiteOf {
self.expr.as_ref()
}

fn match_to_lint(&self, matched: &[Token], source: &[char]) -> Option<Lint> {
fn match_to_lint(&self, match_info: MatchInfo<'_>, source: &[char]) -> Option<Lint> {
let matched = match_info.matched_tokens;
let span = matched.span()?;
let matched = span.get_content(source);

Expand Down
Loading