-
-
Notifications
You must be signed in to change notification settings - Fork 501
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): implement
eslint/no-invalid-regexp
closes #611
- Loading branch information
Showing
2 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
240 changes: 240 additions & 0 deletions
240
crates/oxc_linter/src/rules/eslint/no_invalid_regexp.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |