Skip to content

Commit

Permalink
Remove character class checker
Browse files Browse the repository at this point in the history
  • Loading branch information
camchenry committed Sep 22, 2024
1 parent d9c7402 commit 1a1e436
Showing 1 changed file with 52 additions and 21 deletions.
73 changes: 52 additions & 21 deletions crates/oxc_linter/src/rules/eslint/no_regex_spaces.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use aho_corasick::AhoCorasick;
use lazy_static::lazy_static;
use oxc_allocator::{Allocator, Vec};
use oxc_ast::{
ast::{Argument, CallExpression, NewExpression, RegExpLiteral},
Expand All @@ -6,7 +8,7 @@ use oxc_ast::{
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_regular_expression::{
ast::{Pattern, Term},
ast::{Alternative, Disjunction, Pattern, Term},
Parser, ParserOptions,
};
use oxc_span::Span;
Expand Down Expand Up @@ -45,6 +47,11 @@ declare_oxc_lint!(
pending // TODO: This is somewhat autofixable, but the fixer does not exist yet.
);

lazy_static! {
static ref DOUBLE_SPACE: AhoCorasick =
AhoCorasick::new([" "]).expect("no-regex-spaces: Unable to build AhoCorasick");
}

impl Rule for NoRegexSpaces {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
match node.kind() {
Expand Down Expand Up @@ -75,7 +82,7 @@ impl NoRegexSpaces {
fn find_literal_to_report(literal: &RegExpLiteral, ctx: &LintContext) -> Option<Span> {
let pattern_text = literal.regex.pattern.source_text(ctx.source_text());
let pattern_text = pattern_text.as_ref();
if Self::has_exempted_char_class(pattern_text) {
if !Self::has_double_space(pattern_text) {
return None;
}

Expand All @@ -93,8 +100,8 @@ impl NoRegexSpaces {
let Some(Argument::StringLiteral(pattern)) = args.first() else {
return None;
};
if Self::has_exempted_char_class(&pattern.value) {
return None; // skip spaces inside char class, e.g. RegExp('[ ]')
if !Self::has_double_space(&pattern.value) {
return None;
}

let alloc = Allocator::default();
Expand All @@ -109,7 +116,7 @@ impl NoRegexSpaces {
fn find_consecutive_spaces(pattern: &Pattern) -> Option<Span> {
let mut last_space_span: Option<Span> = None;
let mut in_quantifier = false;
pattern.visit_terms(&mut |term| {
visit_terms(pattern, &mut |term| {
if let Term::Quantifier(_) = term {
in_quantifier = true;
return;
Expand All @@ -121,7 +128,7 @@ impl NoRegexSpaces {
in_quantifier = false;
return;
}
if ch.value != b' ' as u32 {
if ch.value != u32::from(b' ') {
return;
}
if let Some(ref mut space_span) = last_space_span {
Expand Down Expand Up @@ -154,23 +161,47 @@ impl NoRegexSpaces {
expr.callee.is_specific_id("RegExp") && expr.arguments.len() > 0
}

/// Whether the input has a character class but no consecutive spaces
/// outside the character class.
fn has_exempted_char_class(input: &str) -> bool {
let mut inside_class = false;

for (i, c) in input.chars().enumerate() {
match c {
'[' => inside_class = true,
']' => inside_class = false,
' ' if input.chars().nth(i + 1) == Some(' ') && !inside_class => {
return false;
}
_ => {}
// For skipping if there aren't any consecutive spaces in the source, to avoid reporting cases
// where the space is explicitly escaped, like: `RegExp(' \ ')``.
fn has_double_space(input: &str) -> bool {
DOUBLE_SPACE.is_match(input)
}
}

/// 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);
}

/// 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),
}

true
}
}

Expand Down

0 comments on commit 1a1e436

Please sign in to comment.