mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): add unicode sets support to no-useless-escape rule (#5974)
- part of https://github.com/oxc-project/oxc/issues/5416 Replaces the handwritten regex parsing logic with the `oxc_regular_expression` parser, which should be more accurate and enables support for unicode sets.
This commit is contained in:
parent
74d8714d5a
commit
f9b44c5738
2 changed files with 595 additions and 45 deletions
|
|
@ -1,7 +1,10 @@
|
||||||
use memchr::memmem;
|
use memchr::memmem;
|
||||||
use oxc_ast::AstKind;
|
use oxc_ast::{ast::RegExpFlags, AstKind};
|
||||||
use oxc_diagnostics::OxcDiagnostic;
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
use oxc_macros::declare_oxc_lint;
|
use oxc_macros::declare_oxc_lint;
|
||||||
|
use oxc_regular_expression::ast::{
|
||||||
|
Alternative, Character, CharacterClass, CharacterClassContents, Disjunction, Pattern, Term,
|
||||||
|
};
|
||||||
use oxc_semantic::NodeId;
|
use oxc_semantic::NodeId;
|
||||||
use oxc_span::Span;
|
use oxc_span::Span;
|
||||||
|
|
||||||
|
|
@ -75,12 +78,16 @@ impl Rule for NoUselessEscape {
|
||||||
if literal.regex.pattern.len() + literal.regex.flags.iter().count()
|
if literal.regex.pattern.len() + literal.regex.flags.iter().count()
|
||||||
!= literal.span.size() as usize =>
|
!= literal.span.size() as usize =>
|
||||||
{
|
{
|
||||||
check(
|
if let Some(pattern) = literal.regex.pattern.as_pattern() {
|
||||||
ctx,
|
let unicode_sets = literal.regex.flags.contains(RegExpFlags::V);
|
||||||
node.id(),
|
let useless_escape_spans = check_pattern(ctx, pattern, unicode_sets);
|
||||||
literal.span.start,
|
for span in useless_escape_spans {
|
||||||
&check_regexp(literal.span.source_text(ctx.source_text())),
|
let c = span.source_text(ctx.source_text()).chars().last().unwrap();
|
||||||
);
|
ctx.diagnostic_with_fix(no_useless_escape_diagnostic(c, span), |fixer| {
|
||||||
|
fixer.replace(span, c.to_string())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AstKind::StringLiteral(literal) => check(
|
AstKind::StringLiteral(literal) => check(
|
||||||
ctx,
|
ctx,
|
||||||
|
|
@ -130,52 +137,131 @@ fn check(ctx: &LintContext<'_>, node_id: NodeId, start: u32, offsets: &[usize])
|
||||||
|
|
||||||
const REGEX_GENERAL_ESCAPES: &str = "\\bcdDfnpPrsStvwWxu0123456789]";
|
const REGEX_GENERAL_ESCAPES: &str = "\\bcdDfnpPrsStvwWxu0123456789]";
|
||||||
const REGEX_NON_CHARCLASS_ESCAPES: &str = "\\bcdDfnpPrsStvwWxu0123456789]^/.$*+?[{}|()Bk";
|
const REGEX_NON_CHARCLASS_ESCAPES: &str = "\\bcdDfnpPrsStvwWxu0123456789]^/.$*+?[{}|()Bk";
|
||||||
|
const REGEX_CLASSSET_CHARACTER_ESCAPES: &str = "\\bcdDfnpPrsStvwWxu0123456789]q/[{}|()-";
|
||||||
|
const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR: &str = "!#$%&*+,.:;<=>?@^`~";
|
||||||
|
|
||||||
fn check_regexp(regex: &str) -> Vec<usize> {
|
fn check_pattern(ctx: &LintContext, pattern: &Pattern, unicode_sets: bool) -> Vec<Span> {
|
||||||
let mut offsets = vec![];
|
let mut spans = vec![];
|
||||||
let mut in_escape = false;
|
|
||||||
let mut in_character_class = false;
|
|
||||||
let mut start_char_class = false;
|
|
||||||
let mut offset = 1;
|
|
||||||
|
|
||||||
// Skip the leading and trailing `/`
|
visit_terms(pattern, &mut |term, stack| match term {
|
||||||
let mut chars = regex[1..regex.len() - 1].chars().peekable();
|
Term::CharacterClass(class) => {
|
||||||
while let Some(c) = chars.next() {
|
check_character_class(ctx, class, unicode_sets, &mut spans);
|
||||||
if in_escape {
|
}
|
||||||
in_escape = false;
|
Term::Character(ch) => {
|
||||||
match c {
|
let character_class = stack.iter().find_map(|visit| match visit {
|
||||||
'-' if in_character_class
|
Visit::Term(Term::CharacterClass(class)) => Some(class),
|
||||||
&& !start_char_class
|
Visit::Term(_) => None,
|
||||||
&& !chars.peek().is_some_and(|c| *c == ']') =>
|
});
|
||||||
{ /* noop */ }
|
if let Some(span) = check_character(ctx, ch, character_class, unicode_sets) {
|
||||||
'^' if start_char_class => { /* noop */ }
|
spans.push(span);
|
||||||
_ => {
|
}
|
||||||
let escapes = if in_character_class {
|
}
|
||||||
|
_ => (),
|
||||||
|
});
|
||||||
|
|
||||||
|
spans
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_character_class(
|
||||||
|
ctx: &LintContext,
|
||||||
|
character_class: &oxc_allocator::Box<CharacterClass>,
|
||||||
|
unicode_sets: bool,
|
||||||
|
spans: &mut Vec<Span>,
|
||||||
|
) {
|
||||||
|
for term in &character_class.body {
|
||||||
|
match term {
|
||||||
|
CharacterClassContents::Character(ch) => {
|
||||||
|
if let Some(span) = check_character(ctx, ch, Some(character_class), unicode_sets) {
|
||||||
|
spans.push(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CharacterClassContents::NestedCharacterClass(nested_class) => {
|
||||||
|
check_character_class(ctx, nested_class, unicode_sets, spans);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_character(
|
||||||
|
ctx: &LintContext,
|
||||||
|
character: &Character,
|
||||||
|
character_class: Option<&oxc_allocator::Box<CharacterClass>>,
|
||||||
|
unicode_sets: bool,
|
||||||
|
) -> Option<Span> {
|
||||||
|
let char_text = character.span.source_text(ctx.source_text());
|
||||||
|
let is_escaped = char_text.starts_with('\\');
|
||||||
|
if !is_escaped {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let span = character.span;
|
||||||
|
let escape_char = char_text.chars().nth(1).unwrap();
|
||||||
|
let escapes = if character_class.is_some() {
|
||||||
|
if unicode_sets {
|
||||||
|
REGEX_CLASSSET_CHARACTER_ESCAPES
|
||||||
|
} else {
|
||||||
REGEX_GENERAL_ESCAPES
|
REGEX_GENERAL_ESCAPES
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
REGEX_NON_CHARCLASS_ESCAPES
|
REGEX_NON_CHARCLASS_ESCAPES
|
||||||
};
|
};
|
||||||
if !escapes.contains(c) {
|
if escapes.contains(escape_char) {
|
||||||
offsets.push(offset);
|
return None;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if c == '/' && !in_character_class {
|
|
||||||
break;
|
|
||||||
} else if c == '[' {
|
|
||||||
in_character_class = true;
|
|
||||||
start_char_class = true;
|
|
||||||
} else if c == '\\' {
|
|
||||||
in_escape = true;
|
|
||||||
} else if c == ']' {
|
|
||||||
in_character_class = false;
|
|
||||||
} else {
|
|
||||||
start_char_class = false;
|
|
||||||
}
|
|
||||||
offset += c.len_utf8();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
offsets
|
if let Some(class) = character_class {
|
||||||
|
if escape_char == '^' {
|
||||||
|
/* The '^' character is also a special case; it must always be escaped outside of character classes, but
|
||||||
|
* it only needs to be escaped in character classes if it's at the beginning of the character class. To
|
||||||
|
* account for this, consider it to be a valid escape character outside of character classes, and filter
|
||||||
|
* out '^' characters that appear at the start of a character class.
|
||||||
|
* (From ESLint source: https://github.com/eslint/eslint/blob/main/lib/rules/no-useless-escape.js)
|
||||||
|
*/
|
||||||
|
if class.span.start + 1 == span.start {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unicode_sets {
|
||||||
|
if REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.contains(escape_char) {
|
||||||
|
if let Some(prev_char) = ctx.source_text().chars().nth(span.end as usize) {
|
||||||
|
// Escaping is valid when it is a reserved double punctuator
|
||||||
|
if prev_char == escape_char {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(prev_prev_char) = ctx.source_text().chars().nth(span.start as usize - 1)
|
||||||
|
{
|
||||||
|
if prev_prev_char == escape_char {
|
||||||
|
if escape_char != '^' {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escaping caret is unnecessary if the previous character is a `negate` caret(`^`).
|
||||||
|
if !class.negative {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let caret_index = class.span.start + 1;
|
||||||
|
if caret_index < span.start - 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if escape_char == '-' {
|
||||||
|
/* The '-' character is a special case, because it's only valid to escape it if it's in a character
|
||||||
|
* class, and is not at either edge of the character class. To account for this, don't consider '-'
|
||||||
|
* characters to be valid in general, and filter out '-' characters that appear in the middle of a
|
||||||
|
* character class.
|
||||||
|
* (From ESLint source: https://github.com/eslint/eslint/blob/main/lib/rules/no-useless-escape.js)
|
||||||
|
*/
|
||||||
|
if class.span.start + 1 != span.start && span.end != class.span.end - 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(span)
|
||||||
}
|
}
|
||||||
|
|
||||||
const VALID_STRING_ESCAPES: &str = "\\nrvtbfux\n\r\u{2028}\u{2029}";
|
const VALID_STRING_ESCAPES: &str = "\\nrvtbfux\n\r\u{2028}\u{2029}";
|
||||||
|
|
@ -262,6 +348,68 @@ fn check_template(string: &str) -> Vec<usize> {
|
||||||
offsets
|
offsets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Visit<'a> {
|
||||||
|
Term(&'a Term<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Replace with proper visitor pattern for the regex AST when available
|
||||||
|
/// Calls the given closure on every [`Term`] in the [`Pattern`].
|
||||||
|
fn visit_terms<'a, F: FnMut(&'a Term<'a>, &Vec<Visit<'a>>)>(pattern: &'a Pattern, f: &mut F) {
|
||||||
|
// initialize visit stack with enough initial capacity so we will not need to reallocate
|
||||||
|
// in general (most regex patterns will probably not be this many items deep)
|
||||||
|
let mut stack: Vec<Visit> = Vec::with_capacity(16);
|
||||||
|
visit_terms_disjunction(&pattern.body, f, &mut stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls the given closure on every [`Term`] in the [`Disjunction`].
|
||||||
|
fn visit_terms_disjunction<'a, F: FnMut(&'a Term<'a>, &Vec<Visit<'a>>)>(
|
||||||
|
disjunction: &'a Disjunction,
|
||||||
|
f: &mut F,
|
||||||
|
stack: &mut Vec<Visit<'a>>,
|
||||||
|
) {
|
||||||
|
for alternative in &disjunction.body {
|
||||||
|
visit_terms_alternative(alternative, f, stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls the given closure on every [`Term`] in the [`Alternative`].
|
||||||
|
fn visit_terms_alternative<'a, F: FnMut(&'a Term<'a>, &Vec<Visit<'a>>)>(
|
||||||
|
alternative: &'a Alternative,
|
||||||
|
f: &mut F,
|
||||||
|
stack: &mut Vec<Visit<'a>>,
|
||||||
|
) {
|
||||||
|
for term in &alternative.body {
|
||||||
|
match term {
|
||||||
|
Term::LookAroundAssertion(lookaround) => {
|
||||||
|
stack.push(Visit::Term(term));
|
||||||
|
f(term, stack);
|
||||||
|
visit_terms_disjunction(&lookaround.body, f, stack);
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
Term::Quantifier(quant) => {
|
||||||
|
stack.push(Visit::Term(term));
|
||||||
|
f(term, stack);
|
||||||
|
f(&quant.body, stack);
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
Term::CapturingGroup(group) => {
|
||||||
|
stack.push(Visit::Term(term));
|
||||||
|
f(term, stack);
|
||||||
|
visit_terms_disjunction(&group.body, f, stack);
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
Term::IgnoreGroup(group) => {
|
||||||
|
stack.push(Visit::Term(term));
|
||||||
|
f(term, stack);
|
||||||
|
visit_terms_disjunction(&group.body, f, stack);
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
_ => f(term, stack),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
use crate::tester::Tester;
|
use crate::tester::Tester;
|
||||||
|
|
@ -369,6 +517,61 @@ fn test() {
|
||||||
"var foo = /[\\p{ASCII}]/u",
|
"var foo = /[\\p{ASCII}]/u",
|
||||||
"var foo = /[\\P{ASCII}]/u",
|
"var foo = /[\\P{ASCII}]/u",
|
||||||
"`${/\\s+/g}`",
|
"`${/\\s+/g}`",
|
||||||
|
// Carets
|
||||||
|
"/[^^]/u", // { "ecmaVersion": 2015 },
|
||||||
|
// ES2024
|
||||||
|
r"/[\q{abc}]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\(]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\)]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\{]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\]]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\}]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\/]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\-]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\|]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\$$]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\&&]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\!!]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\##]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\%%]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\**]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\++]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\,,]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\..]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\::]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\;;]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\<<]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\==]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\>>]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\??]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\@@]/v", // { "ecmaVersion": 2024 },
|
||||||
|
"/[\\``]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\~~]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[^\^^]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[_\^^]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[$\$]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[&\&]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[!\!]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[#\#]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[%\%]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[*\*]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[+\+]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[,\,]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[.\.]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[:\:]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[;\;]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[<\<]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[=\=]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[>\>]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[?\?]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[@\@]/v", // { "ecmaVersion": 2024 },
|
||||||
|
"/[`\\`]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[~\~]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[^^\^]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[_^\^]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\&&&\&]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[[\-]\-]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\^]/v", // { "ecmaVersion": 2024 }
|
||||||
];
|
];
|
||||||
|
|
||||||
let fail = vec![
|
let fail = vec![
|
||||||
|
|
@ -424,6 +627,43 @@ fn test() {
|
||||||
r"var foo = /\(([^\)\(]+)\)$|\(([^\)\)]+)\)$/;",
|
r"var foo = /\(([^\)\(]+)\)$|\(([^\)\)]+)\)$/;",
|
||||||
r#"var stringLiteralWithNextLine = "line 1\
line 2";"#,
|
r#"var stringLiteralWithNextLine = "line 1\
line 2";"#,
|
||||||
r"var stringLiteralWithNextLine = `line 1\
line 2`;",
|
r"var stringLiteralWithNextLine = `line 1\
line 2`;",
|
||||||
|
r#""use\ strict";"#,
|
||||||
|
// spellchecker:off
|
||||||
|
r#"({ foo() { "foo"; "bar"; "ba\z" } })"#, // { "ecmaVersion": 6 }
|
||||||
|
// spellchecker:on
|
||||||
|
// Carets
|
||||||
|
r"/[^\^]/",
|
||||||
|
r"/[^\^]/u", // { "ecmaVersion": 2015 },
|
||||||
|
// ES2024
|
||||||
|
r"/[\$]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\&\&]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\!\!]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\#\#]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\%\%]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\*\*]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\+\+]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\,\,]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\.\.]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\:\:]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\;\;]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\<\<]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\=\=]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\>\>]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\?\?]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\@\@]/v", // { "ecmaVersion": 2024 },
|
||||||
|
"/[\\`\\`]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\~\~]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[^\^\^]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[_\^\^]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\&\&&\&]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\p{ASCII}--\.]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\p{ASCII}&&\.]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\.--[.&]]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\.&&[.&]]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\.--\.--\.]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[\.&&\.&&\.]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[[\.&]--[\.&]]/v", // { "ecmaVersion": 2024 },
|
||||||
|
r"/[[\.&]&&[\.&]]/v", // { "ecmaVersion": 2024 }
|
||||||
];
|
];
|
||||||
|
|
||||||
let fix = vec![
|
let fix = vec![
|
||||||
|
|
@ -452,6 +692,43 @@ fn test() {
|
||||||
("let foo = '\\ ';", "let foo = ' ';", None),
|
("let foo = '\\ ';", "let foo = ' ';", None),
|
||||||
("let foo = /\\ /;", "let foo = / /;", None),
|
("let foo = /\\ /;", "let foo = / /;", None),
|
||||||
("var foo = `\\$\\{{${foo}`;", "var foo = `$\\{{${foo}`;", None),
|
("var foo = `\\$\\{{${foo}`;", "var foo = `$\\{{${foo}`;", None),
|
||||||
|
(r#""use\ strict";"#, r#""use strict";"#, None),
|
||||||
|
// spellchecker:off
|
||||||
|
(r#"({ foo() { "foo"; "bar"; "ba\z" } })"#, r#"({ foo() { "foo"; "bar"; "baz" } })"#, None), // { "ecmaVersion": 6 }
|
||||||
|
// spellchecker:on
|
||||||
|
// Carets
|
||||||
|
(r"/[^\^]/", r"/[^^]/", None),
|
||||||
|
(r"/[^\^]/u", r"/[^^]/u", None), // { "ecmaVersion": 2015 },
|
||||||
|
// ES2024
|
||||||
|
(r"/[\$]/v", r"/[$]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\&\&]/v", r"/[&\&]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\!\!]/v", r"/[!\!]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\#\#]/v", r"/[#\#]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\%\%]/v", r"/[%\%]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\*\*]/v", r"/[*\*]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\+\+]/v", r"/[+\+]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\,\,]/v", r"/[,\,]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\.\.]/v", r"/[.\.]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\:\:]/v", r"/[:\:]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\;\;]/v", r"/[;\;]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\<\<]/v", r"/[<\<]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\=\=]/v", r"/[=\=]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\>\>]/v", r"/[>\>]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\?\?]/v", r"/[?\?]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\@\@]/v", r"/[@\@]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\`\`]/v", r"/[`\`]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\~\~]/v", r"/[~\~]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[^\^\^]/v", r"/[^^\^]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[_\^\^]/v", r"/[_^\^]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\&\&&\&]/v", r"/[&\&&\&]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\p{ASCII}--\.]/v", r"/[\p{ASCII}--.]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\p{ASCII}&&\.]/v", r"/[\p{ASCII}&&.]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\.--[.&]]/v", r"/[.--[.&]]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\.&&[.&]]/v", r"/[.&&[.&]]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\.--\.--\.]/v", r"/[.--.--.]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[\.&&\.&&\.]/v", r"/[.&&.&&.]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[[\.&]--[\.&]]/v", r"/[[.&]--[.&]]/v", None), // { "ecmaVersion": 2024 },
|
||||||
|
(r"/[[\.&]&&[\.&]]/v", r"/[[.&]&&[.&]]/v", None), // { "ecmaVersion": 2024 }
|
||||||
];
|
];
|
||||||
|
|
||||||
Tester::new(NoUselessEscape::NAME, pass, fail).expect_fix(fix).test_and_snapshot();
|
Tester::new(NoUselessEscape::NAME, pass, fail).expect_fix(fix).test_and_snapshot();
|
||||||
|
|
|
||||||
|
|
@ -440,3 +440,276 @@ source: crates/oxc_linter/src/tester.rs
|
||||||
· ─
|
· ─
|
||||||
╰────
|
╰────
|
||||||
help: Replace `\
` with `
`.
|
help: Replace `\
` with `
`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character ' '
|
||||||
|
╭─[no_useless_escape.tsx:1:5]
|
||||||
|
1 │ "use\ strict";
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\ ` with ` `.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character 'z'
|
||||||
|
╭─[no_useless_escape.tsx:1:29]
|
||||||
|
1 │ ({ foo() { "foo"; "bar"; "ba\z" } })
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\z` with `z`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '^'
|
||||||
|
╭─[no_useless_escape.tsx:1:4]
|
||||||
|
1 │ /[^\^]/
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\^` with `^`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '^'
|
||||||
|
╭─[no_useless_escape.tsx:1:4]
|
||||||
|
1 │ /[^\^]/u
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\^` with `^`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '$'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\$]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\$` with `$`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '&'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\&\&]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\&` with `&`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '!'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\!\!]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\!` with `!`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '#'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\#\#]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\#` with `#`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '%'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\%\%]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\%` with `%`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '*'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\*\*]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\*` with `*`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '+'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\+\+]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\+` with `+`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character ','
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\,\,]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\,` with `,`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\.\.]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character ':'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\:\:]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\:` with `:`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character ';'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\;\;]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\;` with `;`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '<'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\<\<]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\<` with `<`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '='
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\=\=]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\=` with `=`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '>'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\>\>]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\>` with `>`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '?'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\?\?]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\?` with `?`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '@'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\@\@]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\@` with `@`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '`'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\`\`]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\`` with ```.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '~'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\~\~]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\~` with `~`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '^'
|
||||||
|
╭─[no_useless_escape.tsx:1:4]
|
||||||
|
1 │ /[^\^\^]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\^` with `^`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '^'
|
||||||
|
╭─[no_useless_escape.tsx:1:4]
|
||||||
|
1 │ /[_\^\^]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\^` with `^`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '&'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\&\&&\&]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\&` with `&`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:14]
|
||||||
|
1 │ /[\p{ASCII}--\.]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:14]
|
||||||
|
1 │ /[\p{ASCII}&&\.]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\.--[.&]]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\.&&[.&]]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\.--\.--\.]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:7]
|
||||||
|
1 │ /[\.--\.--\.]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:11]
|
||||||
|
1 │ /[\.--\.--\.]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:3]
|
||||||
|
1 │ /[\.&&\.&&\.]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:7]
|
||||||
|
1 │ /[\.&&\.&&\.]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:11]
|
||||||
|
1 │ /[\.&&\.&&\.]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:4]
|
||||||
|
1 │ /[[\.&]--[\.&]]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:11]
|
||||||
|
1 │ /[[\.&]--[\.&]]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:4]
|
||||||
|
1 │ /[[\.&]&&[\.&]]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
||||||
|
⚠ eslint(no-useless-escape): Unnecessary escape character '.'
|
||||||
|
╭─[no_useless_escape.tsx:1:11]
|
||||||
|
1 │ /[[\.&]&&[\.&]]/v
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Replace `\.` with `.`.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue