feat(linter): eslint/no-empty-character-class (#635)

closes #559
This commit is contained in:
Sg 2023-07-27 11:32:43 +08:00 committed by GitHub
parent 451667acfc
commit 453edd27eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 157 additions and 0 deletions

View file

@ -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,

View file

@ -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();
}

View file

@ -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