Skip to content

Commit

Permalink
Use regex parser for string literals
Browse files Browse the repository at this point in the history
  • Loading branch information
camchenry committed Sep 22, 2024
1 parent 7b22e28 commit d9c7402
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 81 deletions.
108 changes: 31 additions & 77 deletions crates/oxc_linter/src/rules/eslint/no_regex_spaces.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use oxc_allocator::Vec;
use oxc_allocator::{Allocator, Vec};
use oxc_ast::{
ast::{Argument, CallExpression, NewExpression, RegExpLiteral},
AstKind,
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_regular_expression::ast::Term;
use oxc_regular_expression::{
ast::{Pattern, Term},
Parser, ParserOptions,
};
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule, AstNode};
Expand Down Expand Up @@ -77,7 +80,33 @@ impl NoRegexSpaces {
}

let pattern = literal.regex.pattern.as_pattern()?;
Self::find_consecutive_spaces(pattern)
}

fn find_expr_to_report(args: &Vec<'_, Argument<'_>>) -> Option<Span> {
if let Some(expr) = args.get(1).and_then(Argument::as_expression) {
if !expr.is_string_literal() {
return None; // skip on indeterminate flag, e.g. RegExp('a b', flags)
}
}

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('[ ]')
}

let alloc = Allocator::default();
let pattern_with_slashes = format!("/{}/", &pattern.value);
let parser = Parser::new(&alloc, pattern_with_slashes.as_str(), ParserOptions::default());
let regex = parser.parse().ok()?;

Self::find_consecutive_spaces(&regex.pattern)
.map(|span| Span::new(span.start + pattern.span.start, span.end + pattern.span.start))
}

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| {
Expand Down Expand Up @@ -117,81 +146,6 @@ impl NoRegexSpaces {
}
}

fn find_expr_to_report(args: &Vec<'_, Argument<'_>>) -> Option<Span> {
if let Some(expr) = args.get(1).and_then(Argument::as_expression) {
if !expr.is_string_literal() {
return None; // skip on indeterminate flag, e.g. RegExp('a b', flags)
}
}

if let Some(Argument::StringLiteral(pattern)) = args.first() {
if Self::has_exempted_char_class(&pattern.value) {
return None; // skip spaces inside char class, e.g. RegExp('[ ]')
}

if let Some((idx_start, idx_end)) =
Self::find_consecutive_spaces_indices(&pattern.value)
{
let start = pattern.span.start + u32::try_from(idx_start).unwrap() + 1;
let end = pattern.span.start + u32::try_from(idx_end).unwrap() + 2;

return Some(Span::new(start, end));
}
}

None
}

fn find_consecutive_spaces_indices(input: &str) -> Option<(usize, usize)> {
let mut consecutive_spaces = 0;
let mut start: Option<usize> = None;
let mut inside_square_brackets: usize = 0;

for (cur_idx, char) in input.char_indices() {
if char == '[' {
inside_square_brackets += 1;
} else if char == ']' {
inside_square_brackets = inside_square_brackets.saturating_sub(1);
}

if char != ' ' || inside_square_brackets > 0 {
// ignore spaces inside char class, including nested ones
consecutive_spaces = 0;
start = None;
continue;
}

if start.is_none() {
start = Some(cur_idx);
}

consecutive_spaces += 1;

if consecutive_spaces < 2 {
continue;
}

// 2 or more consecutive spaces

if let Some(next_char) = input.chars().nth(cur_idx + 1) {
if next_char == ' ' {
continue; // keep collecting spaces
}

if "+*{?".contains(next_char) && consecutive_spaces == 2 {
continue; // ignore 2 consecutive spaces before quantifier
}

return Some((start.unwrap(), cur_idx));
}

// end of string
return Some((start.unwrap(), cur_idx));
}

None
}

fn is_regexp_new_expression(expr: &NewExpression<'_>) -> bool {
expr.callee.is_specific_id("RegExp") && expr.arguments.len() > 0
}
Expand Down
8 changes: 4 additions & 4 deletions crates/oxc_linter/src/snapshots/no_regex_spaces.snap
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ source: crates/oxc_linter/src/tester.rs
eslint(no-regex-spaces): Spaces are hard to count.
╭─[no_regex_spaces.tsx:1:26]
1var foo = new RegExp('bar *baz')
· ──
· ──
╰────
help: Use a quantifier, e.g. {2}

eslint(no-regex-spaces): Spaces are hard to count.
╭─[no_regex_spaces.tsx:1:22]
1var foo = RegExp('bar +baz')
· ──
· ──
╰────
help: Use a quantifier, e.g. {2}

Expand Down Expand Up @@ -177,8 +177,8 @@ source: crates/oxc_linter/src/tester.rs
help: Use a quantifier, e.g. {2}

eslint(no-regex-spaces): Spaces are hard to count.
╭─[no_regex_spaces.tsx:1:35]
╭─[no_regex_spaces.tsx:1:30]
1var foo = new RegExp('[[ ] ] ', 'v');
· ────
· ────
╰────
help: Use a quantifier, e.g. {2}

0 comments on commit d9c7402

Please sign in to comment.