js_string_escape/lib.rs
1//! # js-string-escape — escape a string for a JavaScript string literal
2//!
3//! Escape the characters that aren't valid inside a single- or double-quoted JavaScript
4//! string literal — `"`, `'`, `\`, and the line terminators `\n`, `\r`, `U+2028`, and
5//! `U+2029`. Everything else (including tabs, other control characters, and non-ASCII) is
6//! left as-is. A faithful Rust port of the
7//! [`js-string-escape`](https://www.npmjs.com/package/js-string-escape) npm package.
8//!
9//! ```
10//! use js_string_escape::js_string_escape;
11//!
12//! assert_eq!(js_string_escape(r#"a"b'c\d"#), r#"a\"b\'c\\d"#);
13//! assert_eq!(js_string_escape("line1\nline2"), "line1\\nline2");
14//! assert_eq!(js_string_escape("café\t"), "café\t"); // non-ASCII and tab pass through
15//! ```
16//!
17//! This is the minimal escaping needed to embed arbitrary text into a JS string literal; for
18//! richer options (ASCII-only output, JSON mode, …) see
19//! [`jsesc`](https://crates.io/crates/jsesc).
20//!
21//! **Zero dependencies** and `#![no_std]`.
22
23#![no_std]
24#![forbid(unsafe_code)]
25#![doc(html_root_url = "https://docs.rs/js-string-escape/0.1.0")]
26
27extern crate alloc;
28
29use alloc::string::String;
30
31// Compile-test the README's examples as part of `cargo test`.
32#[cfg(doctest)]
33#[doc = include_str!("../README.md")]
34struct ReadmeDoctests;
35
36/// Escape `string` so it can be embedded in a JavaScript string literal.
37///
38/// Only `"`, `'`, `\`, `\n`, `\r`, `U+2028`, and `U+2029` are escaped; all other characters
39/// pass through unchanged.
40///
41/// ```
42/// # use js_string_escape::js_string_escape;
43/// assert_eq!(js_string_escape("say \"hi\""), "say \\\"hi\\\"");
44/// ```
45#[must_use]
46pub fn js_string_escape(string: &str) -> String {
47 let mut out = String::with_capacity(string.len());
48 for character in string.chars() {
49 match character {
50 '"' | '\'' | '\\' => {
51 out.push('\\');
52 out.push(character);
53 }
54 '\n' => out.push_str("\\n"),
55 '\r' => out.push_str("\\r"),
56 '\u{2028}' => out.push_str("\\u2028"),
57 '\u{2029}' => out.push_str("\\u2029"),
58 _ => out.push(character),
59 }
60 }
61 out
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn escapes_quotes_and_backslash() {
70 assert_eq!(js_string_escape("a\"b"), "a\\\"b");
71 assert_eq!(js_string_escape("a'b"), "a\\'b");
72 assert_eq!(js_string_escape("a\\b"), "a\\\\b");
73 }
74
75 #[test]
76 fn escapes_line_terminators() {
77 assert_eq!(js_string_escape("a\nb"), "a\\nb");
78 assert_eq!(js_string_escape("a\rb"), "a\\rb");
79 assert_eq!(js_string_escape("a\u{2028}b"), "a\\u2028b");
80 assert_eq!(js_string_escape("a\u{2029}b"), "a\\u2029b");
81 }
82
83 #[test]
84 fn leaves_other_characters_untouched() {
85 // Tab, backspace, form feed, NUL, backtick, and non-ASCII all pass through.
86 assert_eq!(js_string_escape("a\tb"), "a\tb");
87 assert_eq!(js_string_escape("a\u{0}b"), "a\u{0}b");
88 assert_eq!(js_string_escape("x`y"), "x`y");
89 assert_eq!(js_string_escape("café 😀"), "café 😀");
90 assert_eq!(js_string_escape(""), "");
91 assert_eq!(js_string_escape("plain"), "plain");
92 }
93}