Jessedbrown's picture
Sync with GitHub main 85faeb1 (part 2)
cfb152f verified
Raw History Blame Contribute Delete
2.45 kB
//! ECMAScript string operations that the upstream lines rely on, reproduced on Rust `&str`.
//!
//! JavaScript strings are UTF-16: `slice` and `charCodeAt` count UTF-16 code units, `trim` strips the
//! ECMAScript WhiteSpace and LineTerminator code points, and `JSON.stringify` of a string returns its
//! quoted literal. Node's `createHash().update(string)` and `appendFileSync` write a string as UTF-8,
//! encoding an unpaired surrogate as U+FFFD.
use std::fmt::Write;
/// `JSON.stringify(s)` for a string value (ECMA-262 QuoteJSONString). It quotes one string and parses
/// nothing; a Rust `&str` holds no unpaired surrogate, so the `\uDXXX` branch of the algorithm never applies.
pub fn json_quote(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\u{8}' => out.push_str("\\b"),
'\u{c}' => out.push_str("\\f"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if u32::from(c) < 0x20 => {
let _ = write!(out, "\\u{:04x}", u32::from(c));
}
c => out.push(c),
}
}
out.push('"');
out
}
/// `String(s).slice(0, n)` as Node writes it to UTF-8: the first `n` UTF-16 code units. When the cut
/// splits a surrogate pair, the kept high surrogate is written as U+FFFD.
pub fn utf16_prefix(s: &str, n: usize) -> String {
let mut out = String::new();
let mut units = 0usize;
for c in s.chars() {
let width = c.len_utf16();
if units + width > n {
if units < n {
out.push('\u{FFFD}');
}
break;
}
out.push(c);
units += width;
}
out
}
/// ECMAScript WhiteSpace (TAB, VT, FF, ZWNBSP, category Zs) and LineTerminator (LF, CR, LS, PS) code
/// points, the set `String.prototype.trim` strips.
pub fn is_js_whitespace(c: char) -> bool {
const SINGLES: [char; 14] = [
'\u{9}', '\u{A}', '\u{B}', '\u{C}', '\u{D}', '\u{20}', '\u{A0}', '\u{1680}', '\u{2028}',
'\u{2029}', '\u{202F}', '\u{205F}', '\u{3000}', '\u{FEFF}',
];
SINGLES.contains(&c) || ('\u{2000}'..='\u{200A}').contains(&c)
}
/// `String(s).trim()`.
pub fn js_trim(s: &str) -> &str {
s.trim_matches(is_js_whitespace)
}