Skip to content

Commit

Permalink
refactor(linter): use regex visitor in no-empty-character-class
Browse files Browse the repository at this point in the history
  • Loading branch information
camchenry committed Sep 27, 2024
1 parent 2090fce commit 6e7f0a4
Showing 1 changed file with 16 additions and 54 deletions.
70 changes: 16 additions & 54 deletions crates/oxc_linter/src/rules/eslint/no_empty_character_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use memchr::memchr2;
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_regular_expression::ast::{
Alternative, CharacterClass, CharacterClassContents, Disjunction, Pattern, Term,
use oxc_regular_expression::{
ast::CharacterClass,
visit::{walk::walk_character_class, Visit},
};
use oxc_span::Span;

Expand Down Expand Up @@ -48,65 +49,26 @@ impl Rule for NoEmptyCharacterClass {
return;
}

visit_terms(pattern, &mut |term| {
if let Term::CharacterClass(class) = term {
check_character_class(ctx, class);
}
});
}
}
}
let mut finder = EmptyClassFinder { empty_classes: vec![] };
finder.visit_pattern(pattern);

fn check_character_class(ctx: &LintContext, class: &CharacterClass) {
// Class has nothing in it, example: `/[]/`
if !class.negative && class.body.is_empty() {
ctx.diagnostic(no_empty_character_class_diagnostic(class.span));
return;
}

// Class has something in it, but might contain empty nested character classes,
// example: `/[[]]/`
for term in &class.body {
if let CharacterClassContents::NestedCharacterClass(class) = term {
check_character_class(ctx, class);
for span in finder.empty_classes {
ctx.diagnostic(no_empty_character_class_diagnostic(span));
}
}
}
}

// TODO: Replace with proper regex AST visitor when available
/// Calls the given closure on every [`Term`] in the [`Pattern`].
fn visit_terms<'a, F: FnMut(&'a Term<'a>)>(pattern: &'a Pattern, f: &mut F) {
visit_terms_disjunction(&pattern.body, f);
struct EmptyClassFinder {
empty_classes: Vec<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 {
match term {
Term::LookAroundAssertion(lookaround) => {
f(term);
visit_terms_disjunction(&lookaround.body, f);
}
Term::Quantifier(quant) => {
f(term);
f(&quant.body);
}
Term::CapturingGroup(group) => {
f(term);
visit_terms_disjunction(&group.body, f);
}
Term::IgnoreGroup(group) => {
f(term);
visit_terms_disjunction(&group.body, f);
}
_ => f(term),
impl<'a> Visit<'a> for EmptyClassFinder {
fn visit_character_class(&mut self, class: &CharacterClass) {
if !class.negative && class.body.is_empty() {
self.empty_classes.push(class.span);
} else {
walk_character_class(self, class);
}
}
}
Expand Down

0 comments on commit 6e7f0a4

Please sign in to comment.