From 453edd27ebed0a61ca0ff33d9d292322242fb3cb Mon Sep 17 00:00:00 2001 From: Sg Date: Thu, 27 Jul 2023 11:32:43 +0800 Subject: [PATCH] feat(linter): eslint/no-empty-character-class (#635) closes #559 --- crates/oxc_linter/src/rules.rs | 1 + .../rules/eslint/no_empty_character_class.rs | 94 +++++++++++++++++++ .../snapshots/no_empty_character_class.snap | 62 ++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/no_empty_character_class.rs create mode 100644 crates/oxc_linter/src/snapshots/no_empty_character_class.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 4e9f211cf..6da8f361f 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -31,6 +31,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_dupe_keys, eslint::no_duplicate_case, eslint::no_empty, + eslint::no_empty_character_class, eslint::no_empty_pattern, eslint::no_eval, eslint::no_ex_assign, diff --git a/crates/oxc_linter/src/rules/eslint/no_empty_character_class.rs b/crates/oxc_linter/src/rules/eslint/no_empty_character_class.rs new file mode 100644 index 000000000..8c85d3d6b --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_empty_character_class.rs @@ -0,0 +1,94 @@ +// Ported from https://github.com/eslint/eslint/blob/main/lib/rules/no-empty-character-class.js +use lazy_static::lazy_static; +use oxc_ast::AstKind; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use regex::Regex; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint(no-empty-character-class): Empty character class")] +#[diagnostic(severity(warning), help("Try to remove empty character class `[]` in regexp literal"))] +struct NoEmptyCharacterClassDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoEmptyCharacterClass; + +declare_oxc_lint!( + /// ### What it does + /// Disallow empty character classes in regular expressions + /// + /// ### Why is this bad? + /// Because empty character classes in regular expressions do not match anything, they might be typing mistakes. + /// + /// ### Example + /// ```javascript + /// var foo = /^abc[]/; + /// ``` + NoEmptyCharacterClass, + correctness +); + +impl Rule for NoEmptyCharacterClass { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + lazy_static! { + /* + * plain-English description of the following regexp: + * 0. `^` fix the match at the beginning of the string + * 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following + * 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes) + * 1.1. `\\.`: an escape sequence + * 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty + * 2. `$`: fix the match at the end of the string + */ + static ref NO_EMPTY_CLASS_REGEX_PATTERN: Regex = + Regex::new(r"^([^\\\[]|\\.|\[([^\\\]]|\\.)+\])*$").unwrap(); + } + + if let AstKind::RegExpLiteral(lit) = node.kind() { + if !NO_EMPTY_CLASS_REGEX_PATTERN.is_match(&lit.regex.pattern) { + ctx.diagnostic(NoEmptyCharacterClassDiagnostic(lit.span)); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var foo = /^abc[a-zA-Z]/;", None), + ("var regExp = new RegExp(\"^abc[]\");", None), + ("var foo = /^abc/;", None), + ("var foo = /[\\[]/;", None), + ("var foo = /[\\]]/;", None), + ("var foo = /[a-zA-Z\\[]/;", None), + ("var foo = /[[]/;", None), + ("var foo = /[\\[a-z[]]/;", None), + ("var foo = /[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\^\\$\\|]/g;", None), + ("var foo = /\\s*:\\s*/gim;", None), + ("var foo = /[\\]]/uy;", None), + ("var foo = /[\\]]/s;", None), + ("var foo = /[\\]]/d;", None), + ("var foo = /\\[]/", None), + ]; + + let fail = vec![ + ("var foo = /^abc[]/;", None), + ("var foo = /foo[]bar/;", None), + ("if (foo.match(/^abc[]/)) {}", None), + ("if (/^abc[]/.test(foo)) {}", None), + ("var foo = /[]]/;", None), + ("var foo = /\\[[]/;", None), + ("var foo = /\\[\\[\\]a-z[]/;", None), + ("var foo = /[]]/d;", None), + ]; + + Tester::new(NoEmptyCharacterClass::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_empty_character_class.snap b/crates/oxc_linter/src/snapshots/no_empty_character_class.snap new file mode 100644 index 000000000..2a798c20f --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_empty_character_class.snap @@ -0,0 +1,62 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 80 +expression: no_empty_character_class +--- + ⚠ eslint(no-empty-character-class): Empty character class + ╭─[no_empty_character_class.tsx:1:1] + 1 │ var foo = /^abc[]/; + · ──────── + ╰──── + help: Try to remove empty character class `[]` in regexp literal + + ⚠ eslint(no-empty-character-class): Empty character class + ╭─[no_empty_character_class.tsx:1:1] + 1 │ var foo = /foo[]bar/; + · ────────── + ╰──── + help: Try to remove empty character class `[]` in regexp literal + + ⚠ eslint(no-empty-character-class): Empty character class + ╭─[no_empty_character_class.tsx:1:1] + 1 │ if (foo.match(/^abc[]/)) {} + · ──────── + ╰──── + help: Try to remove empty character class `[]` in regexp literal + + ⚠ eslint(no-empty-character-class): Empty character class + ╭─[no_empty_character_class.tsx:1:1] + 1 │ if (/^abc[]/.test(foo)) {} + · ──────── + ╰──── + help: Try to remove empty character class `[]` in regexp literal + + ⚠ eslint(no-empty-character-class): Empty character class + ╭─[no_empty_character_class.tsx:1:1] + 1 │ var foo = /[]]/; + · ───── + ╰──── + help: Try to remove empty character class `[]` in regexp literal + + ⚠ eslint(no-empty-character-class): Empty character class + ╭─[no_empty_character_class.tsx:1:1] + 1 │ var foo = /\[[]/; + · ────── + ╰──── + help: Try to remove empty character class `[]` in regexp literal + + ⚠ eslint(no-empty-character-class): Empty character class + ╭─[no_empty_character_class.tsx:1:1] + 1 │ var foo = /\[\[\]a-z[]/; + · ───────────── + ╰──── + help: Try to remove empty character class `[]` in regexp literal + + ⚠ eslint(no-empty-character-class): Empty character class + ╭─[no_empty_character_class.tsx:1:1] + 1 │ var foo = /[]]/d; + · ────── + ╰──── + help: Try to remove empty character class `[]` in regexp literal + +