mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
refactor(linter): use parsed patterns for unicorn/no-hex-escape (#5985)
- part of https://github.com/oxc-project/oxc/issues/5416 This pull request includes significant improvements to the `no_hex_escape` rule in the `oxc_linter` crate. The changes enhance the detection and replacement of hexadecimal escapes within regular expressions by introducing a more comprehensive AST traversal. - Implemented a new `visit_terms` function and its helper functions to traverse the regex AST and apply checks on individual terms. - Introduced the `check_character` function to replace hexadecimal escapes with Unicode escapes within regex patterns. - Updated snapshots to reflect the new diagnostic messages and replacements for hexadecimal escapes in regex patterns.
This commit is contained in:
parent
b681c9a12c
commit
0a5a4a9812
2 changed files with 96 additions and 19 deletions
|
|
@ -4,6 +4,9 @@ use oxc_ast::{
|
||||||
};
|
};
|
||||||
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, CharacterClassContents, CharacterKind, Disjunction, Pattern, Term,
|
||||||
|
};
|
||||||
use oxc_span::Span;
|
use oxc_span::Span;
|
||||||
|
|
||||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||||
|
|
@ -57,7 +60,7 @@ fn check_escape(value: &str) -> Option<String> {
|
||||||
if matched.is_empty() {
|
if matched.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let mut fixed = String::with_capacity(value.len() + matched.len() * 2);
|
let mut fixed: String = String::with_capacity(value.len() + matched.len() * 2);
|
||||||
let mut last = 0;
|
let mut last = 0;
|
||||||
for index in matched {
|
for index in matched {
|
||||||
fixed.push_str(&value[last..index - 1]);
|
fixed.push_str(&value[last..index - 1]);
|
||||||
|
|
@ -90,26 +93,87 @@ impl Rule for NoHexEscape {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AstKind::RegExpLiteral(regex) => {
|
AstKind::RegExpLiteral(regex) => {
|
||||||
if let Some(fixed) =
|
let Some(pattern) = regex.regex.pattern.as_pattern() else {
|
||||||
check_escape(regex.regex.pattern.source_text(ctx.source_text()).as_ref())
|
return;
|
||||||
{
|
};
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
ctx.diagnostic_with_fix(no_hex_escape_diagnostic(regex.span), |fixer| {
|
visit_terms(pattern, &mut |term| match term {
|
||||||
fixer.replace(
|
Term::Character(ch) => {
|
||||||
Span::new(
|
check_character(ch, ctx);
|
||||||
regex.span.start,
|
}
|
||||||
regex.span.end - regex.regex.flags.iter().count() as u32,
|
Term::CharacterClass(class) => {
|
||||||
),
|
for term in &class.body {
|
||||||
format!("/{fixed}/"),
|
match term {
|
||||||
)
|
CharacterClassContents::Character(ch) => {
|
||||||
});
|
check_character(ch, ctx);
|
||||||
}
|
}
|
||||||
|
CharacterClassContents::CharacterClassRange(range) => {
|
||||||
|
check_character(&range.min, ctx);
|
||||||
|
check_character(&range.max, ctx);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_character(ch: &Character, ctx: &LintContext) {
|
||||||
|
if ch.kind == CharacterKind::HexadecimalEscape {
|
||||||
|
let unicode_escape = format!(r"\u00{}", &ch.span.source_text(ctx.source_text())[2..]);
|
||||||
|
ctx.diagnostic_with_fix(no_hex_escape_diagnostic(ch.span), |fixer| {
|
||||||
|
fixer.replace(ch.span, unicode_escape)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Replace with proper regex AST visitor when available
|
||||||
|
/// Calls the given closure on every [`Term`] in the [`Pattern`].
|
||||||
|
fn visit_terms<'a, F: FnMut(&'a Term<'a>)>(pattern: &'a Pattern, f: &mut F) {
|
||||||
|
visit_terms_disjunction(&pattern.body, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls the given closure on every [`Term`] in the [`Disjunction`].
|
||||||
|
fn visit_terms_disjunction<'a, F: FnMut(&'a Term<'a>)>(disjunction: &'a Disjunction, f: &mut F) {
|
||||||
|
for alternative in &disjunction.body {
|
||||||
|
visit_terms_alternative(alternative, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls the given closure on every [`Term`] in the [`Alternative`].
|
||||||
|
fn visit_terms_alternative<'a, F: FnMut(&'a Term<'a>)>(alternative: &'a Alternative, f: &mut F) {
|
||||||
|
for term in &alternative.body {
|
||||||
|
visit_term(term, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_term<'a, F: FnMut(&'a Term<'a>)>(term: &'a Term<'a>, f: &mut F) {
|
||||||
|
match term {
|
||||||
|
Term::LookAroundAssertion(lookaround) => {
|
||||||
|
f(term);
|
||||||
|
visit_terms_disjunction(&lookaround.body, f);
|
||||||
|
}
|
||||||
|
Term::Quantifier(quant) => {
|
||||||
|
f(term);
|
||||||
|
visit_term(&quant.body, f);
|
||||||
|
}
|
||||||
|
Term::CapturingGroup(group) => {
|
||||||
|
f(term);
|
||||||
|
visit_terms_disjunction(&group.body, f);
|
||||||
|
}
|
||||||
|
Term::IgnoreGroup(group) => {
|
||||||
|
f(term);
|
||||||
|
visit_terms_disjunction(&group.body, f);
|
||||||
|
}
|
||||||
|
_ => f(term),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
use crate::tester::Tester;
|
use crate::tester::Tester;
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,22 @@ source: crates/oxc_linter/src/tester.rs
|
||||||
help: Replace `"\xb1"` with `'\u00b1'`.
|
help: Replace `"\xb1"` with `'\u00b1'`.
|
||||||
|
|
||||||
⚠ eslint-plugin-unicorn(no-hex-escape): Use Unicode escapes instead of hexadecimal escapes.
|
⚠ eslint-plugin-unicorn(no-hex-escape): Use Unicode escapes instead of hexadecimal escapes.
|
||||||
╭─[no_hex_escape.tsx:1:8]
|
╭─[no_hex_escape.tsx:1:41]
|
||||||
1 │ wrapId(/(^|[<nonId>])(?:алг|арг(?:\x20*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать|кон(?:(?:\x20+|_)исп)?|кц(?:(?:\x20+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[<nonId>]|$)/.source)
|
1 │ wrapId(/(^|[<nonId>])(?:алг|арг(?:\x20*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать|кон(?:(?:\x20+|_)исп)?|кц(?:(?:\x20+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[<nonId>]|$)/.source)
|
||||||
· ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
· ────
|
||||||
╰────
|
╰────
|
||||||
help: Replace `/(^|[<nonId>])(?:алг|арг(?:\x20*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать|кон(?:(?:\x20+|_)исп)?|кц(?:(?:\x20+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[<nonId>]|$)/...` with `/(^|[<nonId>])(?:алг|арг(?:\u0020*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать|
|
help: Replace `\x20` with `\u0020`.
|
||||||
кон(?:(?:\u0020+|_)исп)?|кц(?:(?:\u0020+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[<nonId>]|$)/...`.
|
|
||||||
|
⚠ eslint-plugin-unicorn(no-hex-escape): Use Unicode escapes instead of hexadecimal escapes.
|
||||||
|
╭─[no_hex_escape.tsx:1:215]
|
||||||
|
1 │ wrapId(/(^|[<nonId>])(?:алг|арг(?:\x20*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать|кон(?:(?:\x20+|_)исп)?|кц(?:(?:\x20+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[<nonId>]|$)/.source)
|
||||||
|
· ────
|
||||||
|
╰────
|
||||||
|
help: Replace `\x20` with `\u0020`.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-unicorn(no-hex-escape): Use Unicode escapes instead of hexadecimal escapes.
|
||||||
|
╭─[no_hex_escape.tsx:1:242]
|
||||||
|
1 │ wrapId(/(^|[<nonId>])(?:алг|арг(?:\x20*рез)?|ввод|ВКЛЮЧИТЬ|вс[её]|выбор|вывод|выход|дано|для|до|дс|если|иначе|исп|использовать|кон(?:(?:\x20+|_)исп)?|кц(?:(?:\x20+|_)при)?|надо|нач|нс|нц|от|пауза|пока|при|раза?|рез|стоп|таб|то|утв|шаг)(?=[<nonId>]|$)/.source)
|
||||||
|
· ────
|
||||||
|
╰────
|
||||||
|
help: Replace `\x20` with `\u0020`.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue