pub(crate) const JS_WHITESPACE: [char; 25] = [
'\u{9}', '\u{a}', '\u{b}', '\u{c}', '\u{d}', '\u{20}', '\u{a0}', '\u{1680}', '\u{2000}', '\u{2001}', '\u{2002}', '\u{2003}', '\u{2004}', '\u{2005}', '\u{2006}', '\u{2007}',
'\u{2008}', '\u{2009}', '\u{200a}', '\u{2028}', '\u{2029}', '\u{202f}', '\u{205f}', '\u{3000}', '\u{feff}', ];
pub(crate) const JS_SPACE_CLASS: &str =
r"\t\n\x0B\f\r \u{a0}\u{1680}\u{2000}-\u{200a}\u{2028}\u{2029}\u{202f}\u{205f}\u{3000}\u{feff}";
pub(crate) fn is_js_whitespace(c: char) -> bool {
JS_WHITESPACE.contains(&c)
}
pub(crate) fn trim(value: &str) -> &str {
value.trim_matches(is_js_whitespace)
}
#[cfg(test)]
mod tests {
use regex::Regex;
use super::*;
#[test]
fn the_char_list_and_the_regex_class_agree() {
let class = Regex::new(&format!("^[{JS_SPACE_CLASS}]$")).expect("the class compiles");
for c in JS_WHITESPACE {
assert!(
class.is_match(&c.to_string()),
"{c:?} missing from the class"
);
}
for code in 0u32..=0xffff {
let Some(c) = char::from_u32(code) else {
continue;
};
if class.is_match(&c.to_string()) {
assert!(JS_WHITESPACE.contains(&c), "{c:?} missing from the list");
}
}
}
#[test]
fn a_byte_order_mark_is_whitespace_here_and_not_in_rust() {
assert!(is_js_whitespace('\u{feff}'));
assert!(!'\u{feff}'.is_whitespace());
assert_eq!(trim("\u{feff}a\u{feff}"), "a");
}
#[test]
fn a_next_line_character_is_whitespace_in_rust_and_not_here() {
assert!(!is_js_whitespace('\u{85}'));
assert!('\u{85}'.is_whitespace());
assert_eq!(trim("\u{85}a"), "\u{85}a");
}
#[test]
fn trimming_matches_the_ordinary_cases() {
assert_eq!(trim(" a b "), "a b");
assert_eq!(trim(""), "");
assert_eq!(trim(" \t\n "), "");
}
}