Skip to content

Commit 3aa7e42

Browse files
committed
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.
1 parent e7e8ead commit 3aa7e42

File tree

1 file changed

+20
-67
lines changed

1 file changed

+20
-67
lines changed

crates/oxc_linter/src/rules/unicorn/no_hex_escape.rs

Lines changed: 20 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use oxc_ast::{
44
};
55
use oxc_diagnostics::OxcDiagnostic;
66
use oxc_macros::declare_oxc_lint;
7-
use oxc_regular_expression::ast::{
8-
Alternative, Character, CharacterClassContents, CharacterKind, Disjunction, Pattern, Term,
7+
use oxc_regular_expression::{
8+
ast::{Character, CharacterKind},
9+
visit::Visit,
910
};
1011
use oxc_span::Span;
1112

@@ -97,80 +98,32 @@ impl Rule for NoHexEscape {
9798
return;
9899
};
99100

100-
visit_terms(pattern, &mut |term| match term {
101-
Term::Character(ch) => {
102-
check_character(ch, ctx);
103-
}
104-
Term::CharacterClass(class) => {
105-
for term in &class.body {
106-
match term {
107-
CharacterClassContents::Character(ch) => {
108-
check_character(ch, ctx);
109-
}
110-
CharacterClassContents::CharacterClassRange(range) => {
111-
check_character(&range.min, ctx);
112-
check_character(&range.max, ctx);
113-
}
114-
_ => (),
115-
}
116-
}
117-
}
118-
_ => (),
119-
});
101+
let mut finder = HexEscapeFinder { hex_escapes: vec![] };
102+
finder.visit_pattern(pattern);
103+
104+
for span in finder.hex_escapes {
105+
let unicode_escape =
106+
format!(r"\u00{}", &span.source_text(ctx.source_text())[2..]);
107+
108+
ctx.diagnostic_with_fix(no_hex_escape_diagnostic(span), |fixer| {
109+
fixer.replace(span, unicode_escape)
110+
});
111+
}
120112
}
121113
_ => {}
122114
}
123115
}
124116
}
125117

126-
fn check_character(ch: &Character, ctx: &LintContext) {
127-
if ch.kind == CharacterKind::HexadecimalEscape {
128-
let unicode_escape = format!(r"\u00{}", &ch.span.source_text(ctx.source_text())[2..]);
129-
ctx.diagnostic_with_fix(no_hex_escape_diagnostic(ch.span), |fixer| {
130-
fixer.replace(ch.span, unicode_escape)
131-
});
132-
}
133-
}
134-
135-
// TODO: Replace with proper regex AST visitor when available
136-
/// Calls the given closure on every [`Term`] in the [`Pattern`].
137-
fn visit_terms<'a, F: FnMut(&'a Term<'a>)>(pattern: &'a Pattern, f: &mut F) {
138-
visit_terms_disjunction(&pattern.body, f);
118+
struct HexEscapeFinder {
119+
hex_escapes: Vec<Span>,
139120
}
140121

141-
/// Calls the given closure on every [`Term`] in the [`Disjunction`].
142-
fn visit_terms_disjunction<'a, F: FnMut(&'a Term<'a>)>(disjunction: &'a Disjunction, f: &mut F) {
143-
for alternative in &disjunction.body {
144-
visit_terms_alternative(alternative, f);
145-
}
146-
}
147-
148-
/// Calls the given closure on every [`Term`] in the [`Alternative`].
149-
fn visit_terms_alternative<'a, F: FnMut(&'a Term<'a>)>(alternative: &'a Alternative, f: &mut F) {
150-
for term in &alternative.body {
151-
visit_term(term, f);
152-
}
153-
}
154-
155-
fn visit_term<'a, F: FnMut(&'a Term<'a>)>(term: &'a Term<'a>, f: &mut F) {
156-
match term {
157-
Term::LookAroundAssertion(lookaround) => {
158-
f(term);
159-
visit_terms_disjunction(&lookaround.body, f);
160-
}
161-
Term::Quantifier(quant) => {
162-
f(term);
163-
visit_term(&quant.body, f);
164-
}
165-
Term::CapturingGroup(group) => {
166-
f(term);
167-
visit_terms_disjunction(&group.body, f);
168-
}
169-
Term::IgnoreGroup(group) => {
170-
f(term);
171-
visit_terms_disjunction(&group.body, f);
122+
impl<'a> Visit<'a> for HexEscapeFinder {
123+
fn visit_character(&mut self, ch: &Character) {
124+
if ch.kind == CharacterKind::HexadecimalEscape {
125+
self.hex_escapes.push(ch.span);
172126
}
173-
_ => f(term),
174127
}
175128
}
176129

0 commit comments

Comments
 (0)