Skip to main content

boa_engine/
string.rs

1//! This module contains the [`js_string`][crate::js_string] macro and the
2//! [`js_str`][crate::js_str] macro.
3//!
4//! The [`js_string`][crate::js_string] macro is used when you need to create a new [`JsString`],
5//! and the [`js_str`][crate::js_str] macro is used for const conversions of string literals to [`JsStr`].
6
7#[doc(inline)]
8pub use boa_string::*;
9
10/// Utility macro to create a [`JsString`].
11///
12/// # Examples
13///
14/// You can call the macro without arguments to create an empty `JsString`:
15///
16/// ```
17/// use boa_engine::js_string;
18///
19/// let empty_str = js_string!();
20/// assert!(empty_str.is_empty());
21/// ```
22///
23///
24/// You can create a `JsString` from a string literal, which completely skips the runtime
25/// conversion from [`&str`] to <code>[&\[u16\]][slice]</code>:
26///
27/// ```
28/// # use boa_engine::js_string;
29/// let hw = js_string!("Hello, world!");
30/// assert_eq!(&hw, "Hello, world!");
31/// ```
32///
33/// Any `&[u16]` slice is a valid `JsString`, including unpaired surrogates:
34///
35/// ```
36/// # use boa_engine::js_string;
37/// let array = js_string!(&[0xD8AFu16, 0x00A0, 0xD8FF, 0x00F0]);
38/// ```
39///
40/// You can also pass it any number of `&[u16]` as arguments to create a new `JsString` with
41/// the concatenation of every slice:
42///
43/// ```
44/// # use boa_engine::{js_string, js_str, JsStr};
45/// const NAME: JsStr<'_> = js_str!("human! ");
46/// let greeting = js_string!("Hello, ");
47/// let msg = js_string!(&greeting, NAME, js_str!("Nice to meet you!"));
48///
49/// assert_eq!(&msg, "Hello, human! Nice to meet you!");
50/// ```
51#[macro_export]
52#[allow(clippy::module_name_repetitions)]
53macro_rules! js_string {
54    () => {
55        $crate::string::JsString::default()
56    };
57    ($s:literal) => {const {
58        const LITERAL: $crate::string::StaticString = $crate::string::StaticString::new($crate::js_str!($s));
59        $crate::string::JsString::from_static(&LITERAL)
60    }};
61    ($s:expr) => {
62        $crate::string::JsString::from($s)
63    };
64    ( $x:expr, $y:expr ) => {
65        $crate::string::JsString::concat($crate::string::JsStr::from($x), $crate::string::JsStr::from($y))
66    };
67    ( $( $s:expr ),+ ) => {
68        $crate::string::JsString::concat_array(&[ $( $crate::string::JsStr::from($s) ),+ ])
69    };
70}
71
72#[allow(clippy::redundant_clone)]
73#[cfg(test)]
74mod tests {
75    use std::hash::{BuildHasher, BuildHasherDefault, Hash};
76
77    use crate::{JsStr, string::StaticJsStrings};
78
79    use super::JsString;
80    use boa_macros::{js_str, utf16};
81    use rustc_hash::FxHasher;
82
83    fn hash_value<T: Hash>(value: &T) -> u64 {
84        BuildHasherDefault::<FxHasher>::default().hash_one(value)
85    }
86
87    #[test]
88    fn empty() {
89        let s = js_string!();
90        assert_eq!(&s, utf16!(""));
91    }
92
93    #[test]
94    fn refcount() {
95        let x = js_string!("Hello world");
96        assert_eq!(x.refcount(), None);
97
98        let x = js_string!("你好");
99        assert_eq!(x.refcount(), None);
100
101        let x = js_string!("Hello world".to_string());
102        assert_eq!(x.refcount(), Some(1));
103
104        {
105            let y = x.clone();
106            assert_eq!(x.refcount(), Some(2));
107            assert_eq!(y.refcount(), Some(2));
108
109            {
110                let z = y.clone();
111                assert_eq!(x.refcount(), Some(3));
112                assert_eq!(y.refcount(), Some(3));
113                assert_eq!(z.refcount(), Some(3));
114            }
115
116            assert_eq!(x.refcount(), Some(2));
117            assert_eq!(y.refcount(), Some(2));
118        }
119
120        assert_eq!(x.refcount(), Some(1));
121    }
122
123    #[test]
124    fn static_refcount() {
125        let x = js_string!();
126        assert_eq!(x.refcount(), None);
127
128        {
129            let y = x.clone();
130            assert_eq!(x.refcount(), None);
131            assert_eq!(y.refcount(), None);
132        };
133
134        assert_eq!(x.refcount(), None);
135    }
136
137    #[test]
138    fn as_str() {
139        const HELLO: &[u16] = utf16!("Hello");
140        let x = js_string!(HELLO);
141
142        assert_eq!(&x, HELLO);
143    }
144
145    #[test]
146    fn hash() {
147        const HELLOWORLD: JsStr<'_> = js_str!("Hello World!");
148        let x = js_string!(HELLOWORLD);
149
150        assert_eq!(x.as_str(), HELLOWORLD);
151
152        assert!(HELLOWORLD.is_latin1());
153        assert!(x.as_str().is_latin1());
154
155        let s_hash = hash_value(&HELLOWORLD);
156        let x_hash = hash_value(&x);
157
158        assert_eq!(s_hash, x_hash);
159    }
160
161    #[test]
162    fn concat() {
163        const Y: &[u16] = utf16!(", ");
164        const W: &[u16] = utf16!("!");
165
166        let x = js_string!("hello");
167        let z = js_string!("world");
168
169        let xy = js_string!(&x, &JsString::from(Y));
170        assert_eq!(&xy, utf16!("hello, "));
171        assert_eq!(xy.refcount(), Some(1));
172
173        let xyz = js_string!(&xy, &z);
174        assert_eq!(&xyz, utf16!("hello, world"));
175        assert_eq!(xyz.refcount(), Some(1));
176
177        let xyzw = js_string!(&xyz, &JsString::from(W));
178        assert_eq!(&xyzw, utf16!("hello, world!"));
179        assert_eq!(xyzw.refcount(), Some(1));
180    }
181
182    #[test]
183    fn trim_start_non_ascii_to_ascii() {
184        let s = "\u{2029}abc";
185        let x = js_string!(s);
186
187        let y = js_string!(x.trim_start());
188
189        assert_eq!(&y, s.trim_start());
190    }
191
192    #[test]
193    fn conversion_to_known_static_js_string() {
194        const JS_STR_U8: &JsStr<'_> = &js_str!("length");
195        const JS_STR_U16: &JsStr<'_> = &JsStr::utf16(utf16!("length"));
196
197        assert!(JS_STR_U8.is_latin1());
198        assert!(!JS_STR_U16.is_latin1());
199
200        assert_eq!(JS_STR_U8, JS_STR_U8);
201        assert_eq!(JS_STR_U16, JS_STR_U16);
202
203        assert_eq!(JS_STR_U8, JS_STR_U16);
204        assert_eq!(JS_STR_U16, JS_STR_U8);
205
206        assert_eq!(hash_value(JS_STR_U8), hash_value(JS_STR_U16));
207
208        let string = StaticJsStrings::get_string(JS_STR_U8);
209
210        assert!(string.is_some());
211        assert!(string.unwrap().as_str().is_latin1());
212
213        let string = StaticJsStrings::get_string(JS_STR_U16);
214
215        assert!(string.is_some());
216        assert!(string.unwrap().as_str().is_latin1());
217    }
218}