mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
perf(formatter): write string literals with swapped quotes into the arena (#27558)
A string literal whose quotes change was built with `format!` on the heap, and then copied into the arena to be written. It is now built in the arena directly by `alloc_concat_strs_array`, through one helper for every place that puts content in quotes (string literals, directives, JSX attributes). Tested on vscode `src/` (NOTE: This repo is written with single quotes, no formatter installed, so formatted with the default double quotes), 1 thread: 2.6% fewer instructions, 2.2% fewer cycles. The output is unchanged byte for byte. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
073413771b
commit
d8ce750585
2 files changed
+40
-50
No files matched your search
@@ -2,6 +2,7 @@ use std::{borrow::Cow, ops::Deref};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_formatter_core::{Format, arena_cow_str, spec::normalize_string};
|
||||
use oxc_span::SourceType;
|
||||
use oxc_syntax::identifier::is_identifier_name_patched;
|
||||
@@ -55,7 +56,7 @@ impl<'a> FormatLiteralStringToken<'a> {
|
||||
};
|
||||
|
||||
let string_cleaner =
|
||||
LiteralStringNormalizer::new(*self, chosen_quote_style, is_quote_needed);
|
||||
LiteralStringNormalizer::new(*self, chosen_quote_style, is_quote_needed, f.allocator());
|
||||
|
||||
let content = string_cleaner.normalize_text(source_type);
|
||||
|
||||
@@ -83,9 +84,8 @@ impl CleanedStringLiteralText<'_> {
|
||||
|
||||
impl<'a> Format<'a, JsFormatContext<'a>> for CleanedStringLiteralText<'a> {
|
||||
fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
|
||||
// In the common case the literal needs no normalization, so `text` borrows a slice of the
|
||||
// source (`Cow::Borrowed`) which already outlives the buffer; pass it straight through and
|
||||
// only copy into the arena for the owned (normalized) case.
|
||||
// `text` borrows the source or the arena (quotes swapped) in all but the rare cases left owned,
|
||||
// which are copied into the arena here.
|
||||
text(arena_cow_str(&self.text, f)).fmt(f);
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,8 @@ struct LiteralStringNormalizer<'a> {
|
||||
chosen_quote_style: QuoteStyle,
|
||||
/// State whether we need to print the quotes or not.
|
||||
is_quote_needed: bool,
|
||||
/// Arena for the strings with other quotes, so that `fmt` does not copy them again.
|
||||
allocator: &'a Allocator,
|
||||
}
|
||||
|
||||
impl<'a> LiteralStringNormalizer<'a> {
|
||||
@@ -196,8 +198,9 @@ impl<'a> LiteralStringNormalizer<'a> {
|
||||
token: FormatLiteralStringToken<'a>,
|
||||
chosen_quote_style: QuoteStyle,
|
||||
is_quote_needed: bool,
|
||||
allocator: &'a Allocator,
|
||||
) -> Self {
|
||||
Self { token, chosen_quote_style, is_quote_needed }
|
||||
Self { token, chosen_quote_style, is_quote_needed, allocator }
|
||||
}
|
||||
|
||||
fn normalize_text(&self, source_type: SourceType) -> Cow<'a, str> {
|
||||
@@ -223,23 +226,13 @@ impl<'a> LiteralStringNormalizer<'a> {
|
||||
|
||||
let (normalized, chosen_quote) = normalize_jsx_string(raw_content, self.chosen_quote_style);
|
||||
|
||||
let quote_char = chosen_quote.as_char();
|
||||
|
||||
match normalized {
|
||||
Cow::Borrowed(s) if s == raw_content && current_quote == chosen_quote => {
|
||||
// No changes needed, return original
|
||||
normalize_newlines(self.token.string, ['\r'])
|
||||
}
|
||||
Cow::Borrowed(s) => {
|
||||
// Content unchanged but quotes need swapping
|
||||
let normalized_newlines = normalize_newlines(s, ['\r']);
|
||||
Cow::Owned(std::format!("{quote_char}{normalized_newlines}{quote_char}"))
|
||||
}
|
||||
Cow::Owned(s) => {
|
||||
// Content changed
|
||||
let normalized_newlines = normalize_newlines(&s, ['\r']);
|
||||
Cow::Owned(std::format!("{quote_char}{normalized_newlines}{quote_char}"))
|
||||
}
|
||||
// Content changed or quotes need swapping
|
||||
_ => self.quoted(chosen_quote, &normalize_newlines(&normalized, ['\r'])),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,16 +266,12 @@ impl<'a> LiteralStringNormalizer<'a> {
|
||||
}
|
||||
}
|
||||
Cow::Owned(string) => {
|
||||
let mut s = String::with_capacity(string.len() + 2);
|
||||
let quote = if string_information.raw_content_has_quotes {
|
||||
string_information.current_quote.as_char()
|
||||
string_information.current_quote
|
||||
} else {
|
||||
string_information.preferred_quote.as_char()
|
||||
string_information.preferred_quote
|
||||
};
|
||||
s.push(quote);
|
||||
s.push_str(&string);
|
||||
s.push(quote);
|
||||
Cow::Owned(s)
|
||||
self.quoted(quote, &string)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,13 +322,9 @@ impl<'a> LiteralStringNormalizer<'a> {
|
||||
|
||||
match polished_raw_content {
|
||||
Cow::Borrowed(raw_content) => self.swap_quotes(raw_content, string_information),
|
||||
Cow::Owned(mut s) => {
|
||||
// content is owned, meaning we allocated a new string,
|
||||
// so we force replacing quotes, regardless
|
||||
s.insert(0, preferred_quote.as_char());
|
||||
s.push(preferred_quote.as_char());
|
||||
Cow::Owned(s)
|
||||
}
|
||||
// content is owned, meaning we allocated a new string,
|
||||
// so we force replacing quotes, regardless
|
||||
Cow::Owned(s) => self.quoted(preferred_quote, &s),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,15 +335,20 @@ impl<'a> LiteralStringNormalizer<'a> {
|
||||
}
|
||||
|
||||
fn swap_quotes(&self, content_to_use: &'a str, str_info: StringInformation) -> Cow<'a, str> {
|
||||
let preferred_quote = str_info.preferred_quote.as_char();
|
||||
let original = self.token.string;
|
||||
|
||||
if original.starts_with(preferred_quote) {
|
||||
if original.starts_with(str_info.preferred_quote.as_char()) {
|
||||
Cow::Borrowed(original)
|
||||
} else {
|
||||
Cow::Owned(std::format!("{preferred_quote}{content_to_use}{preferred_quote}"))
|
||||
self.quoted(str_info.preferred_quote, content_to_use)
|
||||
}
|
||||
}
|
||||
|
||||
/// `content` in `quote`s, in the arena.
|
||||
fn quoted(&self, quote: QuoteStyle, content: &str) -> Cow<'a, str> {
|
||||
let quote = quote.as_str();
|
||||
Cow::Borrowed(self.allocator.alloc_concat_strs_array([quote, content, quote]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Format<'a, JsFormatContext<'a>> for FormatLiteralStringToken<'a> {
|
||||
|
||||
@@ -22,9 +22,9 @@ App.tsx:
|
||||
|
||||
RadixUIAdoptionSection.jsx:
|
||||
file size: 2520 # 2.52 kB
|
||||
sys allocs: 92
|
||||
sys reallocs: 74
|
||||
sys deallocs: 91
|
||||
sys allocs: 69
|
||||
sys reallocs: 69
|
||||
sys deallocs: 68
|
||||
sys alloc bytes: 36132 # 36.13 kB
|
||||
sys peak growth: 12192 # 12.19 kB
|
||||
arena allocs: 1149
|
||||
@@ -33,9 +33,9 @@ RadixUIAdoptionSection.jsx:
|
||||
|
||||
pdf.mjs:
|
||||
file size: 567296 # 567.30 kB
|
||||
sys allocs: 9782
|
||||
sys reallocs: 1328
|
||||
sys deallocs: 9781
|
||||
sys allocs: 9777
|
||||
sys reallocs: 1323
|
||||
sys deallocs: 9776
|
||||
sys alloc bytes: 3141552 # 3.14 MB
|
||||
sys peak growth: 1156432 # 1.16 MB
|
||||
arena allocs: 429815
|
||||
@@ -44,12 +44,12 @@ pdf.mjs:
|
||||
|
||||
antd.js:
|
||||
file size: 6686316 # 6.69 MB
|
||||
sys allocs: 90046
|
||||
sys reallocs: 7658
|
||||
sys deallocs: 90045
|
||||
sys allocs: 84592
|
||||
sys reallocs: 4017
|
||||
sys deallocs: 84591
|
||||
sys alloc bytes: 76472316 # 76.47 MB
|
||||
sys peak growth: 50737424 # 50.74 MB
|
||||
arena allocs: 2535459
|
||||
arena allocs: 2535528
|
||||
arena reallocs: 1889
|
||||
arena size: 246383800 # 246.38 MB
|
||||
|
||||
@@ -66,11 +66,11 @@ binder.ts:
|
||||
|
||||
kitchen-sink.tsx:
|
||||
file size: 732898 # 732.90 kB
|
||||
sys allocs: 17090
|
||||
sys reallocs: 4956
|
||||
sys deallocs: 17089
|
||||
sys alloc bytes: 4436340 # 4.44 MB
|
||||
sys allocs: 12672
|
||||
sys reallocs: 2347
|
||||
sys deallocs: 12671
|
||||
sys alloc bytes: 4353874 # 4.35 MB
|
||||
sys peak growth: 1491796 # 1.49 MB
|
||||
arena allocs: 557531
|
||||
arena allocs: 557605
|
||||
arena reallocs: 1041
|
||||
arena size: 38432064 # 38.43 MB
|
||||
arena size: 38433168 # 38.43 MB
|
||||
Reference in new issue
Block a user