Skip to content

Commit

Permalink
feat(linter): implement eslint/no-invalid-regexp
Browse files Browse the repository at this point in the history
closes #611
  • Loading branch information
Boshen committed Sep 4, 2024
1 parent 9c937e0 commit ab074cb
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ mod eslint {
pub mod no_global_assign;
pub mod no_import_assign;
pub mod no_inner_declarations;
pub mod no_invalid_regexp;
pub mod no_irregular_whitespace;
pub mod no_iterator;
pub mod no_label_var;
Expand Down Expand Up @@ -563,6 +564,7 @@ oxc_macros::declare_all_lint_rules! {
eslint::no_useless_concat,
eslint::no_useless_constructor,
eslint::no_var,
eslint::no_invalid_regexp,
eslint::no_void,
eslint::no_with,
eslint::radix,
Expand Down
240 changes: 240 additions & 0 deletions crates/oxc_linter/src/rules/eslint/no_invalid_regexp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
use oxc_allocator::Allocator;
use oxc_ast::{ast::Argument, AstKind};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_regular_expression::{FlagsParser, ParserOptions, PatternParser};
use oxc_span::Span;
// use oxc_regular_expression::PatternParser

use crate::{
context::LintContext,
fixer::{RuleFix, RuleFixer},
rule::Rule,
AstNode,
};

#[derive(Debug, Default, Clone)]
pub struct NoInvalidRegexp;

declare_oxc_lint!(
/// ### What it does
///
///
/// ### Why is this bad?
///
///
/// ### Examples
///
/// Examples of **incorrect** code for this rule:
/// ```js
/// FIXME: Tests will fail if examples are missing or syntactically incorrect.
/// ```
///
/// Examples of **correct** code for this rule:
/// ```js
/// FIXME: Tests will fail if examples are missing or syntactically incorrect.
/// ```
NoInvalidRegexp,
nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style`
// See <https://oxc.rs/docs/contribute/linter.html#rule-category> for details

pending // TODO: describe fix capabilities. Remove if no fix can be done,
// keep at 'pending' if you think one could be added but don't know how.
// Options are 'fix', 'fix_dangerous', 'suggestion', and 'conditional_fix_suggestion'
);

impl Rule for NoInvalidRegexp {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let (pattern, flags, span_offset) = match node.kind() {
AstKind::NewExpression(e) => {
if !e.callee.is_specific_id("RegExp") {
return;
}
let Some(Argument::StringLiteral(pattern)) = e.arguments.get(0) else { return };
let Some(Argument::StringLiteral(flags)) = e.arguments.get(1) else { return };
(pattern.value.as_str(), flags.value.as_str(), pattern.span.start)
}
AstKind::CallExpression(e) => {
if !e.callee.is_specific_id("RegExp") {
return;
}
let Some(Argument::StringLiteral(pattern)) = e.arguments.get(0) else { return };
let Some(Argument::StringLiteral(flags)) = e.arguments.get(1) else { return };
(pattern.value.as_str(), flags.value.as_str(), pattern.span.start)
}
_ => return,
};

let allocator = Allocator::default();
let flags = match FlagsParser::new(&allocator, flags, ParserOptions::default()).parse() {
Ok(flags) => flags,
Err(diagnostic) => {
ctx.diagnostic(diagnostic);
return;
}
};

let options = ParserOptions {
span_offset: span_offset + 1,
unicode_mode: flags.unicode || flags.unicode_sets,
unicode_sets_mode: flags.unicode_sets,
};

let Err(diagnostic) = PatternParser::new(&allocator, pattern, options).parse() else {
return;
};

ctx.diagnostic(diagnostic);
}
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
("RegExp('')", None),
("RegExp()", None),
("RegExp('.', 'g')", None),
("new RegExp('.')", None),
("new RegExp", None),
("new RegExp('.', 'im')", None),
("global.RegExp('\\\\')", None),
("new RegExp('.', y)", None),
("new RegExp('.', 'y')", None),
("new RegExp('.', 'u')", None),
("new RegExp('.', 'yu')", None),
("new RegExp('/', 'yu')", None),
("new RegExp('\\/', 'yu')", None),
("new RegExp('\\u{65}', 'u')", None),
("new RegExp('\\u{65}*', 'u')", None),
("new RegExp('[\\u{0}-\\u{1F}]', 'u')", None),
("new RegExp('.', 's')", None),
("new RegExp('(?<=a)b')", None),
("new RegExp('(?<!a)b')", None),
("new RegExp('(?<a>b)\\k<a>')", None),
("new RegExp('(?<a>b)\\k<a>', 'u')", None),
("new RegExp('\\\\p{Letter}', 'u')", None),
("RegExp('{', flags)", None),
("new RegExp('{', flags)", None),
("RegExp('\\u{0}*', flags)", None),
("new RegExp('\\u{0}*', flags)", None),
("RegExp('{', flags)", Some(serde_json::json!([{ "allowConstructorFlags": ["u"] }]))),
("RegExp('\\u{0}*', flags)", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
("new RegExp(pattern, 'g')", None),
("new RegExp('.' + '', 'g')", None),
("new RegExp(pattern, '')", None),
("new RegExp(pattern)", None),
("new RegExp('(?<\\ud835\\udc9c>.)', 'g')", None),
("new RegExp('(?<\\u{1d49c}>.)', 'g')", None),
("new RegExp('(?<𝒜>.)', 'g');", None),
("new RegExp('\\\\p{Script=Nandinagari}', 'u');", None),
("new RegExp('a+(?<Z>z)?', 'd')", None),
("new RegExp('\\\\p{Script=Cpmn}', 'u')", None),
("new RegExp('\\\\p{Script=Cypro_Minoan}', 'u')", None),
("new RegExp('\\\\p{Script=Old_Uyghur}', 'u')", None),
("new RegExp('\\\\p{Script=Ougr}', 'u')", None),
("new RegExp('\\\\p{Script=Tangsa}', 'u')", None),
("new RegExp('\\\\p{Script=Tnsa}', 'u')", None),
("new RegExp('\\\\p{Script=Toto}', 'u')", None),
("new RegExp('\\\\p{Script=Vith}', 'u')", None),
("new RegExp('\\\\p{Script=Vithkuqi}', 'u')", None),
("new RegExp('[A--B]', 'v')", None),
("new RegExp('[A&&B]', 'v')", None),
("new RegExp('[A--[0-9]]', 'v')", None),
("new RegExp('[\\\\p{Basic_Emoji}--\\\\q{a|bc|def}]', 'v')", None),
("new RegExp('[A--B]', flags)", None),
("new RegExp('[[]\\u{0}*', flags)", None),
("new RegExp('((?<k>a)|(?<k>b))')", None),
("new RegExp('.', 'g')", Some(serde_json::json!([{ "allowConstructorFlags": [] }]))),
("new RegExp('.', 'g')", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
("new RegExp('.', 'a')", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
("new RegExp('.', 'ag')", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
("new RegExp('.', 'ga')", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
(
"new RegExp(pattern, 'ga')",
Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }])),
),
(
"new RegExp('.' + '', 'ga')",
Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }])),
),
(
"new RegExp('.', 'a')",
Some(serde_json::json!([{ "allowConstructorFlags": ["a", "z"] }])),
),
(
"new RegExp('.', 'z')",
Some(serde_json::json!([{ "allowConstructorFlags": ["a", "z"] }])),
),
(
"new RegExp('.', 'az')",
Some(serde_json::json!([{ "allowConstructorFlags": ["a", "z"] }])),
),
(
"new RegExp('.', 'za')",
Some(serde_json::json!([{ "allowConstructorFlags": ["a", "z"] }])),
),
(
"new RegExp('.', 'agz')",
Some(serde_json::json!([{ "allowConstructorFlags": ["a", "z"] }])),
),
];

let fail = vec![
("RegExp('[');", None),
("RegExp('.', 'z');", None),
("RegExp('.', 'a');", Some(serde_json::json!([{}]))),
("new RegExp('.', 'a');", Some(serde_json::json!([{ "allowConstructorFlags": [] }]))),
("new RegExp('.', 'z');", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
("RegExp('.', 'a');", Some(serde_json::json!([{ "allowConstructorFlags": ["A"] }]))),
("RegExp('.', 'A');", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
("new RegExp('.', 'az');", Some(serde_json::json!([{ "allowConstructorFlags": ["z"] }]))),
("new RegExp('.', 'aa');", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
(
"new RegExp('.', 'aa');",
Some(serde_json::json!([{ "allowConstructorFlags": ["a", "a"] }])),
),
("new RegExp('.', 'aA');", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
(
"new RegExp('.', 'aaz');",
Some(serde_json::json!([{ "allowConstructorFlags": ["a", "z"] }])),
),
(
"new RegExp('.', 'azz');",
Some(serde_json::json!([{ "allowConstructorFlags": ["a", "z"] }])),
),
("new RegExp('.', 'aga');", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
("new RegExp('.', 'uu');", Some(serde_json::json!([{ "allowConstructorFlags": ["u"] }]))),
("new RegExp('.', 'ouo');", Some(serde_json::json!([{ "allowConstructorFlags": ["u"] }]))),
("new RegExp(')');", None),
("new RegExp('\\a', 'u');", None),
("new RegExp('\\a', 'u');", Some(serde_json::json!([{ "allowConstructorFlags": ["u"] }]))),
("RegExp('\u{0}*');", None),
("new RegExp('\u{0}*');", None),
("new RegExp('\u{0}*', '');", None),
(
"new RegExp('\u{0}*', 'a');",
Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }])),
),
("RegExp('\u{0}*');", Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }]))),
("new RegExp('\');", None),
("RegExp(')' + '', 'a');", None),
(
"new RegExp('.' + '', 'az');",
Some(serde_json::json!([{ "allowConstructorFlags": ["z"] }])),
),
(
"new RegExp(pattern, 'az');",
Some(serde_json::json!([{ "allowConstructorFlags": ["a"] }])),
),
("new RegExp('[[]', 'v');", None),
("new RegExp('.', 'uv');", None),
("new RegExp(pattern, 'uv');", None),
("new RegExp('[A--B]' /* valid only with `v` flag */, 'u')", None),
("new RegExp('[[]\\u{0}*' /* valid only with `u` flag */, 'v')", None),
("new RegExp('(?<k>a)(?<k>b)')", None),
];

Tester::new(NoInvalidRegexp::NAME, pass, fail).test_and_snapshot();
}

0 comments on commit ab074cb

Please sign in to comment.