use super::Context;
use super::LintRule;
use crate::swc_util::extract_regex;
use swc_common::Span;
use swc_ecmascript::ast::{CallExpr, Expr, ExprOrSuper, NewExpr, Regex};
use swc_ecmascript::visit::noop_visit_type;
use swc_ecmascript::visit::Node;
use swc_ecmascript::visit::Visit;
use std::sync::Arc;
pub struct NoRegexSpaces;
impl LintRule for NoRegexSpaces {
fn new() -> Box<Self> {
Box::new(NoRegexSpaces)
}
fn code(&self) -> &'static str {
"no-regex-spaces"
}
fn lint_module(
&self,
context: Arc<Context>,
module: &swc_ecmascript::ast::Module,
) {
let mut visitor = NoRegexSpacesVisitor::new(context);
visitor.visit_module(module, module);
}
}
struct NoRegexSpacesVisitor {
context: Arc<Context>,
}
impl NoRegexSpacesVisitor {
pub fn new(context: Arc<Context>) -> Self {
Self { context }
}
fn check_regex(&self, regex: &str, span: Span) {
lazy_static! {
static ref DOUBLE_SPACE: regex::Regex =
regex::Regex::new(r"(?u) {2}").unwrap();
static ref BRACKETS: regex::Regex =
regex::Regex::new(r"\[.*?[^\\]\]").unwrap();
static ref SPACES: regex::Regex =
regex::Regex::new(r#"(?u)( {2,})(?: [+*{?]|[^+*{?]|$)"#).unwrap();
}
if !DOUBLE_SPACE.is_match(regex) {
return;
}
let mut character_classes = vec![];
for mtch in BRACKETS.find_iter(regex) {
character_classes.push((mtch.start(), mtch.end()));
}
for mtch in SPACES.find_iter(regex) {
let not_in_classes = &character_classes
.iter()
.all(|ref v| mtch.start() < v.0 || v.1 <= mtch.start());
if *not_in_classes {
self.context.add_diagnostic(
span,
"no-regex-spaces",
"more than one consecutive spaces in RegExp is not allowed",
);
return;
}
}
}
}
impl Visit for NoRegexSpacesVisitor {
noop_visit_type!();
fn visit_regex(&mut self, regex: &Regex, parent: &dyn Node) {
self.check_regex(regex.exp.to_string().as_str(), regex.span);
swc_ecmascript::visit::visit_regex(self, regex, parent);
}
fn visit_new_expr(&mut self, new_expr: &NewExpr, parent: &dyn Node) {
if let Expr::Ident(ident) = &*new_expr.callee {
if let Some(args) = &new_expr.args {
if let Some(regex) = extract_regex(&self.context.scope, ident, args) {
self.check_regex(regex.as_str(), new_expr.span);
}
}
}
swc_ecmascript::visit::visit_new_expr(self, new_expr, parent);
}
fn visit_call_expr(&mut self, call_expr: &CallExpr, parent: &dyn Node) {
if let ExprOrSuper::Expr(expr) = &call_expr.callee {
if let Expr::Ident(ident) = expr.as_ref() {
if let Some(regex) =
extract_regex(&self.context.scope, ident, &call_expr.args)
{
self.check_regex(regex.as_str(), call_expr.span);
}
}
}
swc_ecmascript::visit::visit_call_expr(self, call_expr, parent);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::*;
#[test]
fn no_regex_spaces_valid() {
assert_lint_ok_n::<NoRegexSpaces>(vec![
"var foo = /foo/;",
"var foo = RegExp('foo')",
"var foo = / /;",
"var foo = RegExp(' ')",
"var foo = / a b c d /;",
"var foo = /bar {3}baz/g;",
"var foo = RegExp('bar {3}baz', 'g')",
"var foo = new RegExp('bar {3}baz')",
"var foo = /bar\t\t\tbaz/;",
"var foo = RegExp('bar\t\t\tbaz');",
"var foo = new RegExp('bar\t\t\tbaz');",
"var foo = / +/;",
"var foo = / ?/;",
"var foo = / */;",
"var foo = / {2}/;",
"var RegExp = function() {}; var foo = new RegExp('bar baz');",
"var RegExp = function() {}; var foo = RegExp('bar baz');",
r"var foo = /bar \\ baz/;",
r"var foo = /bar\\ \\ baz/;",
r"var foo = /bar \\u0020 baz/;",
r"var foo = /bar\\u0020\\u0020baz/;",
r"var foo = new RegExp('bar \\ baz')",
r"var foo = new RegExp('bar\\ \\ baz')",
r"var foo = new RegExp('bar \\\\ baz')",
r"var foo = new RegExp('bar \\u0020 baz')",
r"var foo = new RegExp('bar\\u0020\\u0020baz')",
r"var foo = new RegExp('bar \\\\u0020 baz')",
"var foo = /[ ]/;",
"var foo = /[ ]/;",
"var foo = / [ ] /;",
"var foo = / [ ] [ ] /;",
"var foo = new RegExp('[ ]');",
"var foo = new RegExp('[ ]');",
"var foo = new RegExp(' [ ] ');",
"var foo = RegExp(' [ ] [ ] ');",
"var foo = new RegExp(' \\[ \\] ');",
]);
}
#[test]
fn no_regex_spaces_invalid() {
assert_lint_err::<NoRegexSpaces>("let foo = /bar baz/;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = /bar baz/;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = / a b c d /;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = RegExp(' a b c d ');", 10);
assert_lint_err::<NoRegexSpaces>("let foo = RegExp('bar baz');", 10);
assert_lint_err::<NoRegexSpaces>("let foo = new RegExp('bar baz');", 10);
assert_lint_err::<NoRegexSpaces>(
"{ let RegExp = function() {}; } var foo = RegExp('bar baz');",
42,
);
assert_lint_err::<NoRegexSpaces>("let foo = /bar {3}baz/;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = /bar ?baz/;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = RegExp('bar +baz')", 10);
assert_lint_err::<NoRegexSpaces>("let foo = new RegExp('bar ');", 10);
assert_lint_err::<NoRegexSpaces>("let foo = /bar\\ baz/;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = /[ ] /;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = / [ ] /;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = new RegExp('[ ] ');", 10);
assert_lint_err::<NoRegexSpaces>("let foo = RegExp(' [ ]');", 10);
assert_lint_err::<NoRegexSpaces>("let foo = /\\[ /;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = /\\[ \\]/;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = /(?: )/;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = RegExp('^foo(?= )');", 10);
assert_lint_err::<NoRegexSpaces>("let foo = /\\ /", 10);
assert_lint_err::<NoRegexSpaces>("let foo = / \\ /", 10);
assert_lint_err::<NoRegexSpaces>("let foo = / foo /;", 10);
assert_lint_err::<NoRegexSpaces>("let foo = new RegExp('\\\\d ')", 10);
assert_lint_err::<NoRegexSpaces>("let foo = RegExp('\\u0041 ')", 10);
assert_lint_err::<NoRegexSpaces>(
"let foo = new RegExp('\\\\[ \\\\]');",
10,
);
}
}