use oxc_data_structures::code_buffer::CodeBuffer;
use super::{ESTree, Serializer};
const fn to_bytes<const N: usize>(ch: char) -> [u8; N] {
let mut bytes = [0u8; N];
ch.encode_utf8(&mut bytes);
bytes
}
const LOSSY_REPLACEMENT_CHAR_BYTES: [u8; 3] = to_bytes('\u{FFFD}');
const LOSSY_REPLACEMENT_CHAR_FIRST_BYTE: u8 = LOSSY_REPLACEMENT_CHAR_BYTES[0];
pub struct JsonSafeString<'s>(pub &'s str);
impl ESTree for JsonSafeString<'_> {
#[inline(always)]
fn serialize<S: Serializer>(&self, mut serializer: S) {
let buffer = serializer.buffer_mut();
buffer.print_ascii_byte(b'"');
buffer.print_str(self.0);
buffer.print_ascii_byte(b'"');
}
}
pub struct LoneSurrogatesString<'s>(pub &'s str);
impl ESTree for LoneSurrogatesString<'_> {
#[inline(always)]
fn serialize<S: Serializer>(&self, mut serializer: S) {
write_str(self.0, &ESCAPE_LONE_SURROGATES, serializer.buffer_mut());
}
}
impl ESTree for str {
fn serialize<S: Serializer>(&self, mut serializer: S) {
write_str(self, &ESCAPE, serializer.buffer_mut());
}
}
impl ESTree for String {
fn serialize<S: Serializer>(&self, serializer: S) {
self.as_str().serialize(serializer);
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
enum Escape {
__ = 0,
BB = b'b', TT = b't', NN = b'n', FF = b'f', RR = b'r', QU = b'"', BS = b'\\', LO = LOSSY_REPLACEMENT_CHAR_FIRST_BYTE,
UU = b'u', }
static ESCAPE: [Escape; 256] = create_table(Escape::__);
static ESCAPE_LONE_SURROGATES: [Escape; 256] = create_table(Escape::LO);
const fn create_table(lo: Escape) -> [Escape; 256] {
#[allow(clippy::enum_glob_use, clippy::allow_attributes)]
use Escape::*;
[
UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, lo, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, ]
}
#[inline(always)]
fn write_str(s: &str, table: &[Escape; 256], buffer: &mut CodeBuffer) {
buffer.print_ascii_byte(b'"');
let bytes = s.as_bytes();
let mut start = 0;
let mut iter = bytes.iter().enumerate();
while let Some((index, &byte)) = iter.next() {
let escape = table[byte as usize];
if escape == Escape::__ {
continue;
}
if table == &ESCAPE_LONE_SURROGATES && escape == Escape::LO {
let (_, &next1) = iter.next().unwrap();
let (_, &next2) = iter.next().unwrap();
if [next1, next2] == [LOSSY_REPLACEMENT_CHAR_BYTES[1], LOSSY_REPLACEMENT_CHAR_BYTES[2]]
{
let (_, &hex1) = iter.next().unwrap();
let (_, &hex2) = iter.next().unwrap();
let (_, &hex3) = iter.next().unwrap();
let (_, &hex4) = iter.next().unwrap();
let hex = [hex1, hex2, hex3, hex4];
unsafe { buffer.print_bytes_unchecked(&bytes[start..index]) };
if hex == *b"fffd" {
buffer.print_str("\u{FFFD}");
} else {
buffer.print_str("\\u");
assert_eq!(u32::from_ne_bytes(hex) & 0x8080_8080, 0);
unsafe { buffer.print_bytes_unchecked(&hex) };
}
start = index + 7;
} else {
}
continue;
}
if start < index {
unsafe { buffer.print_bytes_unchecked(&bytes[start..index]) };
}
write_char_escape(escape, byte, buffer);
start = index + 1;
}
if start < bytes.len() {
unsafe { buffer.print_bytes_unchecked(&bytes[start..]) };
}
buffer.print_ascii_byte(b'"');
}
fn write_char_escape(escape: Escape, byte: u8, buffer: &mut CodeBuffer) {
#[expect(clippy::if_not_else)]
if escape != Escape::UU {
buffer.print_ascii_byte(b'\\');
unsafe { buffer.print_byte_unchecked(escape as u8) };
} else {
static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
let bytes = [
b'\\',
b'u',
b'0',
b'0',
HEX_DIGITS[(byte >> 4) as usize],
HEX_DIGITS[(byte & 0xF) as usize],
];
unsafe { buffer.print_bytes_unchecked(&bytes) }
}
}
#[cfg(test)]
mod tests {
use super::super::CompactTSSerializer;
use super::*;
#[test]
fn serialize_string_slice() {
let cases = [
("", r#""""#),
("foobar", r#""foobar""#),
("\n", r#""\n""#),
("\nfoobar", r#""\nfoobar""#),
("foo\nbar", r#""foo\nbar""#),
("foobar\n", r#""foobar\n""#),
(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F",
r#""\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f""#,
),
(
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F",
r#""\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f""#,
),
(
r#"They call me "Bob" but I prefer "Dennis", innit?"#,
r#""They call me \"Bob\" but I prefer \"Dennis\", innit?""#,
),
];
for (input, output) in cases {
let mut serializer = CompactTSSerializer::new();
input.serialize(&mut serializer);
let s = serializer.into_string();
assert_eq!(&s, output);
}
}
#[test]
fn serialize_string() {
let cases = [(String::new(), r#""""#), ("foobar".to_string(), r#""foobar""#)];
for (input, output) in cases {
let mut serializer = CompactTSSerializer::new();
input.to_string().serialize(&mut serializer);
let s = serializer.into_string();
assert_eq!(&s, output);
}
}
#[test]
fn serialize_json_safe_string() {
let cases = [("", r#""""#), ("a", r#""a""#), ("abc", r#""abc""#)];
for (input, output) in cases {
let mut serializer = CompactTSSerializer::new();
JsonSafeString(input).serialize(&mut serializer);
let s = serializer.into_string();
assert_eq!(&s, output);
}
}
#[test]
fn serialize_lone_surrogates_string() {
let cases = [
("\u{FFFD}fffd", "\"\u{FFFD}\""),
("_x_\u{FFFD}fffd_y_\u{FFFD}fffd_z_", "\"_x_\u{FFFD}_y_\u{FFFD}_z_\""),
("\u{FFFD}d834\u{FFFD}d835", r#""\ud834\ud835""#),
("_x_\u{FFFD}d834\u{FFFD}d835", r#""_x_\ud834\ud835""#),
("\u{FFFD}d834\u{FFFD}d835_y_", r#""\ud834\ud835_y_""#),
("_x_\u{FFFD}d834_y_\u{FFFD}d835_z_", r#""_x_\ud834_y_\ud835_z_""#),
];
for (input, output) in cases {
let mut serializer = CompactTSSerializer::new();
LoneSurrogatesString(input).serialize(&mut serializer);
let s = serializer.into_string();
assert_eq!(&s, output);
}
}
}