refactor(linter): use RegExp AST visitor for no-hex-escape (#6117)

Updates the `no-hex-escape` to use the new standard `Visit` trait for visiting the RegExp AST, replacing the handwritten implementation in this rule. This makes it much simpler to maintain.
This commit is contained in:
camchenry 2024-09-27 23:24:59 +00:00
parent e7e8eada69
commit 3aa7e42826

View file

@ -4,8 +4,9 @@ use oxc_ast::{
}; };
use oxc_diagnostics::OxcDiagnostic; use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint; use oxc_macros::declare_oxc_lint;
use oxc_regular_expression::ast::{ use oxc_regular_expression::{
Alternative, Character, CharacterClassContents, CharacterKind, Disjunction, Pattern, Term, ast::{Character, CharacterKind},
visit::Visit,
}; };
use oxc_span::Span; use oxc_span::Span;
@ -97,80 +98,32 @@ impl Rule for NoHexEscape {
return; return;
}; };
visit_terms(pattern, &mut |term| match term { let mut finder = HexEscapeFinder { hex_escapes: vec![] };
Term::Character(ch) => { finder.visit_pattern(pattern);
check_character(ch, ctx);
} for span in finder.hex_escapes {
Term::CharacterClass(class) => { let unicode_escape =
for term in &class.body { format!(r"\u00{}", &span.source_text(ctx.source_text())[2..]);
match term {
CharacterClassContents::Character(ch) => { ctx.diagnostic_with_fix(no_hex_escape_diagnostic(span), |fixer| {
check_character(ch, ctx); fixer.replace(span, unicode_escape)
} });
CharacterClassContents::CharacterClassRange(range) => { }
check_character(&range.min, ctx);
check_character(&range.max, ctx);
}
_ => (),
}
}
}
_ => (),
});
} }
_ => {} _ => {}
} }
} }
} }
fn check_character(ch: &Character, ctx: &LintContext) { struct HexEscapeFinder {
if ch.kind == CharacterKind::HexadecimalEscape { hex_escapes: Vec<Span>,
let unicode_escape = format!(r"\u00{}", &ch.span.source_text(ctx.source_text())[2..]);
ctx.diagnostic_with_fix(no_hex_escape_diagnostic(ch.span), |fixer| {
fixer.replace(ch.span, unicode_escape)
});
}
} }
// TODO: Replace with proper regex AST visitor when available impl<'a> Visit<'a> for HexEscapeFinder {
/// Calls the given closure on every [`Term`] in the [`Pattern`]. fn visit_character(&mut self, ch: &Character) {
fn visit_terms<'a, F: FnMut(&'a Term<'a>)>(pattern: &'a Pattern, f: &mut F) { if ch.kind == CharacterKind::HexadecimalEscape {
visit_terms_disjunction(&pattern.body, f); self.hex_escapes.push(ch.span);
}
/// Calls the given closure on every [`Term`] in the [`Disjunction`].
fn visit_terms_disjunction<'a, F: FnMut(&'a Term<'a>)>(disjunction: &'a Disjunction, f: &mut F) {
for alternative in &disjunction.body {
visit_terms_alternative(alternative, f);
}
}
/// Calls the given closure on every [`Term`] in the [`Alternative`].
fn visit_terms_alternative<'a, F: FnMut(&'a Term<'a>)>(alternative: &'a Alternative, f: &mut F) {
for term in &alternative.body {
visit_term(term, f);
}
}
fn visit_term<'a, F: FnMut(&'a Term<'a>)>(term: &'a Term<'a>, f: &mut F) {
match term {
Term::LookAroundAssertion(lookaround) => {
f(term);
visit_terms_disjunction(&lookaround.body, f);
} }
Term::Quantifier(quant) => {
f(term);
visit_term(&quant.body, f);
}
Term::CapturingGroup(group) => {
f(term);
visit_terms_disjunction(&group.body, f);
}
Term::IgnoreGroup(group) => {
f(term);
visit_terms_disjunction(&group.body, f);
}
_ => f(term),
} }
} }