From 2b65ed2a69093f30fb2ddaec662ca2b776dd96d4 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Mon, 17 Mar 2025 14:15:19 +0000 Subject: [PATCH] perf(linter/no_unescaped_entities): optimize string search and error generation (#9832) #9777 made `>` or `}` in `JSXText` a parser error, so `react/no_unescaped_entities` rule can be simplified. Speed it up a little by: * Using `raw` field instead of slicing source. * Iterating over the string as bytes, instead of `char`s. * Using static strings for errors. --- .../src/rules/react/no_unescaped_entities.rs | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/crates/oxc_linter/src/rules/react/no_unescaped_entities.rs b/crates/oxc_linter/src/rules/react/no_unescaped_entities.rs index 8de3c04835f6c..ae0839143404c 100644 --- a/crates/oxc_linter/src/rules/react/no_unescaped_entities.rs +++ b/crates/oxc_linter/src/rules/react/no_unescaped_entities.rs @@ -2,7 +2,6 @@ use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use phf::{Map, phf_map}; use crate::{ AstNode, @@ -10,7 +9,11 @@ use crate::{ rule::Rule, }; -fn no_unescaped_entities_diagnostic(span: Span, unescaped: char, escaped: &str) -> OxcDiagnostic { +static ESCAPED_DOUBLE_QUOTE: &str = "" or “ or " or ”"; +static ESCAPED_SINGLE_QUOTE: &str = "' or ‘ or ' or ’"; + +fn no_unescaped_entities_diagnostic(span: Span, unescaped: char) -> OxcDiagnostic { + let escaped = if unescaped == '"' { ESCAPED_DOUBLE_QUOTE } else { ESCAPED_SINGLE_QUOTE }; OxcDiagnostic::warn(format!("`{unescaped}` can be escaped with {escaped}")).with_label(span) } @@ -50,20 +53,14 @@ declare_oxc_lint!( impl Rule for NoUnescapedEntities { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXText(jsx_text) = node.kind() { - let source = jsx_text.span.source_text(ctx.source_text()); - for (i, char) in source.char_indices() { - if !CHARS.contains(&char) { - continue; - } - if let Some(escapes) = DEFAULTS.get(&char) { + let source = jsx_text.raw.unwrap().as_str(); + for (i, &byte) in source.as_bytes().iter().enumerate() { + if matches!(byte, b'\'' | b'\"') { #[expect(clippy::cast_possible_truncation)] + let start = jsx_text.span.start + i as u32; ctx.diagnostic(no_unescaped_entities_diagnostic( - Span::new( - jsx_text.span.start + i as u32, - jsx_text.span.start + i as u32 + 1, - ), - char, - &escapes.join(" or "), + Span::new(start, start + 1), + byte as char, )); } } @@ -75,14 +72,6 @@ impl Rule for NoUnescapedEntities { } } -// NOTE: If we add substantially more characters, we should consider using a hash set instead. -pub const CHARS: [char; 2] = ['"', '\'']; - -pub const DEFAULTS: Map = phf_map! { - '"' => &[""", "“", """, "”"], - '\'' => &["'", "‘", "'", "’"], -}; - #[test] fn test() { use crate::tester::Tester;