diff --git a/.changeset/chatty-hotels-sort.md b/.changeset/chatty-hotels-sort.md new file mode 100644 index 000000000000..d728f5d86c75 --- /dev/null +++ b/.changeset/chatty-hotels-sort.md @@ -0,0 +1,6 @@ +--- +swc_atoms: major +hstr: major +--- + +fix(es/ast): Fix unicode unpaired surrogates handling diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 928cdc6bada3..1ad628a866dc 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -291,7 +291,7 @@ jobs: key: swc-exec-cache-${{ matrix.settings.crate }}-${{ hashFiles('**/Cargo.lock') }} - name: Run cargo test - if: matrix.settings.crate != 'swc_plugin_backend_tests' && matrix.settings.crate != 'swc_ecma_parser' && matrix.settings.crate != 'swc_ecma_minifier' && matrix.settings.crate != 'swc_core' && matrix.settings.crate != 'swc_ecma_quote' && matrix.settings.crate != 'swc_cli' && matrix.settings.crate != 'binding_core_wasm' + if: matrix.settings.crate != 'swc_plugin_backend_tests' && matrix.settings.crate != 'swc_ecma_parser' && matrix.settings.crate != 'swc_ecma_minifier' && matrix.settings.crate != 'swc_core' && matrix.settings.crate != 'swc_ecma_quote' && matrix.settings.crate != 'swc_cli' && matrix.settings.crate != 'binding_core_wasm' && matrix.settings.crate != 'hstr' run: | cargo test -p ${{ matrix.settings.crate }} @@ -317,6 +317,11 @@ jobs: # export CARGO_TARGET_DIR=$(pwd)/target cargo test -p swc_plugin_backend_tests --release + - name: Run cargo test (hstr) + if: matrix.settings.crate == 'hstr' + run: | + cargo test -p hstr --features serde + - name: Run cargo test (swc_ecma_minifier) if: matrix.settings.crate == 'swc_ecma_minifier' run: | diff --git a/Cargo.lock b/Cargo.lock index 68c75d8edfdc..bde5673ee2f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2282,6 +2282,7 @@ dependencies = [ "rkyv", "rustc-hash 2.1.1", "serde", + "serde_json", "smartstring", "smol_str", "string_cache", @@ -5618,7 +5619,6 @@ dependencies = [ name = "swc_ecma_lexer" version = "24.0.1" dependencies = [ - "arrayvec", "bitflags 2.6.0", "codspeed-criterion-compat", "either", diff --git a/crates/hstr/Cargo.toml b/crates/hstr/Cargo.toml index fb713291ba2f..717b888d8c4f 100644 --- a/crates/hstr/Cargo.toml +++ b/crates/hstr/Cargo.toml @@ -34,6 +34,7 @@ kstring = { workspace = true } num_cpus = { workspace = true } par-iter = { workspace = true } rand = { workspace = true } +serde_json = { workspace = true } smartstring = { workspace = true } smol_str = { workspace = true } string_cache = { workspace = true } diff --git a/crates/hstr/src/lib.rs b/crates/hstr/src/lib.rs index c0b7642062bb..5311b586a12f 100644 --- a/crates/hstr/src/lib.rs +++ b/crates/hstr/src/lib.rs @@ -3,6 +3,7 @@ use core::str; use std::{ + borrow::Borrow, fmt::{Debug, Display}, hash::Hash, mem::{self, forget, transmute, ManuallyDrop}, @@ -104,6 +105,7 @@ pub use wtf8_atom::Wtf8Atom; /// - Atoms created via the `atom!` macro or `String::into` are stored in the /// global atom store. By default, these atoms are never deallocated. To clean /// up unused atoms, call [global_atom_store_gc]. +#[repr(transparent)] pub struct Atom { // If this Atom is a dynamic one, this is *const Entry unsafe_data: TaggedValue, @@ -369,6 +371,19 @@ impl PartialEq for str { } } +impl Borrow for Atom { + #[inline(always)] + fn borrow(&self) -> &Wtf8Atom { + // SAFETY: + // 1. Wtf8Atom is #[repr(transparent)] over TaggedValue + // 2. Atom is #[repr(transparent)] over TaggedValue + // 3. hstr::Atom and hstr::Wtf8Atom share the same TaggedValue + const _: () = assert!(std::mem::size_of::() == std::mem::size_of::()); + const _: () = assert!(std::mem::align_of::() == std::mem::align_of::()); + unsafe { transmute::<&Atom, &Wtf8Atom>(self) } + } +} + /// NOT A PUBLIC API #[cfg(feature = "rkyv")] impl rkyv::Archive for Atom { diff --git a/crates/hstr/src/wtf8/mod.rs b/crates/hstr/src/wtf8/mod.rs index 0bba1bf0fc6e..214ee07e2dd8 100644 --- a/crates/hstr/src/wtf8/mod.rs +++ b/crates/hstr/src/wtf8/mod.rs @@ -35,6 +35,7 @@ use core::{ slice, str, str::FromStr, }; +use std::ops::Add; mod not_quite_std; @@ -68,7 +69,7 @@ impl CodePoint { /// /// Only use when `value` is known to be less than or equal to 0x10FFFF. #[inline] - pub unsafe fn from_u32_unchecked(value: u32) -> CodePoint { + pub const unsafe fn from_u32_unchecked(value: u32) -> CodePoint { CodePoint { value } } @@ -76,7 +77,7 @@ impl CodePoint { /// /// Return `None` if `value` is above 0x10FFFF. #[inline] - pub fn from_u32(value: u32) -> Option { + pub const fn from_u32(value: u32) -> Option { match value { 0..=0x10ffff => Some(CodePoint { value }), _ => None, @@ -87,7 +88,7 @@ impl CodePoint { /// /// Since all Unicode scalar values are code points, this always succeds. #[inline] - pub fn from_char(value: char) -> CodePoint { + pub const fn from_char(value: char) -> CodePoint { CodePoint { value: value as u32, } @@ -118,6 +119,18 @@ impl CodePoint { pub fn to_char_lossy(&self) -> char { self.to_char().unwrap_or('\u{FFFD}') } + + /// Return `true` if the code point is in the ASCII range. + #[inline] + pub fn is_ascii(&self) -> bool { + self.value <= 0x7f + } +} + +impl PartialEq for CodePoint { + fn eq(&self, other: &char) -> bool { + self.value == *other as u32 + } } /// An owned, growable string of well-formed WTF-8 data. @@ -165,6 +178,23 @@ impl FromStr for Wtf8Buf { } } +impl fmt::Write for Wtf8Buf { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + self.push_str(s); + Ok(()) + } +} + +impl Add<&Wtf8> for Wtf8Buf { + type Output = Wtf8Buf; + + fn add(self, rhs: &Wtf8) -> Self::Output { + let mut result = self; + result.push_wtf8(rhs); + result + } +} + impl Wtf8Buf { /// Create an new, empty WTF-8 string. #[inline] @@ -313,6 +343,12 @@ impl Wtf8Buf { self.bytes.truncate(new_len) } + /// Clear the WTF-8 vector, removing all contents. + #[inline] + pub fn clear(&mut self) { + self.bytes.clear(); + } + /// Consume the WTF-8 string and try to convert it to UTF-8. /// /// This does not copy the data. @@ -345,6 +381,26 @@ impl Wtf8Buf { } } } + + /// Create a [Wtf8Buf] from a WTF-8 encoded byte vector. + /// + /// # Safety + /// + /// The caller must ensure that `bytes` is a well-formed WTF-8 byte + /// sequence. + /// + /// This means that: + /// - All bytes must form valid UTF-8 sequences OR valid surrogate code + /// point encodings + /// - Surrogate code points may appear unpaired and be encoded separately, + /// but if they are paired, it should be encoded as a single 4-byte UTF-8 + /// sequence. For example, the byte sequence `[0xED, 0xA0, 0x80, 0xED, + /// 0xB0, 0x80]` is not valid WTF-8 because WTF-8 forbids encoding a + /// surrogate pair as two separate 3-byte sequences. + #[inline] + pub unsafe fn from_bytes_unchecked(bytes: Vec) -> Self { + Self { bytes } + } } /// Create a new WTF-8 string from an iterator of code points. @@ -474,6 +530,12 @@ impl Wtf8 { self.bytes.is_empty() } + /// Return `true` if the string contains only ASCII characters. + #[inline] + pub const fn is_ascii(&self) -> bool { + self.bytes.is_ascii() + } + /// Return a slice of the given string for the byte range [`begin`..`end`). /// /// # Failure @@ -547,6 +609,34 @@ impl Wtf8 { } } + /// Returns `true` if this WTF-8 string contains the given character. + #[inline] + pub fn contains_char(&self, ch: char) -> bool { + let target = CodePoint::from_char(ch); + self.contains(target) + } + + /// Returns `true` if this WTF-8 string contains the given code point. + #[inline] + pub fn contains(&self, code_point: CodePoint) -> bool { + self.code_points().any(|cp| cp == code_point) + } + + /// Returns `true` if this WTF-8 string starts with the given UTF-8 string. + #[inline] + pub fn starts_with(&self, pattern: &str) -> bool { + if pattern.len() > self.len() { + return false; + } + + let pattern_wtf8 = self.slice_to(pattern.len()); + if let Some(pattern_str) = pattern_wtf8.as_str() { + pattern_str == pattern + } else { + false + } + } + /// Try to convert the string to UTF-8 and return a `&str` slice. /// /// Return `None` if the string contains surrogates. @@ -614,6 +704,46 @@ impl Wtf8 { } } + /// Returns the uppercase equivalent of this wtf8 slice, as a new [Wtf8Buf]. + #[inline] + pub fn to_uppercase(&self) -> Wtf8Buf { + let mut result = Wtf8Buf::with_capacity(self.len()); + for cp in self.code_points() { + if let Some(ch) = cp.to_char() { + for upper_ch in ch.to_uppercase() { + result.push_char(upper_ch); + } + } else { + // Surrogates are known to be in the code point range. + let code_point = unsafe { CodePoint::from_u32_unchecked(cp.to_u32()) }; + // Skip the WTF-8 concatenation check, + // surrogate pairs are already decoded by utf16_items + not_quite_std::push_code_point(&mut result, code_point) + } + } + result + } + + /// Returns the lowercase equivalent of this wtf8 slice, as a new [Wtf8Buf]. + #[inline] + pub fn to_lowercase(&self) -> Wtf8Buf { + let mut result = Wtf8Buf::with_capacity(self.len()); + for cp in self.code_points() { + if let Some(ch) = cp.to_char() { + for lower_ch in ch.to_lowercase() { + result.push_char(lower_ch); + } + } else { + // Surrogates are known to be in the code point range. + let code_point = unsafe { CodePoint::from_u32_unchecked(cp.to_u32()) }; + // Skip the WTF-8 concatenation check, + // surrogate pairs are already decoded by utf16_items + not_quite_std::push_code_point(&mut result, code_point) + } + } + result + } + /// Create a WTF-8 from a WTF-8 encoded byte slice. /// /// # Safety @@ -770,6 +900,24 @@ impl PartialEq for &Wtf8 { } } +impl PartialEq for &Wtf8 { + fn eq(&self, other: &str) -> bool { + match self.as_str() { + Some(s) => s == other, + None => false, + } + } +} + +impl PartialEq<&str> for &Wtf8 { + fn eq(&self, other: &&str) -> bool { + match self.as_str() { + Some(s) => s == *other, + None => false, + } + } +} + impl hash::Hash for CodePoint { #[inline] fn hash(&self, state: &mut H) { @@ -824,6 +972,13 @@ impl<'a> From<&'a str> for &'a Wtf8 { } } +impl<'a> From for Cow<'a, Wtf8> { + #[inline] + fn from(s: Wtf8Buf) -> Cow<'a, Wtf8> { + Cow::Owned(s) + } +} + #[cfg(test)] mod tests { use alloc::{format, vec}; diff --git a/crates/hstr/src/wtf8_atom.rs b/crates/hstr/src/wtf8_atom.rs index 7e5f75c4510d..548792291e83 100644 --- a/crates/hstr/src/wtf8_atom.rs +++ b/crates/hstr/src/wtf8_atom.rs @@ -1,7 +1,7 @@ use std::{ fmt::Debug, hash::Hash, - mem::{forget, transmute}, + mem::{forget, transmute, ManuallyDrop}, ops::Deref, }; @@ -10,14 +10,15 @@ use debug_unreachable::debug_unreachable; use crate::{ macros::{get_hash, impl_from_alias, partial_eq}, tagged_value::TaggedValue, - wtf8::Wtf8, - DYNAMIC_TAG, INLINE_TAG, LEN_MASK, LEN_OFFSET, TAG_MASK, + wtf8::{CodePoint, Wtf8, Wtf8Buf}, + Atom, DYNAMIC_TAG, INLINE_TAG, LEN_MASK, LEN_OFFSET, TAG_MASK, }; /// A WTF-8 encoded atom. This is like [Atom], but can contain unpaired /// surrogates. /// /// [Atom]: crate::Atom +#[repr(transparent)] pub struct Wtf8Atom { pub(crate) unsafe_data: TaggedValue, } @@ -31,6 +32,21 @@ impl Wtf8Atom { Self::from(s) } + /// Try to convert this to a UTF-8 [Atom]. + /// + /// Returns [Atom] if the string is valid UTF-8, otherwise returns + /// the original [Wtf8Atom]. + pub fn try_into_atom(self) -> Result { + if self.as_str().is_some() { + let atom = ManuallyDrop::new(self); + Ok(Atom { + unsafe_data: atom.unsafe_data, + }) + } else { + Err(self) + } + } + #[inline(always)] fn tag(&self) -> u8 { self.unsafe_data.tag() & TAG_MASK @@ -59,7 +75,7 @@ unsafe impl Sync for Wtf8Atom {} impl Debug for Wtf8Atom { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self.to_string_lossy(), f) + Debug::fmt(&**self, f) } } @@ -69,7 +85,37 @@ impl serde::ser::Serialize for Wtf8Atom { where S: serde::ser::Serializer, { - serializer.serialize_bytes(self.as_bytes()) + fn convert_wtf8_to_raw(s: &Wtf8) -> String { + let mut result = String::new(); + let mut iter = s.code_points().peekable(); + + while let Some(code_point) = iter.next() { + if let Some(c) = code_point.to_char() { + // Escape literal '\u' sequences to avoid ambiguity with surrogate encoding. + // Without this escaping, we couldn't distinguish between: + // - JavaScript's "\uD800" (actual unpaired surrogate) + // - JavaScript's "\\uD800" (literal text '\uD800') + // + // By escaping literal '\u' to '\\u', we ensure: + // - Unpaired surrogates serialize as '\uXXXX' + // - Literal '\u' text serializes as '\\uXXXX' + if c == '\\' && iter.peek().map(|cp| cp.to_u32()) == Some('u' as u32) { + iter.next(); // skip 'u' + result.push_str("\\\\u"); + } else { + result.push(c) + } + } else { + // Unpaired surrogates can't be represented in valid UTF-8, + // so encode them as '\uXXXX' for JavaScript compatibility + result.push_str(format!("\\u{:04X}", code_point.to_u32()).as_str()); + } + } + + result + } + + serializer.serialize_str(&convert_wtf8_to_raw(self)) } } @@ -79,7 +125,79 @@ impl<'de> serde::de::Deserialize<'de> for Wtf8Atom { where D: serde::Deserializer<'de>, { - String::deserialize(deserializer).map(Self::new) + fn convert_wtf8_string_to_wtf8(s: String) -> Wtf8Buf { + let mut iter = s.chars().peekable(); + let mut result = Wtf8Buf::with_capacity(s.len()); + + // This function reverses the encoding done in serialize. + // It handles two cases: + // 1. '\uXXXX' - Decode as an unpaired surrogate code point + // 2. '\\uXXXX' - Treat as literal text '\uXXXX' + while let Some(c) = iter.next() { + if c == '\\' { + if iter.peek() == Some(&'u') { + // Found '\u' - might be a surrogate encoding + let _ = iter.next(); // skip 'u' + + // Try to read 4 hex digits + let d1 = iter.next(); + let d2 = iter.next(); + let d3 = iter.next(); + let d4 = iter.next(); + + if d1.is_some() && d2.is_some() && d3.is_some() && d4.is_some() { + let hex = format!( + "{}{}{}{}", + d1.unwrap(), + d2.unwrap(), + d3.unwrap(), + d4.unwrap() + ); + if let Ok(code_point) = u16::from_str_radix(&hex, 16) { + result.push(unsafe { + CodePoint::from_u32_unchecked(code_point as u32) + }); + continue; + } + } + + result.push_char('\\'); + result.push_char('u'); + + macro_rules! push_if_some { + ($expr:expr) => { + if let Some(c) = $expr { + result.push_char(c); + } + }; + } + + push_if_some!(d1); + push_if_some!(d2); + push_if_some!(d3); + push_if_some!(d4); + } else if iter.peek() == Some(&'\\') { + // Found '\\' - this is an escaped backslash + // '\\u' should become literal '\u' text + let _ = iter.next(); // skip the second '\' + if iter.peek() == Some(&'u') { + let _ = iter.next(); // skip 'u' + result.push_char('\\'); + result.push_char('u'); + } else { + result.push_str("\\\\"); + } + } else { + result.push_char(c); + } + } else { + result.push_char(c); + } + } + result + } + + String::deserialize(deserializer).map(|v| convert_wtf8_string_to_wtf8(v).into()) } } @@ -163,6 +281,20 @@ impl PartialEq for Wtf8 { } } +impl PartialEq for Wtf8Atom { + #[inline] + fn eq(&self, other: &str) -> bool { + matches!(self.as_str(), Some(s) if s == other) + } +} + +impl PartialEq<&str> for Wtf8Atom { + #[inline] + fn eq(&self, other: &&str) -> bool { + matches!(self.as_str(), Some(s) if s == *other) + } +} + impl Wtf8Atom { pub(super) fn get_hash(&self) -> u64 { get_hash!(self) @@ -199,3 +331,224 @@ impl Wtf8Atom { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::wtf8::{CodePoint, Wtf8Buf}; + + #[test] + fn test_serialize_normal_utf8() { + let atom = Wtf8Atom::new("Hello, world!"); + let serialized = serde_json::to_string(&atom).unwrap(); + assert_eq!(serialized, "\"Hello, world!\""); + } + + #[test] + fn test_deserialize_normal_utf8() { + let json = "\"Hello, world!\""; + let atom: Wtf8Atom = serde_json::from_str(json).unwrap(); + assert_eq!(atom.as_str(), Some("Hello, world!")); + } + + #[test] + fn test_serialize_unpaired_high_surrogate() { + // Create a WTF-8 string with an unpaired high surrogate (U+D800) + let mut wtf8 = Wtf8Buf::new(); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xd800) }); + let atom = Wtf8Atom::from(wtf8); + + let serialized = serde_json::to_string(&atom).unwrap(); + // The serialized output will have double escaping due to serde_json + assert_eq!(serialized, "\"\\\\uD800\""); + } + + #[test] + fn test_serialize_unpaired_low_surrogate() { + // Create a WTF-8 string with an unpaired low surrogate (U+DC00) + let mut wtf8 = Wtf8Buf::new(); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xdc00) }); + let atom = Wtf8Atom::from(wtf8); + + let serialized = serde_json::to_string(&atom).unwrap(); + // The serialized output will have double escaping due to serde_json + assert_eq!(serialized, "\"\\\\uDC00\""); + } + + #[test] + fn test_serialize_multiple_surrogates() { + // Create a WTF-8 string with multiple unpaired surrogates + let mut wtf8 = Wtf8Buf::new(); + wtf8.push_str("Hello "); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xd800) }); + wtf8.push_str(" World "); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xdc00) }); + let atom = Wtf8Atom::from(wtf8); + + let serialized = serde_json::to_string(&atom).unwrap(); + // The serialized output will have double escaping due to serde_json + assert_eq!(serialized, "\"Hello \\\\uD800 World \\\\uDC00\""); + } + + #[test] + fn test_serialize_literal_backslash_u() { + // Test that literal "\u" in the string gets escaped properly + let atom = Wtf8Atom::new("\\u0041"); + let serialized = serde_json::to_string(&atom).unwrap(); + // serde_json escapes the backslash, resulting in 4 backslashes + assert_eq!(serialized, "\"\\\\\\\\u0041\""); + } + + #[test] + fn test_deserialize_escaped_backslash_u() { + // Test deserializing the escaped format for unpaired surrogates + let json = "\"\\\\uD800\""; + let atom: Wtf8Atom = serde_json::from_str(json).unwrap(); + // This should be parsed as an unpaired surrogate + assert_eq!(atom.as_str(), None); + assert_eq!(atom.to_string_lossy(), "\u{FFFD}"); + } + + #[test] + fn test_deserialize_unpaired_surrogates() { + let json = "\"\\\\uD800\""; // Use escaped format that matches serialization + let atom: Wtf8Atom = serde_json::from_str(json).unwrap(); + // Should contain an unpaired surrogate, so as_str() returns None + assert_eq!(atom.as_str(), None); + // But to_string_lossy should work + assert_eq!(atom.to_string_lossy(), "\u{FFFD}"); + } + + #[test] + fn test_round_trip_normal_string() { + let original = Wtf8Atom::new("Hello, 世界! 🌍"); + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: Wtf8Atom = serde_json::from_str(&serialized).unwrap(); + assert_eq!(original.as_str(), deserialized.as_str()); + } + + #[test] + fn test_round_trip_unpaired_surrogates() { + // Create a string with unpaired surrogates + let mut wtf8 = Wtf8Buf::new(); + wtf8.push_str("Before "); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xd800) }); + wtf8.push_str(" Middle "); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xdc00) }); + wtf8.push_str(" After"); + let original = Wtf8Atom::from(wtf8); + + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: Wtf8Atom = serde_json::from_str(&serialized).unwrap(); + + // Both should be equal when compared as WTF-8 + assert_eq!(original, deserialized); + + // Both should produce the same lossy string + assert_eq!(original.to_string_lossy(), deserialized.to_string_lossy()); + } + + #[test] + fn test_round_trip_mixed_content() { + // Create a complex string with normal text, emojis, and unpaired surrogates + let mut wtf8 = Wtf8Buf::new(); + wtf8.push_str("Hello 世界 🌍 "); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xd83d) }); // Unpaired high + wtf8.push_str(" test "); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xdca9) }); // Unpaired low + let original = Wtf8Atom::from(wtf8); + + let serialized = serde_json::to_string(&original).unwrap(); + let deserialized: Wtf8Atom = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(original, deserialized); + } + + #[test] + fn test_empty_string() { + let atom = Wtf8Atom::new(""); + let serialized = serde_json::to_string(&atom).unwrap(); + assert_eq!(serialized, "\"\""); + + let deserialized: Wtf8Atom = serde_json::from_str("\"\"").unwrap(); + assert_eq!(deserialized.as_str(), Some("")); + } + + #[test] + fn test_special_characters() { + let test_cases = vec![ + ("\"", "\"\\\"\""), + ("\n\r\t", "\"\\n\\r\\t\""), // serde_json escapes control characters + ("\\", "\"\\\\\""), + ("/", "\"/\""), + ]; + + for (input, expected) in test_cases { + let atom = Wtf8Atom::new(input); + let serialized = serde_json::to_string(&atom).unwrap(); + assert_eq!(serialized, expected, "Failed for input: {input:?}"); + + let deserialized: Wtf8Atom = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized.as_str(), Some(input)); + } + } + + #[test] + fn test_consecutive_surrogates_not_paired() { + // Test that consecutive surrogates that don't form a valid pair + // are handled correctly + let mut wtf8 = Wtf8Buf::new(); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xd800) }); // High surrogate + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xd800) }); // Another high surrogate + let atom = Wtf8Atom::from(wtf8); + + let serialized = serde_json::to_string(&atom).unwrap(); + // The serialized output will have double escaping due to serde_json + assert_eq!(serialized, "\"\\\\uD800\\\\uD800\""); + + let deserialized: Wtf8Atom = serde_json::from_str(&serialized).unwrap(); + assert_eq!(atom, deserialized); + } + + #[test] + fn test_deserialize_incomplete_escape() { + // Test handling of incomplete escape sequences from our custom format + let json = "\"\\\\\\\\u123\""; // Escaped backslash + incomplete sequence + let atom: Wtf8Atom = serde_json::from_str(json).unwrap(); + // JSON decodes \\\\u123 to \\u123, then our deserializer sees \u123 and treats + // it as literal + assert_eq!(atom.as_str(), Some("\\u123")); + } + + #[test] + fn test_deserialize_invalid_hex() { + // Test handling of invalid hex in escape sequences from our custom format + let json = "\"\\\\\\\\uGGGG\""; // Escaped backslash + invalid hex + let atom: Wtf8Atom = serde_json::from_str(json).unwrap(); + // JSON decodes \\\\uGGGG to \\uGGGG, then our deserializer sees \uGGGG and + // treats it as literal + assert_eq!(atom.as_str(), Some("\\uGGGG")); + } + + #[test] + fn test_try_into_atom_valid_utf8() { + let wtf8_atom = Wtf8Atom::new("Valid UTF-8 string"); + let result = wtf8_atom.try_into_atom(); + assert!(result.is_ok()); + assert_eq!(result.unwrap().as_str(), "Valid UTF-8 string"); + } + + #[test] + fn test_try_into_atom_invalid_utf8() { + // Create an atom with unpaired surrogates + let mut wtf8 = Wtf8Buf::new(); + wtf8.push(unsafe { CodePoint::from_u32_unchecked(0xd800) }); + let wtf8_atom = Wtf8Atom::from(wtf8); + + let result = wtf8_atom.try_into_atom(); + assert!(result.is_err()); + // Should return the original Wtf8Atom + let err_atom = result.unwrap_err(); + assert_eq!(err_atom.to_string_lossy(), "\u{FFFD}"); + } +} diff --git a/crates/swc/tests/exec/issues-4xxx/4120/exec.js b/crates/swc/tests/exec/issues-4xxx/4120/exec.js index 19ebfc38e7d8..8d8ded41aa9b 100644 --- a/crates/swc/tests/exec/issues-4xxx/4120/exec.js +++ b/crates/swc/tests/exec/issues-4xxx/4120/exec.js @@ -1,7 +1,7 @@ const a = - "\u0591-\u06ef\u06fa-\u08ff\u200f\ud802-\ud803\ud83a-\ud83b\ufb1d-\ufdff\ufe70-\ufefc"; + "\u0591-\u06ef\u06fa-\u08ff\u200f\ud802-\ud803\ud83a-\ud83b\ufb1d-\ufdff\ufe70-\ufefc"; const b = - "A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02b8\u0300-\u0590\u0900-\u1fff\u200e\u2c00-\ud801\ud804-\ud839\ud83c-\udbff\uf900-\ufb1c\ufe00-\ufe6f\ufefd-\uffff"; + "A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02b8\u0300-\u0590\u0900-\u1fff\u200e\u2c00-\ud801\ud804-\ud839\ud83c-\udbff\uf900-\ufb1c\ufe00-\ufe6f\ufefd-\uffff"; console.log(a); console.log(b); diff --git a/crates/swc/tests/fixture/issues-4xxx/4120/1/output/index.js b/crates/swc/tests/fixture/issues-4xxx/4120/1/output/index.js index d450bbfd1a3d..2131ba1c3fff 100644 --- a/crates/swc/tests/fixture/issues-4xxx/4120/1/output/index.js +++ b/crates/swc/tests/fixture/issues-4xxx/4120/1/output/index.js @@ -1 +1 @@ -export default{a:"֑-ۯۺ-ࣿ‏\ud802-\ud803\ud83a-\ud83bיִ-﷿ﹰ-ﻼ",b:"A-Za-z\xc0-\xd6\xd8-\xf6\xf8-ʸ̀-֐ऀ-῿‎Ⰰ-\ud801\ud804-\ud839\ud83c-\udbff豈-﬜︀-﹯﻽-￿"}; +export default{a:"֑-ۯۺ-ࣿ‏\uD802-\uD803\uD83A-\uD83Bיִ-﷿ﹰ-ﻼ",b:"A-Za-z\xc0-\xd6\xd8-\xf6\xf8-ʸ̀-֐ऀ-῿‎Ⰰ-\uD801\uD804-\uD839\uD83C-\uDBFF豈-﬜︀-﹯﻽-￿"}; diff --git a/crates/swc/tests/fixture/issues-7xxx/7678/output/1.js b/crates/swc/tests/fixture/issues-7xxx/7678/output/1.js index 4581a3820115..90f649fa88aa 100644 --- a/crates/swc/tests/fixture/issues-7xxx/7678/output/1.js +++ b/crates/swc/tests/fixture/issues-7xxx/7678/output/1.js @@ -1 +1 @@ -let str="\uD83D\uDC68\\u200D\uD83D\uDE80";let obj={"\uD83D\uDC68\\u200D\uD83D\uDE80":"wrong"}; +let str="\\uD83D\\uDC68\\u200D\\uD83D\\uDE80";let obj={"\\uD83D\\uDC68\\u200D\\uD83D\\uDE80":"wrong"}; diff --git a/crates/swc/tests/tsc-references/unicodeExtendedEscapesInStrings10_ES5.1.normal.js b/crates/swc/tests/tsc-references/unicodeExtendedEscapesInStrings10_ES5.1.normal.js index 9842e56d882e..6a0202d28ebb 100644 --- a/crates/swc/tests/tsc-references/unicodeExtendedEscapesInStrings10_ES5.1.normal.js +++ b/crates/swc/tests/tsc-references/unicodeExtendedEscapesInStrings10_ES5.1.normal.js @@ -3,4 +3,4 @@ // 2. Let cu1 be floor((cp – 65536) / 1024) + 0xD800. // Although we should just get back a single code point value of 0xD800, // this is a useful edge-case test. -var x = "\u{D800}"; +var x = "\uD800"; diff --git a/crates/swc/tests/tsc-references/unicodeExtendedEscapesInStrings11_ES5.1.normal.js b/crates/swc/tests/tsc-references/unicodeExtendedEscapesInStrings11_ES5.1.normal.js index 2e2d5e00cd6d..14737469861d 100644 --- a/crates/swc/tests/tsc-references/unicodeExtendedEscapesInStrings11_ES5.1.normal.js +++ b/crates/swc/tests/tsc-references/unicodeExtendedEscapesInStrings11_ES5.1.normal.js @@ -3,4 +3,4 @@ // 2. Let cu2 be ((cp – 65536) modulo 1024) + 0xDC00. // Although we should just get back a single code point value of 0xDC00, // this is a useful edge-case test. -var x = "\u{DC00}"; +var x = "\uDC00"; diff --git a/crates/swc/tests/tsc-references/unicodeExtendedEscapesInTemplates10_ES5.1.normal.js b/crates/swc/tests/tsc-references/unicodeExtendedEscapesInTemplates10_ES5.1.normal.js index 1e3e0fa950b4..719311a2a0c4 100644 --- a/crates/swc/tests/tsc-references/unicodeExtendedEscapesInTemplates10_ES5.1.normal.js +++ b/crates/swc/tests/tsc-references/unicodeExtendedEscapesInTemplates10_ES5.1.normal.js @@ -3,4 +3,4 @@ // 2. Let cu1 be floor((cp – 65536) / 1024) + 0xD800. // Although we should just get back a single code point value of 0xD800, // this is a useful edge-case test. -var x = "\u{D800}"; +var x = "\uD800"; diff --git a/crates/swc/tests/tsc-references/unicodeExtendedEscapesInTemplates11_ES5.1.normal.js b/crates/swc/tests/tsc-references/unicodeExtendedEscapesInTemplates11_ES5.1.normal.js index db96092c3f5f..aec679c3522b 100644 --- a/crates/swc/tests/tsc-references/unicodeExtendedEscapesInTemplates11_ES5.1.normal.js +++ b/crates/swc/tests/tsc-references/unicodeExtendedEscapesInTemplates11_ES5.1.normal.js @@ -3,4 +3,4 @@ // 2. Let cu2 be ((cp – 65536) modulo 1024) + 0xDC00. // Although we should just get back a single code point value of 0xDC00, // this is a useful edge-case test. -var x = "\u{DC00}"; +var x = "\uDC00"; diff --git a/crates/swc/tests/vercel/full/utf8-1/output/index.js b/crates/swc/tests/vercel/full/utf8-1/output/index.js index adfd5a878e91..0afac7863537 100644 --- a/crates/swc/tests/vercel/full/utf8-1/output/index.js +++ b/crates/swc/tests/vercel/full/utf8-1/output/index.js @@ -5,7 +5,7 @@ import r from './on-demand-entries-client'; import { addMessageListener as n, connectHMR as c } from './error-overlay/websocket'; var o = JSON.parse(document.getElementById('__NEXT_DATA__').textContent); window.__NEXT_DATA__ = o; -var s = o.assetPrefix, i = o.page, _ = null, u = __webpack_hash__, d = (s = s || '') + (s.endsWith('/') ? '' : '/') + '_next/static/webpack/'; +var s = o.assetPrefix, i = o.page, _ = null, d = __webpack_hash__, u = (s = s || '') + (s.endsWith('/') ? '' : '/') + '_next/static/webpack/'; n(function(a) { if ('\uD83D\uDC93' !== a.data) try { var r = JSON.parse(a.data); @@ -28,7 +28,7 @@ n(function(a) { 5 ]), [ 4, - fetch('undefined' != typeof __webpack_runtime_id__ ? "".concat(d).concat(u, ".").concat(__webpack_runtime_id__, ".hot-update.json") : "".concat(d).concat(u, ".hot-update.json")) + fetch('undefined' != typeof __webpack_runtime_id__ ? "".concat(u).concat(d, ".").concat(__webpack_runtime_id__, ".hot-update.json") : "".concat(u).concat(d, ".hot-update.json")) ]; case 2: return [ @@ -38,7 +38,7 @@ n(function(a) { case 3: return e = t.sent(), a = '/' === i ? 'index' : i, (Array.isArray(e.c) ? e.c : Object.keys(e.c)).some(function(e) { return -1 !== e.indexOf("pages".concat(a.startsWith('/') ? a : "/".concat(a))) || -1 !== e.indexOf("pages".concat(a.startsWith('/') ? a : "/".concat(a)).replace(/\//g, '\\')); - }) ? document.location.reload(!0) : u = _, [ + }) ? document.location.reload(!0) : d = _, [ 3, 5 ]; diff --git a/crates/swc_atoms/Cargo.toml b/crates/swc_atoms/Cargo.toml index e66a468c564e..1d21b6cefffc 100644 --- a/crates/swc_atoms/Cargo.toml +++ b/crates/swc_atoms/Cargo.toml @@ -26,4 +26,4 @@ rkyv = { workspace = true, optional = true } serde = { workspace = true } shrink-to-fit = { workspace = true, optional = true } -hstr = { version = "2.1.0", path = "../hstr" } +hstr = { version = "2.1.0", path = "../hstr", features = ["serde"] } diff --git a/crates/swc_atoms/src/lib.rs b/crates/swc_atoms/src/lib.rs index 07c584f10c8a..af07ca0d03cb 100644 --- a/crates/swc_atoms/src/lib.rs +++ b/crates/swc_atoms/src/lib.rs @@ -10,20 +10,24 @@ pub extern crate hstr; pub extern crate once_cell; use std::{ - borrow::Cow, + borrow::{Borrow, Cow}, cell::UnsafeCell, fmt::{self, Display, Formatter}, hash::Hash, + mem::transmute, ops::Deref, rc::Rc, }; +pub use hstr::wtf8; use once_cell::sync::Lazy; use serde::Serializer; +use wtf8::Wtf8; -pub use crate::fast::UnsafeAtom; +pub use crate::{fast::UnsafeAtom, wtf8_atom::Wtf8Atom}; mod fast; +mod wtf8_atom; /// Clone-on-write string. /// @@ -31,7 +35,7 @@ mod fast; /// See [tendril] for more details. #[derive(Clone, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))] -#[cfg_attr(feature = "rkyv-impl", repr(C))] +#[repr(transparent)] pub struct Atom(hstr::Atom); #[cfg(feature = "arbitrary")] @@ -75,6 +79,23 @@ impl Atom { pub fn as_str(&self) -> &str { &self.0 } + + /// Converts a WTF-8 encoded [Wtf8Atom] to a regular UTF-8 [Atom] without + /// validation. + /// + /// # Safety + /// + /// The caller must ensure that the WTF-8 atom contains only valid UTF-8 + /// data (no unpaired surrogates). This function performs no validation + /// and will create an invalid `Atom` if the input contains unpaired + /// surrogates. + /// + /// This is a zero-cost conversion that preserves all internal optimizations + /// (inline storage, precomputed hashes, etc.) since both types have + /// identical internal representation. + pub unsafe fn from_wtf8_unchecked(s: Wtf8Atom) -> Self { + Atom(unsafe { hstr::Atom::from_wtf8_unchecked(s.0) }) + } } impl Deref for Atom { @@ -113,6 +134,13 @@ impl From for Atom { } } +impl From for hstr::Wtf8Atom { + #[inline(always)] + fn from(s: Atom) -> Self { + hstr::Wtf8Atom::from(&*s) + } +} + impl PartialEq for Atom { fn eq(&self, other: &str) -> bool { &**self == other @@ -161,6 +189,20 @@ impl Ord for Atom { } } +impl Borrow for Atom { + fn borrow(&self) -> &Wtf8Atom { + // SAFETY: + // 1. Wtf8Atom is #[repr(transparent)] over hstr::Wtf8Atom, so as hstr::Wtf8Atom + // over TaggedValue + // 2. Atom is #[repr(transparent)] over hstr::Atom, so as hstr::Atom over + // TaggedValue + // 3. hstr::Atom and hstr::Wtf8Atom share the same TaggedValue + const _: () = assert!(std::mem::size_of::() == std::mem::size_of::()); + const _: () = assert!(std::mem::align_of::() == std::mem::align_of::()); + unsafe { transmute::<&Atom, &Wtf8Atom>(self) } + } +} + impl serde::ser::Serialize for Atom { fn serialize(&self, serializer: S) -> Result where @@ -253,6 +295,11 @@ impl AtomStore { pub fn atom<'a>(&mut self, s: impl Into>) -> Atom { Atom(self.0.atom(s)) } + + #[inline] + pub fn wtf8_atom<'a>(&mut self, s: impl Into>) -> Wtf8Atom { + Wtf8Atom(self.0.wtf8_atom(s)) + } } /// A fast internally mutable cell for [AtomStore]. @@ -270,6 +317,17 @@ impl AtomStoreCell { // only to this block. unsafe { (*self.0.get()).atom(s) } } + + #[inline] + pub fn wtf8_atom<'a>(&self, s: impl Into>) -> Wtf8Atom { + // evaluate the into before borrowing (see #8362) + let s: Cow<'a, Wtf8> = s.into(); + // SAFETY: We can skip the borrow check of RefCell because + // this API enforces a safe contract. It is slightly faster + // to use an UnsafeCell. Note the borrow here is short lived + // only to this block. + unsafe { (*self.0.get()).wtf8_atom(s) } + } } /// noop diff --git a/crates/swc_atoms/src/wtf8_atom.rs b/crates/swc_atoms/src/wtf8_atom.rs new file mode 100644 index 000000000000..7f96698daff1 --- /dev/null +++ b/crates/swc_atoms/src/wtf8_atom.rs @@ -0,0 +1,237 @@ +use std::{ + borrow::Cow, + fmt::{self, Formatter}, + ops::Deref, +}; + +use hstr::wtf8::{Wtf8, Wtf8Buf}; +use serde::Serializer; + +use crate::Atom; + +/// Clone-on-write WTF-8 string. +/// +/// +/// See [tendril] for more details. +#[derive(Clone, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))] +#[repr(transparent)] +pub struct Wtf8Atom(pub(super) hstr::Wtf8Atom); + +#[cfg(feature = "arbitrary")] +#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))] +impl<'a> arbitrary::Arbitrary<'a> for Wtf8Atom { + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + let sym = u.arbitrary::>()?; + if sym.is_empty() { + return Err(arbitrary::Error::NotEnoughData); + } + Ok(Self(hstr::Wtf8Atom::from(unsafe { + Wtf8Buf::from_bytes_unchecked(sym) + }))) + } +} + +fn _asserts() { + // let _static_assert_size_eq = std::mem::transmute::; + + fn _assert_send() {} + fn _assert_sync() {} + + _assert_sync::(); + _assert_send::(); +} + +impl Wtf8Atom { + /// Creates a new [Wtf8Atom] from a string. + #[inline(always)] + pub fn new(s: S) -> Self + where + hstr::Wtf8Atom: From, + { + Wtf8Atom(hstr::Wtf8Atom::from(s)) + } + + pub fn as_wtf8(&self) -> &Wtf8 { + &self.0 + } + + pub fn as_atom(&self) -> Option<&Atom> { + if self.as_str().is_some() { + Some(unsafe { &*(self as *const Wtf8Atom as *const Atom) }) + } else { + None + } + } + + /// Returns the UTF-8 [`Atom`] representation, borrowing when possible. + pub fn to_atom_lossy(&self) -> Cow<'_, Atom> { + if let Some(atom) = self.as_atom() { + return Cow::Borrowed(atom); + } + Cow::Owned(Atom::new(self.to_string_lossy())) + } + + /// Try to convert this to a UTF-8 [Atom]. + /// + /// Returns [Atom] if the string is valid UTF-8, otherwise returns + /// the original [Wtf8Atom]. + pub fn try_into_atom(self) -> Result { + self.0.try_into_atom().map(Atom).map_err(Wtf8Atom) + } +} + +impl Deref for Wtf8Atom { + type Target = Wtf8; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Debug for Wtf8Atom { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl PartialOrd for Wtf8Atom { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Wtf8Atom { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_wtf8().cmp(other.as_wtf8()) + } +} + +impl PartialEq for Wtf8Atom +where + hstr::Wtf8Atom: PartialEq, + T: ?Sized, +{ + fn eq(&self, other: &T) -> bool { + self.0.eq(other) + } +} + +impl From for Wtf8Atom +where + hstr::Wtf8Atom: From, +{ + fn from(s: T) -> Self { + Wtf8Atom::new(s) + } +} + +impl From<&Wtf8Atom> for Wtf8Buf { + fn from(s: &Wtf8Atom) -> Self { + // SAFETY: `Wtf8Atom` is guaranteed to be valid WTF-8 byte sequence. + unsafe { Wtf8Buf::from_bytes_unchecked(s.as_bytes().to_vec()) } + } +} + +impl serde::ser::Serialize for Wtf8Atom { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de> serde::de::Deserialize<'de> for Wtf8Atom { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + hstr::Wtf8Atom::deserialize(deserializer).map(Wtf8Atom) + } +} + +/// NOT A PUBLIC API +#[cfg(feature = "rkyv-impl")] +impl rkyv::Archive for Wtf8Atom { + type Archived = rkyv::vec::ArchivedVec; + type Resolver = rkyv::vec::VecResolver; + + #[allow(clippy::unit_arg)] + fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place) { + rkyv::vec::ArchivedVec::::resolve_from_slice(self.as_bytes(), resolver, out) + } +} + +/// NOT A PUBLIC API +#[cfg(feature = "rkyv-impl")] +impl rkyv::Serialize + for Wtf8Atom +where + ::Error: rancor::Source, +{ + fn serialize(&self, serializer: &mut S) -> Result { + rkyv::vec::ArchivedVec::::serialize_from_slice(self.as_bytes(), serializer) + } +} + +/// NOT A PUBLIC API +#[cfg(feature = "rkyv-impl")] +impl rkyv::Deserialize for rkyv::vec::ArchivedVec +where + D: ?Sized + rancor::Fallible, + ::Error: rancor::Source, +{ + fn deserialize( + &self, + deserializer: &mut D, + ) -> Result::Error> { + let s: Vec = self.deserialize(deserializer)?; + + Ok(Wtf8Atom::new( + // SAFETY: `ArchivedVec` is guaranteed to be serialized with `Wtf8Atom` byte + // sequence. `Wtf8Atom` byte sequence is identical to `Wtf8` byte sequence. + unsafe { Wtf8::from_bytes_unchecked(&s) }, + )) + } +} + +/// noop +#[cfg(feature = "shrink-to-fit")] +impl shrink_to_fit::ShrinkToFit for Wtf8Atom { + #[inline(always)] + fn shrink_to_fit(&mut self) {} +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn to_atom_lossy_returns_borrowed_for_utf8() { + let atom = Atom::from("swc"); + let wtf = Wtf8Atom::from(atom.clone()); + + match wtf.to_atom_lossy() { + Cow::Borrowed(borrowed) => assert_eq!(borrowed, &atom), + Cow::Owned(_) => panic!("expected a borrowed Atom for valid UTF-8 input"), + } + } + + #[test] + fn to_atom_lossy_returns_owned_for_invalid_utf8() { + let invalid_bytes = vec![0xed, 0xa0, 0x80]; + let invalid = unsafe { Wtf8Buf::from_bytes_unchecked(invalid_bytes.clone()) }; + let wtf = Wtf8Atom::new(invalid); + + let lossy = wtf.to_string_lossy(); + + match wtf.to_atom_lossy() { + Cow::Borrowed(_) => panic!("expected an owned Atom for invalid UTF-8 input"), + Cow::Owned(atom) => { + assert_eq!(atom.as_ref(), lossy); + } + } + } +} diff --git a/crates/swc_bundler/src/bundler/chunk/cjs.rs b/crates/swc_bundler/src/bundler/chunk/cjs.rs index 8b7db90b3886..428812e6263f 100644 --- a/crates/swc_bundler/src/bundler/chunk/cjs.rs +++ b/crates/swc_bundler/src/bundler/chunk/cjs.rs @@ -203,7 +203,8 @@ where // TODO: Check for global mark if i.sym == *"require" && node.args.len() == 1 { if let Expr::Lit(Lit::Str(module_name)) = &*node.args[0].expr { - if self.bundler.is_external(&module_name.value) { + let module_atom = module_name.value.to_atom_lossy(); + if self.bundler.is_external(module_atom.as_ref()) { return; } let load = CallExpr { diff --git a/crates/swc_bundler/src/bundler/chunk/merge.rs b/crates/swc_bundler/src/bundler/chunk/merge.rs index 4263aafef3ee..cc5769b34bec 100644 --- a/crates/swc_bundler/src/bundler/chunk/merge.rs +++ b/crates/swc_bundler/src/bundler/chunk/merge.rs @@ -456,7 +456,8 @@ where entry.retain_mut(|_, item| { match item { ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export)) => { - if self.config.external_modules.contains(&export.src.value) { + let src_atom = export.src.value.to_atom_lossy().into_owned(); + if self.config.external_modules.contains(&src_atom) { return true; } @@ -465,7 +466,8 @@ where ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => { if let Some(src) = &export.src { - if self.config.external_modules.contains(&src.value) { + let src_atom = src.value.to_atom_lossy().into_owned(); + if self.config.external_modules.contains(&src_atom) { return true; } } @@ -478,7 +480,8 @@ where } ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => { - if self.config.external_modules.contains(&import.src.value) { + let src_atom = import.src.value.to_atom_lossy().into_owned(); + if self.config.external_modules.contains(&src_atom) { return true; } @@ -597,8 +600,9 @@ where for item in items { match item { ModuleItem::ModuleDecl(ModuleDecl::Import(mut import)) => { + let src_atom = import.src.value.to_atom_lossy().into_owned(); // Preserve imports from node.js builtin modules. - if self.config.external_modules.contains(&import.src.value) { + if self.config.external_modules.contains(&src_atom) { new.push(import.into()); continue; } @@ -1276,7 +1280,8 @@ where for stmt in stmts { if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = &stmt { - if self.config.external_modules.contains(&import.src.value) { + let src_atom = import.src.value.to_atom_lossy().into_owned(); + if self.config.external_modules.contains(&src_atom) { new.push(stmt); continue; } diff --git a/crates/swc_bundler/src/bundler/export.rs b/crates/swc_bundler/src/bundler/export.rs index 8d224dc7360c..3f0bf55928ed 100644 --- a/crates/swc_bundler/src/bundler/export.rs +++ b/crates/swc_bundler/src/bundler/export.rs @@ -201,11 +201,10 @@ where } ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => { - let ctxt = named - .src - .as_ref() - .map(|s| &s.value) - .and_then(|src| self.ctxt_for(src)); + let ctxt = named.src.as_ref().and_then(|s| { + let src_atom = s.value.to_atom_lossy(); + self.ctxt_for(src_atom.as_ref()) + }); let mut need_wrapping = false; let v = self @@ -292,12 +291,16 @@ where } if need_wrapping { - self.mark_as_wrapping_required(&named.src.as_ref().unwrap().value); + let wrap_atom = named.src.as_ref().unwrap().value.to_atom_lossy(); + self.mark_as_wrapping_required(wrap_atom.as_ref()); } } ModuleItem::ModuleDecl(ModuleDecl::ExportAll(all)) => { - let ctxt = self.ctxt_for(&all.src.value); + let ctxt = { + let src_atom = all.src.value.to_atom_lossy(); + self.ctxt_for(src_atom.as_ref()) + }; if let Some((_, export_ctxt)) = ctxt { ExportMetadata { export_ctxt: Some(export_ctxt), diff --git a/crates/swc_bundler/src/bundler/finalize.rs b/crates/swc_bundler/src/bundler/finalize.rs index 59aef6b95fac..fd6ce8e3cb78 100644 --- a/crates/swc_bundler/src/bundler/finalize.rs +++ b/crates/swc_bundler/src/bundler/finalize.rs @@ -411,10 +411,10 @@ where noop_fold_type!(); fn fold_import_decl(&mut self, import: ImportDecl) -> ImportDecl { - let resolved = match self - .resolver - .resolve(&FileName::Real(self.base.clone()), &import.src.value) - { + let resolved = match self.resolver.resolve( + &FileName::Real(self.base.clone()), + &import.src.value.to_string_lossy(), + ) { Ok(v) => match v.filename { FileName::Real(v) => v, _ => panic!("rename_bundles called with non-path module"), diff --git a/crates/swc_bundler/src/bundler/import/mod.rs b/crates/swc_bundler/src/bundler/import/mod.rs index 8c79601ab0d3..978303c639f7 100644 --- a/crates/swc_bundler/src/bundler/import/mod.rs +++ b/crates/swc_bundler/src/bundler/import/mod.rs @@ -203,7 +203,7 @@ where }) .map(|import| import.src.value.clone()) { - self.info.forced_ns.insert(src); + self.info.forced_ns.insert(src.to_atom_lossy().into_owned()); } } @@ -222,12 +222,13 @@ where Callee::Expr(callee) if self.bundler.config.require && callee.is_ident_ref_to("require") => { - if self.bundler.is_external(&src.value) { + let src_atom = src.value.to_atom_lossy(); + if self.bundler.is_external(src_atom.as_ref()) { return; } if let Expr::Ident(i) = &mut **callee { - self.mark_as_cjs(&src.value); - if let Some((_, export_ctxt)) = self.ctxt_for(&src.value) { + self.mark_as_cjs(src_atom.as_ref()); + if let Some((_, export_ctxt)) = self.ctxt_for(src_atom.as_ref()) { i.ctxt = export_ctxt; } } @@ -302,7 +303,8 @@ where None => return, }; - let mark = self.ctxt_for(&import.src.value); + let src_atom = import.src.value.to_atom_lossy(); + let mark = self.ctxt_for(src_atom.as_ref()); let exported_ctxt = match mark { None => return, Some(ctxts) => ctxts.1, @@ -416,13 +418,14 @@ where } fn visit_mut_import_decl(&mut self, import: &mut ImportDecl) { + let src_atom = import.src.value.to_atom_lossy().into_owned(); // Ignore if it's a core module. - if self.bundler.is_external(&import.src.value) { + if self.bundler.is_external(&src_atom) { return; } if !self.deglob_phase { - if let Some((_, export_ctxt)) = self.ctxt_for(&import.src.value) { + if let Some((_, export_ctxt)) = self.ctxt_for(&src_atom) { // Firstly we attach proper syntax contexts. ExportMetadata { export_ctxt: Some(export_ctxt), @@ -515,7 +518,9 @@ where } // We failed to found property usage. - self.info.forced_ns.insert(import.src.value.clone()); + self.info + .forced_ns + .insert(import.src.value.clone().to_atom_lossy().into_owned()); } } } @@ -551,12 +556,9 @@ where if self.deglob_phase { let mut wrapping_required = Vec::new(); for import in self.info.imports.iter_mut() { + let src_atom = import.src.value.to_atom_lossy().into_owned(); let use_ns = self.info.forced_ns.contains(&import.src.value) - || self - .bundler - .config - .external_modules - .contains(&import.src.value); + || self.bundler.config.external_modules.contains(&src_atom); if use_ns { wrapping_required.push(import.src.value.clone()); @@ -569,7 +571,8 @@ where } for id in wrapping_required { - self.mark_as_wrapping_required(&id); + let id_atom = id.to_atom_lossy(); + self.mark_as_wrapping_required(id_atom.as_ref()); } } } @@ -622,14 +625,20 @@ where _ => return, }; // Ignore core modules. - if self.bundler.config.external_modules.contains(&src.value) { + let src_atom = src.value.to_atom_lossy(); + if self + .bundler + .config + .external_modules + .contains(src_atom.as_ref()) + { return; } - self.mark_as_cjs(&src.value); + self.mark_as_cjs(src_atom.as_ref()); if let Expr::Ident(i) = &mut **callee { - if let Some((_, export_ctxt)) = self.ctxt_for(&src.value) { + if let Some((_, export_ctxt)) = self.ctxt_for(src_atom.as_ref()) { i.ctxt = export_ctxt; } } diff --git a/crates/swc_bundler/src/bundler/load.rs b/crates/swc_bundler/src/bundler/load.rs index 4d5bc47b26c1..40290272381a 100644 --- a/crates/swc_bundler/src/bundler/load.rs +++ b/crates/swc_bundler/src/bundler/load.rs @@ -237,7 +237,7 @@ where self.run(|| { let info = match src { Some(src) => { - let name = self.resolve(base, &src.value)?; + let name = self.resolve(base, &src.value.to_string_lossy())?; let (id, local_mark, export_mark) = self.scope.module_id_gen.gen(&name); Some((id, local_mark, export_mark, name, src)) @@ -314,7 +314,7 @@ where .map(|(decl, dynamic, unconditional)| -> Result<_, Error> { self.run(|| { // - let file_name = self.resolve(base, &decl.src.value)?; + let file_name = self.resolve(base, &decl.src.value.to_string_lossy())?; let (id, local_mark, export_mark) = self.scope.module_id_gen.gen(&file_name); diff --git a/crates/swc_bundler/src/util.rs b/crates/swc_bundler/src/util.rs index 127ffc80c4b7..63b136f1d9a1 100644 --- a/crates/swc_bundler/src/util.rs +++ b/crates/swc_bundler/src/util.rs @@ -261,8 +261,10 @@ impl ExportMetadata { } } else if *sym == "__swc_bundler__export_ctxt__" { if let Expr::Lit(Lit::Str(Str { value, .. })) = &**value { - if let Ok(v) = value.parse() { - data.export_ctxt = Some(SyntaxContext::from_u32(v)); + if let Some(value) = value.as_str() { + if let Ok(v) = value.parse() { + data.export_ctxt = Some(SyntaxContext::from_u32(v)); + } } } } diff --git a/crates/swc_common/src/eq.rs b/crates/swc_common/src/eq.rs index 8d569d720e58..db101e8ecd1e 100644 --- a/crates/swc_common/src/eq.rs +++ b/crates/swc_common/src/eq.rs @@ -24,6 +24,13 @@ impl EqIgnoreSpan for swc_atoms::Atom { } } +impl EqIgnoreSpan for swc_atoms::Wtf8Atom { + #[inline] + fn eq_ignore_span(&self, r: &Self) -> bool { + self == r + } +} + impl EqIgnoreSpan for [T] where T: EqIgnoreSpan, diff --git a/crates/swc_core/tests/fixture/stub_napi/Cargo.lock b/crates/swc_core/tests/fixture/stub_napi/Cargo.lock index f87363a94f7e..40c4bf90beab 100644 --- a/crates/swc_core/tests/fixture/stub_napi/Cargo.lock +++ b/crates/swc_core/tests/fixture/stub_napi/Cargo.lock @@ -109,7 +109,7 @@ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "ast_node" -version = "3.0.3" +version = "3.0.4" dependencies = [ "quote", "swc_macros_common", @@ -1457,7 +1457,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hstr" -version = "2.0.1" +version = "2.1.0" dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", @@ -3279,7 +3279,7 @@ dependencies = [ [[package]] name = "swc" -version = "40.0.0" +version = "42.0.0" dependencies = [ "anyhow", "base64", @@ -3355,7 +3355,7 @@ dependencies = [ [[package]] name = "swc_bundler" -version = "31.0.0" +version = "32.0.0" dependencies = [ "anyhow", "crc", @@ -3384,7 +3384,7 @@ dependencies = [ [[package]] name = "swc_common" -version = "14.0.3" +version = "14.0.4" dependencies = [ "anyhow", "ast_node", @@ -3413,7 +3413,7 @@ dependencies = [ [[package]] name = "swc_compiler_base" -version = "35.0.0" +version = "36.0.0" dependencies = [ "anyhow", "base64", @@ -3439,7 +3439,7 @@ dependencies = [ [[package]] name = "swc_config" -version = "3.1.1" +version = "3.1.2" dependencies = [ "anyhow", "bytes-str", @@ -3468,7 +3468,7 @@ dependencies = [ [[package]] name = "swc_core" -version = "42.0.0" +version = "44.0.2" dependencies = [ "swc", "swc_allocator", @@ -3540,7 +3540,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_bugfixes" -version = "28.0.0" +version = "30.0.0" dependencies = [ "rustc-hash 2.1.1", "swc_atoms", @@ -3566,7 +3566,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2015" -version = "28.0.0" +version = "30.0.0" dependencies = [ "arrayvec", "indexmap", @@ -3591,7 +3591,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2016" -version = "26.0.0" +version = "27.0.0" dependencies = [ "swc_common", "swc_ecma_ast", @@ -3605,7 +3605,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2017" -version = "26.0.0" +version = "27.0.0" dependencies = [ "serde", "swc_common", @@ -3619,7 +3619,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2018" -version = "26.0.0" +version = "27.0.0" dependencies = [ "serde", "swc_common", @@ -3635,7 +3635,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2019" -version = "26.0.0" +version = "27.0.0" dependencies = [ "swc_common", "swc_ecma_ast", @@ -3648,7 +3648,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2020" -version = "27.0.0" +version = "28.0.0" dependencies = [ "serde", "swc_common", @@ -3663,7 +3663,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2021" -version = "26.0.0" +version = "27.0.0" dependencies = [ "swc_ecma_ast", "swc_ecma_compiler", @@ -3674,7 +3674,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2022" -version = "27.0.0" +version = "28.0.0" dependencies = [ "rustc-hash 2.1.1", "swc_atoms", @@ -3705,7 +3705,7 @@ dependencies = [ [[package]] name = "swc_ecma_compiler" -version = "4.0.0" +version = "5.0.0" dependencies = [ "bitflags 2.9.1", "rustc-hash 2.1.1", @@ -3732,9 +3732,8 @@ dependencies = [ [[package]] name = "swc_ecma_lexer" -version = "23.0.1" +version = "23.0.2" dependencies = [ - "arrayvec", "bitflags 2.9.1", "either", "num-bigint", @@ -3792,7 +3791,7 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "32.0.4" +version = "33.0.0" dependencies = [ "arrayvec", "bitflags 2.9.1", @@ -3826,7 +3825,7 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "24.0.1" +version = "24.0.3" dependencies = [ "either", "num-bigint", @@ -3840,7 +3839,7 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "34.0.0" +version = "36.0.0" dependencies = [ "anyhow", "foldhash", @@ -3863,7 +3862,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "33.0.0" +version = "35.0.0" dependencies = [ "par-core", "swc_common", @@ -3880,7 +3879,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "26.0.1" +version = "27.0.0" dependencies = [ "better_scoped_tls", "indexmap", @@ -3901,7 +3900,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "26.0.0" +version = "27.0.0" dependencies = [ "swc_common", "swc_ecma_ast", @@ -3912,7 +3911,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "29.0.0" +version = "31.0.0" dependencies = [ "indexmap", "par-core", @@ -3949,7 +3948,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "29.0.0" +version = "30.0.0" dependencies = [ "Inflector", "anyhow", @@ -3975,7 +3974,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "28.0.0" +version = "29.0.0" dependencies = [ "bytes-str", "dashmap 5.5.3", @@ -3997,7 +3996,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "26.0.0" +version = "27.0.0" dependencies = [ "either", "rustc-hash 2.1.1", @@ -4013,7 +4012,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "29.0.0" +version = "30.0.2" dependencies = [ "base64", "bytes-str", @@ -4035,7 +4034,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "29.0.0" +version = "30.0.1" dependencies = [ "bytes-str", "rustc-hash 2.1.1", @@ -4144,7 +4143,7 @@ dependencies = [ [[package]] name = "swc_node_bundler" -version = "41.0.0" +version = "43.0.0" dependencies = [ "anyhow", "rustc-hash 2.1.1", @@ -4176,7 +4175,7 @@ dependencies = [ [[package]] name = "swc_nodejs_common" -version = "1.0.2" +version = "1.0.3" dependencies = [ "anyhow", "napi", diff --git a/crates/swc_core/tests/fixture/stub_wasm/Cargo.lock b/crates/swc_core/tests/fixture/stub_wasm/Cargo.lock index f76d04a193c6..73e8577a6f62 100644 --- a/crates/swc_core/tests/fixture/stub_wasm/Cargo.lock +++ b/crates/swc_core/tests/fixture/stub_wasm/Cargo.lock @@ -76,7 +76,7 @@ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "ast_node" -version = "3.0.3" +version = "3.0.4" dependencies = [ "quote", "swc_macros_common", @@ -125,7 +125,7 @@ dependencies = [ [[package]] name = "binding_macros" -version = "40.0.0" +version = "42.0.0" dependencies = [ "anyhow", "console_error_panic_hook", @@ -599,7 +599,7 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hstr" -version = "2.0.1" +version = "2.1.0" dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", @@ -1494,7 +1494,7 @@ dependencies = [ [[package]] name = "swc" -version = "40.0.0" +version = "42.0.0" dependencies = [ "anyhow", "base64", @@ -1561,7 +1561,7 @@ dependencies = [ [[package]] name = "swc_common" -version = "14.0.3" +version = "14.0.4" dependencies = [ "anyhow", "ast_node", @@ -1587,7 +1587,7 @@ dependencies = [ [[package]] name = "swc_compiler_base" -version = "35.0.0" +version = "36.0.0" dependencies = [ "anyhow", "base64", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "swc_config" -version = "3.1.1" +version = "3.1.2" dependencies = [ "anyhow", "bytes-str", @@ -1640,7 +1640,7 @@ dependencies = [ [[package]] name = "swc_core" -version = "42.0.0" +version = "44.0.2" dependencies = [ "binding_macros", "swc", @@ -1705,7 +1705,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_bugfixes" -version = "28.0.0" +version = "30.0.0" dependencies = [ "rustc-hash", "swc_atoms", @@ -1731,7 +1731,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2015" -version = "28.0.0" +version = "30.0.0" dependencies = [ "arrayvec", "indexmap", @@ -1756,7 +1756,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2016" -version = "26.0.0" +version = "27.0.0" dependencies = [ "swc_common", "swc_ecma_ast", @@ -1770,7 +1770,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2017" -version = "26.0.0" +version = "27.0.0" dependencies = [ "serde", "swc_common", @@ -1784,7 +1784,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2018" -version = "26.0.0" +version = "27.0.0" dependencies = [ "serde", "swc_common", @@ -1800,7 +1800,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2019" -version = "26.0.0" +version = "27.0.0" dependencies = [ "swc_common", "swc_ecma_ast", @@ -1813,7 +1813,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2020" -version = "27.0.0" +version = "28.0.0" dependencies = [ "serde", "swc_common", @@ -1828,7 +1828,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2021" -version = "26.0.0" +version = "27.0.0" dependencies = [ "swc_ecma_ast", "swc_ecma_compiler", @@ -1839,7 +1839,7 @@ dependencies = [ [[package]] name = "swc_ecma_compat_es2022" -version = "27.0.0" +version = "28.0.0" dependencies = [ "rustc-hash", "swc_atoms", @@ -1870,7 +1870,7 @@ dependencies = [ [[package]] name = "swc_ecma_compiler" -version = "4.0.0" +version = "5.0.0" dependencies = [ "bitflags", "rustc-hash", @@ -1897,9 +1897,8 @@ dependencies = [ [[package]] name = "swc_ecma_lexer" -version = "23.0.1" +version = "23.0.2" dependencies = [ - "arrayvec", "bitflags", "either", "num-bigint", @@ -1957,7 +1956,7 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "32.0.4" +version = "33.0.0" dependencies = [ "arrayvec", "bitflags", @@ -1991,7 +1990,7 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "24.0.1" +version = "24.0.3" dependencies = [ "either", "num-bigint", @@ -2005,7 +2004,7 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "34.0.0" +version = "36.0.0" dependencies = [ "anyhow", "foldhash", @@ -2028,7 +2027,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "33.0.0" +version = "35.0.0" dependencies = [ "par-core", "swc_common", @@ -2045,7 +2044,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "26.0.1" +version = "27.0.0" dependencies = [ "better_scoped_tls", "indexmap", @@ -2065,7 +2064,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "26.0.0" +version = "27.0.0" dependencies = [ "swc_common", "swc_ecma_ast", @@ -2076,7 +2075,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "29.0.0" +version = "31.0.0" dependencies = [ "indexmap", "par-core", @@ -2113,7 +2112,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "29.0.0" +version = "30.0.0" dependencies = [ "Inflector", "anyhow", @@ -2139,7 +2138,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "28.0.0" +version = "29.0.0" dependencies = [ "bytes-str", "dashmap", @@ -2161,7 +2160,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "26.0.0" +version = "27.0.0" dependencies = [ "either", "rustc-hash", @@ -2177,7 +2176,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "29.0.0" +version = "30.0.2" dependencies = [ "base64", "bytes-str", @@ -2199,7 +2198,7 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "29.0.0" +version = "30.0.1" dependencies = [ "bytes-str", "rustc-hash", diff --git a/crates/swc_ecma_ast/src/expr.rs b/crates/swc_ecma_ast/src/expr.rs index e87bcdc188a4..b4f75283af39 100644 --- a/crates/swc_ecma_ast/src/expr.rs +++ b/crates/swc_ecma_ast/src/expr.rs @@ -3,7 +3,7 @@ use std::mem::transmute; use is_macro::Is; use string_enum::StringEnum; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::{ ast_node, util::take::Take, BytePos, EqIgnoreSpan, Span, Spanned, SyntaxContext, DUMMY_SP, }; @@ -550,7 +550,10 @@ impl ObjectLit { Prop::KeyValue(kv) => { let key = match &kv.key { PropName::Ident(i) => i.clone(), - PropName::Str(s) => IdentName::new(s.value.clone(), s.span), + PropName::Str(s) => { + let name = s.value.as_str()?; + IdentName::new(name.to_string().into(), s.span) + } _ => return None, }; @@ -1195,7 +1198,7 @@ pub struct TplElement { /// /// If you are going to use codegen right after creating a [TplElement], you /// don't have to worry about this value. - pub cooked: Option, + pub cooked: Option, /// You may need to perform. `.replace("\r\n", "\n").replace('\r', "\n")` on /// this value. @@ -1218,7 +1221,7 @@ impl Take for TplElement { impl<'a> arbitrary::Arbitrary<'a> for TplElement { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let span = u.arbitrary()?; - let cooked = Some(u.arbitrary::()?.into()); + let cooked = Some(u.arbitrary::()?.into()); let raw = u.arbitrary::()?.into(); Ok(Self { diff --git a/crates/swc_ecma_ast/src/lit.rs b/crates/swc_ecma_ast/src/lit.rs index b2eb23046fbf..4f0d2f27e03a 100644 --- a/crates/swc_ecma_ast/src/lit.rs +++ b/crates/swc_ecma_ast/src/lit.rs @@ -6,7 +6,7 @@ use std::{ use is_macro::Is; use num_bigint::BigInt as BigIntValue; -use swc_atoms::{atom, Atom}; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::{ast_node, util::take::Take, EqIgnoreSpan, Span, DUMMY_SP}; use crate::jsx::JSXText; @@ -55,6 +55,7 @@ bridge_expr_from!(Lit, JSXText); bridge_lit_from!(Str, &'_ str); bridge_lit_from!(Str, Atom); +bridge_lit_from!(Str, Wtf8Atom); bridge_lit_from!(Str, Cow<'_, str>); bridge_lit_from!(Str, String); bridge_lit_from!(Bool, bool); @@ -185,7 +186,7 @@ impl From for BigInt { pub struct Str { pub span: Span, - pub value: Atom, + pub value: Wtf8Atom, /// Use `None` value only for transformations to avoid recalculate escaped /// characters in strings @@ -196,7 +197,7 @@ impl Take for Str { fn dummy() -> Self { Str { span: DUMMY_SP, - value: atom!(""), + value: Wtf8Atom::default(), raw: None, } } @@ -207,7 +208,7 @@ impl Take for Str { impl<'a> arbitrary::Arbitrary<'a> for Str { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let span = u.arbitrary()?; - let value = u.arbitrary::()?.into(); + let value = u.arbitrary::()?.into(); let raw = Some(u.arbitrary::()?.into()); Ok(Self { span, value, raw }) @@ -278,6 +279,17 @@ impl EqIgnoreSpan for Str { impl From for Str { #[inline] fn from(value: Atom) -> Self { + Str { + span: DUMMY_SP, + value: value.into(), + raw: None, + } + } +} + +impl From for Str { + #[inline] + fn from(value: Wtf8Atom) -> Self { Str { span: DUMMY_SP, value, @@ -368,6 +380,8 @@ impl Take for Regex { #[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))] impl<'a> arbitrary::Arbitrary<'a> for Regex { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + use swc_atoms::atom; + let span = u.arbitrary()?; let exp = u.arbitrary::()?.into(); let flags = atom!(""); // TODO diff --git a/crates/swc_ecma_ast/src/module_decl.rs b/crates/swc_ecma_ast/src/module_decl.rs index a8a03e4499a4..1e8b33192832 100644 --- a/crates/swc_ecma_ast/src/module_decl.rs +++ b/crates/swc_ecma_ast/src/module_decl.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use is_macro::Is; use swc_atoms::Atom; use swc_common::{ast_node, util::take::Take, EqIgnoreSpan, Span, DUMMY_SP}; @@ -405,10 +407,19 @@ bridge_from!(ModuleExportName, Ident, IdentName); impl ModuleExportName { /// Get the atom of the export name. - pub fn atom(&self) -> &Atom { + /// + /// If the name is a string literal that has ill-formed UTF16, it will be + /// converted to a UTF-8 valid string using lossy conversion (Unpaired + /// surrogates are replaced with Replacement Character). This is + /// a SyntaxError if it's fully complied to the spec. + /// See: https://tc39.es/ecma262/#sec-module-semantics-static-semantics-early-errors + pub fn atom(&self) -> Cow<'_, Atom> { match self { - ModuleExportName::Ident(i) => &i.sym, - ModuleExportName::Str(s) => &s.value, + ModuleExportName::Ident(i) => Cow::Borrowed(&i.sym), + ModuleExportName::Str(s) => match s.value.clone().try_into_atom() { + Ok(atom) => Cow::Owned(atom), + Err(original) => Cow::Owned(Atom::from(&*original.to_string_lossy())), + }, } } } diff --git a/crates/swc_ecma_ast/src/typescript.rs b/crates/swc_ecma_ast/src/typescript.rs index fc983b812ac8..3b1b29373648 100644 --- a/crates/swc_ecma_ast/src/typescript.rs +++ b/crates/swc_ecma_ast/src/typescript.rs @@ -11,7 +11,6 @@ use serde::{ Deserialize, Deserializer, Serialize, }; use string_enum::StringEnum; -use swc_atoms::Atom; use swc_common::{ast_node, EqIgnoreSpan, Span}; use crate::{ @@ -975,14 +974,14 @@ pub enum TsEnumMemberId { Str(Str), } -impl AsRef for TsEnumMemberId { - fn as_ref(&self) -> &Atom { - match &self { - TsEnumMemberId::Str(Str { value: ref sym, .. }) - | TsEnumMemberId::Ident(Ident { ref sym, .. }) => sym, - } - } -} +// impl AsRef for TsEnumMemberId { +// fn as_ref(&self) -> &Atom { +// match &self { +// TsEnumMemberId::Str(Str { value: ref sym, .. }) +// | TsEnumMemberId::Ident(Ident { ref sym, .. }) => sym, +// } +// } +// } #[ast_node("TsModuleDeclaration")] #[derive(Eq, Hash, EqIgnoreSpan)] diff --git a/crates/swc_ecma_codegen/src/lit.rs b/crates/swc_ecma_codegen/src/lit.rs index 1632f640ec01..511d1f1af3e2 100644 --- a/crates/swc_ecma_codegen/src/lit.rs +++ b/crates/swc_ecma_codegen/src/lit.rs @@ -2,6 +2,7 @@ use std::{fmt::Write, io, str}; use ascii::AsciiChar; use compact_str::CompactString; +use swc_atoms::wtf8::{CodePoint, Wtf8}; use swc_common::{Spanned, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_codegen_macros::node_impl; @@ -365,8 +366,39 @@ pub fn encode_regex_for_ascii(pattern: &str, ascii_only: bool) -> CowStr { CowStr::Owned(buf) } +macro_rules! cp { + ($c:expr) => { + unsafe { CodePoint::from_u32_unchecked($c as u32) } + }; +} + +const DOUBLE_QUOTE: CodePoint = cp!('"'); +const SINGLE_QUOTE: CodePoint = cp!('\''); +const NULL_CHAR: CodePoint = cp!('\x00'); +const BACKSPACE: CodePoint = cp!('\u{0008}'); +const FORM_FEED: CodePoint = cp!('\u{000c}'); +const LINE_FEED: CodePoint = cp!('\n'); +const CARRIAGE_RETURN: CodePoint = cp!('\r'); +const VERTICAL_TAB: CodePoint = cp!('\u{000b}'); +const TAB: CodePoint = cp!('\t'); +const BACKSLASH: CodePoint = cp!('\\'); +const CTRL_START_1: CodePoint = cp!('\x01'); +const CTRL_END_1: CodePoint = cp!('\x0f'); +const CTRL_START_2: CodePoint = cp!('\x10'); +const CTRL_END_2: CodePoint = cp!('\x1f'); +const PRINTABLE_START: CodePoint = cp!('\x20'); +const PRINTABLE_END: CodePoint = cp!('\x7e'); +const DEL_START: CodePoint = cp!('\u{7f}'); +const DEL_END: CodePoint = cp!('\u{ff}'); +const LINE_SEPARATOR: CodePoint = cp!('\u{2028}'); +const PARAGRAPH_SEPARATOR: CodePoint = cp!('\u{2029}'); +const ZERO_WIDTH_NO_BREAK_SPACE: CodePoint = cp!('\u{FEFF}'); + +const SURROGATE_START: CodePoint = cp!(0xd800); +const SURROGATE_END: CodePoint = cp!(0xdfff); + /// Returns `(quote_char, value)` -pub fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, CowStr) { +pub fn get_quoted_utf16(v: &Wtf8, ascii_only: bool, target: EsVersion) -> (AsciiChar, CowStr) { // Fast path: If the string is ASCII and doesn't need escaping, we can avoid // allocation if v.is_ascii() { @@ -398,7 +430,12 @@ pub fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiC if (quote_char == AsciiChar::Apostrophe && single_quote_count == 0) || (quote_char == AsciiChar::Quotation && double_quote_count == 0) { - return (quote_char, CowStr::Borrowed(v)); + return ( + quote_char, + // SAFETY: We have checked that the string is ASCII. So it does not contain any + // unpaired surrogate. + CowStr::Borrowed(v.as_str().unwrap()), + ); } } } @@ -406,10 +443,10 @@ pub fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiC // Slow path: Original implementation for strings that need processing // Count quotes first to determine which quote character to use let (mut single_quote_count, mut double_quote_count) = (0, 0); - for c in v.chars() { + for c in v.code_points() { match c { - '\'' => single_quote_count += 1, - '"' => double_quote_count += 1, + SINGLE_QUOTE => single_quote_count += 1, + DOUBLE_QUOTE => double_quote_count += 1, _ => {} } } @@ -435,146 +472,80 @@ pub fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiC let capacity = v.len() + escape_count; let mut buf = CompactString::with_capacity(capacity); - let mut iter = v.chars().peekable(); + let mut iter = v.code_points().peekable(); while let Some(c) = iter.next() { match c { - '\x00' => { - if target < EsVersion::Es5 || matches!(iter.peek(), Some('0'..='9')) { + NULL_CHAR => { + if target < EsVersion::Es5 + || matches!(iter.peek(), Some(x) if *x >= cp!('0') && *x <= cp!('9')) + { buf.push_str("\\x00"); } else { buf.push_str("\\0"); } } - '\u{0008}' => buf.push_str("\\b"), - '\u{000c}' => buf.push_str("\\f"), - '\n' => buf.push_str("\\n"), - '\r' => buf.push_str("\\r"), - '\u{000b}' => buf.push_str("\\v"), - '\t' => buf.push('\t'), - '\\' => { - let next = iter.peek(); - match next { - Some('u') => { - let mut inner_iter = iter.clone(); - inner_iter.next(); - - let mut is_curly = false; - let mut next = inner_iter.peek(); - - if next == Some(&'{') { - is_curly = true; - inner_iter.next(); - next = inner_iter.peek(); - } else if next != Some(&'D') && next != Some(&'d') { - buf.push('\\'); - } - - if let Some(c @ 'D' | c @ 'd') = next { - let mut inner_buf = String::with_capacity(8); - inner_buf.push('\\'); - inner_buf.push('u'); - - if is_curly { - inner_buf.push('{'); - } - - inner_buf.push(*c); - inner_iter.next(); - - let mut is_valid = true; - for _ in 0..3 { - match inner_iter.next() { - Some(c @ '0'..='9') | Some(c @ 'a'..='f') - | Some(c @ 'A'..='F') => { - inner_buf.push(c); - } - _ => { - is_valid = false; - break; - } - } - } - - if is_curly { - inner_buf.push('}'); - } - - let range = if is_curly { - 3..(inner_buf.len() - 1) - } else { - 2..6 - }; - - if is_valid { - let val_str = &inner_buf[range]; - if let Ok(v) = u32::from_str_radix(val_str, 16) { - if v > 0xffff { - buf.push_str(&inner_buf); - let end = if is_curly { 7 } else { 5 }; - for _ in 0..end { - iter.next(); - } - } else if (0xd800..=0xdfff).contains(&v) { - buf.push('\\'); - } else { - buf.push_str("\\\\"); - } - } else { - buf.push_str("\\\\"); - } - } else { - buf.push_str("\\\\"); - } - } else if is_curly { - buf.push_str("\\\\"); - } else { - buf.push('\\'); - } - } - _ => buf.push_str("\\\\"), - } - } - c if c == escape_char => { + BACKSPACE => buf.push_str("\\b"), + FORM_FEED => buf.push_str("\\f"), + LINE_FEED => buf.push_str("\\n"), + CARRIAGE_RETURN => buf.push_str("\\r"), + VERTICAL_TAB => buf.push_str("\\v"), + TAB => buf.push('\t'), + BACKSLASH => buf.push_str("\\\\"), + c if matches!(c.to_char(), Some(c) if c == escape_char) => { buf.push('\\'); - buf.push(c); + // SAFETY: `escape_char` is a valid ASCII character. + buf.push(c.to_char().unwrap()); } - '\x01'..='\x0f' => { + c if c >= CTRL_START_1 && c <= CTRL_END_1 => { buf.push_str("\\x0"); - write!(&mut buf, "{:x}", c as u8).unwrap(); + write!(&mut buf, "{:x}", c.to_u32() as u8).unwrap(); } - '\x10'..='\x1f' => { + c if c >= CTRL_START_2 && c <= CTRL_END_2 => { buf.push_str("\\x"); - write!(&mut buf, "{:x}", c as u8).unwrap(); + write!(&mut buf, "{:x}", c.to_u32() as u8).unwrap(); } - '\x20'..='\x7e' => buf.push(c), - '\u{7f}'..='\u{ff}' => { + c if c >= PRINTABLE_START && c <= PRINTABLE_END => { + // SAFETY: c is a valid ASCII character. + buf.push(c.to_char().unwrap()) + } + c if c >= DEL_START && c <= DEL_END => { if ascii_only || target <= EsVersion::Es5 { buf.push_str("\\x"); - write!(&mut buf, "{:x}", c as u8).unwrap(); + write!(&mut buf, "{:x}", c.to_u32() as u8).unwrap(); } else { - buf.push(c); + // SAFETY: c is a valid Rust char. + buf.push(c.to_char().unwrap()); } } - '\u{2028}' => buf.push_str("\\u2028"), - '\u{2029}' => buf.push_str("\\u2029"), - '\u{FEFF}' => buf.push_str("\\uFEFF"), + LINE_SEPARATOR => buf.push_str("\\u2028"), + PARAGRAPH_SEPARATOR => buf.push_str("\\u2029"), + ZERO_WIDTH_NO_BREAK_SPACE => buf.push_str("\\uFEFF"), c => { if c.is_ascii() { - buf.push(c); - } else if c > '\u{FFFF}' { + // SAFETY: c is a valid ASCII character. + buf.push(c.to_char().unwrap()); + } else if c > cp!('\u{FFFF}') { if target <= EsVersion::Es5 { - let h = ((c as u32 - 0x10000) / 0x400) + 0xd800; - let l = (c as u32 - 0x10000) % 0x400 + 0xdc00; + let h = ((c.to_u32() - 0x10000) / 0x400) + 0xd800; + let l = (c.to_u32() - 0x10000) % 0x400 + 0xdc00; write!(&mut buf, "\\u{h:04X}\\u{l:04X}").unwrap(); } else if ascii_only { - write!(&mut buf, "\\u{{{:04X}}}", c as u32).unwrap(); + write!(&mut buf, "\\u{{{:04X}}}", c.to_u32()).unwrap(); } else { - buf.push(c); + // SAFETY: c is a valid Rust char. (> U+FFFF && <= U+10FFFF) + // The latter condition is guaranteed by CodePoint. + buf.push(c.to_char().unwrap()); } + } else if c >= SURROGATE_START && c <= SURROGATE_END { + // Unparied Surrogate + // Escape as \uXXXX + write!(&mut buf, "\\u{:04X}", c.to_u32()).unwrap(); } else if ascii_only { - write!(&mut buf, "\\u{:04X}", c as u16).unwrap(); + write!(&mut buf, "\\u{:04X}", c.to_u32() as u16).unwrap(); } else { - buf.push(c); + // SAFETY: c is a valid Rust char. (>= U+0080 && <= U+FFFF, excluding + // surrogates) + buf.push(c.to_char().unwrap()); } } } diff --git a/crates/swc_ecma_codegen/src/tests.rs b/crates/swc_ecma_codegen/src/tests.rs index ad065e3eb9d7..0a32dd3f58aa 100644 --- a/crates/swc_ecma_codegen/src/tests.rs +++ b/crates/swc_ecma_codegen/src/tests.rs @@ -596,7 +596,7 @@ fn test_get_quoted_utf16() { #[track_caller] fn es2020(src: &str, expected: &str) { assert_eq!( - combine(get_quoted_utf16(src, true, EsVersion::Es2020)), + combine(get_quoted_utf16(src.into(), true, EsVersion::Es2020)), expected ) } @@ -604,7 +604,7 @@ fn test_get_quoted_utf16() { #[track_caller] fn es2020_nonascii(src: &str, expected: &str) { assert_eq!( - combine(get_quoted_utf16(src, true, EsVersion::Es2020)), + combine(get_quoted_utf16(src.into(), true, EsVersion::Es2020)), expected ) } @@ -612,7 +612,7 @@ fn test_get_quoted_utf16() { #[track_caller] fn es5(src: &str, expected: &str) { assert_eq!( - combine(get_quoted_utf16(src, true, EsVersion::Es5)), + combine(get_quoted_utf16(src.into(), true, EsVersion::Es5)), expected ) } @@ -681,7 +681,7 @@ fn issue_1619_2() { #[test] fn issue_1619_3() { assert_eq!( - &*get_quoted_utf16("\x00\x31", true, EsVersion::Es3).1, + &*get_quoted_utf16("\x00\x31".into(), true, EsVersion::Es3).1, "\\x001" ); } @@ -701,7 +701,7 @@ fn check_latest(src: &str, expected: &str) { #[test] fn invalid_unicode_in_ident() { - check_latest("\\ud83d;", "\\ud83d;"); + check_latest("\\ud83d;", "\\uD83D;"); } #[test] diff --git a/crates/swc_ecma_codegen/tests/fixture/issue-10978/input.js b/crates/swc_ecma_codegen/tests/fixture/issue-10978/input.js new file mode 100644 index 000000000000..cdf2fe1cbfc8 --- /dev/null +++ b/crates/swc_ecma_codegen/tests/fixture/issue-10978/input.js @@ -0,0 +1,4 @@ +"\"\\\\uD83D\\\\uDD25\""; +"\"\\uD83D\\uDD25\""; +`\\uD83D\\uDD25`; +`\uD83D\uDD25`; diff --git a/crates/swc_ecma_codegen/tests/fixture/issue-10978/output.js b/crates/swc_ecma_codegen/tests/fixture/issue-10978/output.js new file mode 100644 index 000000000000..cdf2fe1cbfc8 --- /dev/null +++ b/crates/swc_ecma_codegen/tests/fixture/issue-10978/output.js @@ -0,0 +1,4 @@ +"\"\\\\uD83D\\\\uDD25\""; +"\"\\uD83D\\uDD25\""; +`\\uD83D\\uDD25`; +`\uD83D\uDD25`; diff --git a/crates/swc_ecma_codegen/tests/fixture/issue-10978/output.min.js b/crates/swc_ecma_codegen/tests/fixture/issue-10978/output.min.js new file mode 100644 index 000000000000..ada19f9f566e --- /dev/null +++ b/crates/swc_ecma_codegen/tests/fixture/issue-10978/output.min.js @@ -0,0 +1 @@ +'"\\\\uD83D\\\\uDD25"';'"\\uD83D\\uDD25"';`\\uD83D\\uDD25`;`\uD83D\uDD25`; diff --git a/crates/swc_ecma_codegen/tests/fixture/string/output.min.js b/crates/swc_ecma_codegen/tests/fixture/string/output.min.js index c500cab45a4b..bfb34a968771 100644 --- a/crates/swc_ecma_codegen/tests/fixture/string/output.min.js +++ b/crates/swc_ecma_codegen/tests/fixture/string/output.min.js @@ -1,3 +1,3 @@ import*as commonjsHelpers from"\0commonjsHelpers.js";const string1="test";const string2="test";const string3='te"st';const string4="te'st";const string5="test\ntest\ntest";const string6=`Yet another string primitive`;const string7="This is a very long string which needs to wrap across multiple lines because otherwise my code is unreadable.";const string8="中文 español English हिन्दी العربية português বাংলা русский 日本語 ਪੰਜਾਬੀ 한국어 தமிழ்";const string9=``;const string10=`xx\`x`;const string11=`${foo+2}`;const string12=` foo ${bar+`baz ${qux}`}`;const string13=String.raw`foo`;const string14=foo`bar`;const string15=`foo bar -ↂωↂ`;const string16=`\``;const string17=`${4+4} equals 4 + 4`;const string18=`This is ${undefined}`;const string19=`This is ${NaN}`;const string20=`This is ${null}`;const string21=`This is ${Infinity}`;const string22="This is ${1/0}";const string23="This is ${1/0}";const string24="This is ${NaN}";const string25="This is ${null}";const string26=`This is ${1/0}`;const string27=`This is ${0/0}`;const string28="This is ${0/0}";const string29="This is ${0/0}";const string30=`${4**11}`;const string31=`${4**12}`;const string32=`${4**14}`;const string33="";const string34="\b";const string35="\f";const string36=" ";const string37="\v";const string38="\n";const string39="\\n";const string40="\\";const string41='\\"';const string42="'\"";const string43="\\\\";const string44="\0";const string45="\0!";const string46="\x001";const string47="\\0";const string48="\\0!";const string49="\x07";const string50="\x07!";const string51="\x071";const string52="\x07";const string53="\\7";const string54="\\7!";const string55="\\01";const string56="\x10";const string57="\\x10";const string58="\x1b";const string59="\\x1B";const string60="ꯍ";const string61="ꯍ";const string62="U000123AB";const string63="𒎫";const string64="\uD808\uDFAB";const string65="\uD808";const string66="\uD808X";const string67="\uDFAB";const string68="\uDFABX";const string69="€";const string70="ÿ";const string71="🍕";const string72="\uD801\uDC02\uDC03\uD804";const string73="π";const 貓="🐈";const 貓abc="🐈";const abc貓="🐈";const string74="\u2028";const string75="\u2029";const string76="\uFEFF";const string77="\x10";const string78=" ";const string79=" ";const string80="2";const string81="\x16";const string82="\x06";const string83="\0a";const string84='"test"test"test';const string85="\"test'test'test";const string86='"test"test"test';const string87="'test'test'test";const string88="😄";const string89=new RegExp("\r").test("\r");const string90=new RegExp(" ").test(" ");const string91=new RegExp("\x1b").test("["+"\x1b"+"]");const string92=new RegExp("\\x1b").test("\x1b");const string93=new RegExp("\x1b").test("\x1b");const string94="퟿";const string95="ퟻ";const string96=sql`'#ERROR'`;const string97=" ";const string98="\ud83d\ude00";const string99="\ud83d@\ude00";const string100="a";const string101="\u2028";const string102="\uD800";const string103="\u{D800}";const string104="\uDBFF";const string105="\u{DBFF}";const string106="\uDC00";const string107="\u{DC00}";const string108="\uDFFF";const string109="\u{DFFF}";const string110="￿";const string111="￿";const string112="\ud800";const string113="\uD800";React.createElement("div",null,"this should not parse as unicode: \\u00a0");const a="֑-ۯۺ-ࣿ‏\ud802-\ud803\ud83a-\ud83bיִ-﷿ﹰ-ﻼ";const b="A-Za-zÀ-ÖØ-öø-ʸ̀-֐ऀ-῿‎Ⰰ-\ud801\ud804-\ud839\ud83c-\udbff豈-﬜︀-﹯﻽-￿";var x="\u{D800}";var x2="\u{D800}";var x3="\u{D800}\u{D800}";const zzz="\0a"; +ↂωↂ`;const string16=`\``;const string17=`${4+4} equals 4 + 4`;const string18=`This is ${undefined}`;const string19=`This is ${NaN}`;const string20=`This is ${null}`;const string21=`This is ${Infinity}`;const string22="This is ${1/0}";const string23="This is ${1/0}";const string24="This is ${NaN}";const string25="This is ${null}";const string26=`This is ${1/0}`;const string27=`This is ${0/0}`;const string28="This is ${0/0}";const string29="This is ${0/0}";const string30=`${4**11}`;const string31=`${4**12}`;const string32=`${4**14}`;const string33="";const string34="\b";const string35="\f";const string36=" ";const string37="\v";const string38="\n";const string39="\\n";const string40="\\";const string41='\\"';const string42="'\"";const string43="\\\\";const string44="\0";const string45="\0!";const string46="\x001";const string47="\\0";const string48="\\0!";const string49="\x07";const string50="\x07!";const string51="\x071";const string52="\x07";const string53="\\7";const string54="\\7!";const string55="\\01";const string56="\x10";const string57="\\x10";const string58="\x1b";const string59="\\x1B";const string60="ꯍ";const string61="ꯍ";const string62="U000123AB";const string63="𒎫";const string64="𒎫";const string65="\uD808";const string66="\uD808X";const string67="\uDFAB";const string68="\uDFABX";const string69="€";const string70="ÿ";const string71="🍕";const string72="𐐂\uDC03\uD804";const string73="π";const 貓="🐈";const 貓abc="🐈";const abc貓="🐈";const string74="\u2028";const string75="\u2029";const string76="\uFEFF";const string77="\x10";const string78=" ";const string79=" ";const string80="2";const string81="\x16";const string82="\x06";const string83="\0a";const string84='"test"test"test';const string85="\"test'test'test";const string86='"test"test"test';const string87="'test'test'test";const string88="😄";const string89=new RegExp("\r").test("\r");const string90=new RegExp(" ").test(" ");const string91=new RegExp("\x1b").test("["+"\x1b"+"]");const string92=new RegExp("\\x1b").test("\x1b");const string93=new RegExp("\x1b").test("\x1b");const string94="퟿";const string95="ퟻ";const string96=sql`'#ERROR'`;const string97=" ";const string98="😀";const string99="\uD83D@\uDE00";const string100="a";const string101="\u2028";const string102="\uD800";const string103="\uD800";const string104="\uDBFF";const string105="\uDBFF";const string106="\uDC00";const string107="\uDC00";const string108="\uDFFF";const string109="\uDFFF";const string110="￿";const string111="￿";const string112="\uD800";const string113="\uD800";React.createElement("div",null,"this should not parse as unicode: \\u00a0");const a="֑-ۯۺ-ࣿ‏\uD802-\uD803\uD83A-\uD83Bיִ-﷿ﹰ-ﻼ";const b="A-Za-zÀ-ÖØ-öø-ʸ̀-֐ऀ-῿‎Ⰰ-\uD801\uD804-\uD839\uD83C-\uDBFF豈-﬜︀-﹯﻽-￿";var x="\uD800";var x2="\uD800";var x3="\uD800\uD800";const zzz="\0a"; diff --git a/crates/swc_ecma_codegen/tests/fixture/template-literal/output.min.js b/crates/swc_ecma_codegen/tests/fixture/template-literal/output.min.js index c73000026c7e..9f03e9d428e3 100644 --- a/crates/swc_ecma_codegen/tests/fixture/template-literal/output.min.js +++ b/crates/swc_ecma_codegen/tests/fixture/template-literal/output.min.js @@ -1,5 +1,5 @@ const template_literal1=`test${"test"}test${"test"}`;const template_literal2=``;const template_literal3=` `;const template_literal4=`string text`;const template_literal5=`string text line 1 - string text line 2`;const template_literal6=`string text ${expression} string text`;const templateFn=expression=>`string text ${expression} string text`;const template_literal7=example`string text ${expression} string text`;const template_literal8=`header ${isLargeScreen()?"":`icon-${item.isCollapsed?"expander":"collapser"}`}`;const template_literal9=`test \u00A9`;const template_literal10=`test \u{2F804}`;const template_literal11=`test \xa9`;const template_literal12=`test \0o251`;function latex(str){return{"cooked":str[0],"raw":str.raw[0]}}const template_literal14=latex`\unicode`;const template_literal15=`"test"test"test`;const template_literal16=`"test'test'test`;const template_literal17=`"test"test"test`;const template_literal18=`'test'test'test`;const template_literal19=`\0`;const template_literal20=`\x01`;const template_literal21=`\0${0}`;const template_literal22=`\x01${0}`;const template_literal23=`${0}\0`;const template_literal24=`${0}\x01`;const template_literal25=`${0}\0${1}`;const template_literal26=`${0}\x01${1}`;const template_literal27=String.raw`\1`;const template_literal28=String.raw`\\x01`;const template_literal29=String.raw`\\1${0}`;const template_literal30=String.raw`\\x01${0}`;const template_literal31=String.raw`${0}\\1`;const template_literal32=String.raw`${0}\\x01`;const template_literal33=String.raw`${0}\\1${1}`;const template_literal34=String.raw`${0}\\x01${1}`;const template_literal35=`${y}`;const template_literal36=`$(y)`;const template_literal37=`{y}$`;const template_literal38=`$}y{`;const template_literal39=`\\${y}`;const template_literal40=`$\\{y}`;await tag`x`;await (tag`x`);(await tag)`x`;await tag`${x}`;await (tag`${x}`);(await tag)`${x}`;new tag`x`;new(tag`x`);new tag()`x`;(new tag)`x`;new tag`${x}`;new(tag`${x}`);new tag()`${x}`;(new tag)`${x}`;new tag`${x}`;new(tag`${x}`);new tag()`${x}`;(new tag)`${x}`;const template_literal41=`${"test`"}${'test"'}${"test'''"}`;const template_literal42="֑-ۯۺ-ࣿ‏\ud802-\ud803\ud83a-\ud83bיִ-﷿ﹰ-ﻼ";const template_literal43="A-Za-zÀ-ÖØ-öø-ʸ̀-֐ऀ-῿‎Ⰰ-\ud801\ud804-\ud839\ud83c-\udbff豈-﬜︀-﹯﻽-￿";const template_literal45=`xx\`x`;const template_literal46=`${foo+2}`;const template_literal47=` foo ${bar+`baz ${qux}`}`;const template_literal48=`foo + string text line 2`;const template_literal6=`string text ${expression} string text`;const templateFn=expression=>`string text ${expression} string text`;const template_literal7=example`string text ${expression} string text`;const template_literal8=`header ${isLargeScreen()?"":`icon-${item.isCollapsed?"expander":"collapser"}`}`;const template_literal9=`test \u00A9`;const template_literal10=`test \u{2F804}`;const template_literal11=`test \xa9`;const template_literal12=`test \0o251`;function latex(str){return{"cooked":str[0],"raw":str.raw[0]}}const template_literal14=latex`\unicode`;const template_literal15=`"test"test"test`;const template_literal16=`"test'test'test`;const template_literal17=`"test"test"test`;const template_literal18=`'test'test'test`;const template_literal19=`\0`;const template_literal20=`\x01`;const template_literal21=`\0${0}`;const template_literal22=`\x01${0}`;const template_literal23=`${0}\0`;const template_literal24=`${0}\x01`;const template_literal25=`${0}\0${1}`;const template_literal26=`${0}\x01${1}`;const template_literal27=String.raw`\1`;const template_literal28=String.raw`\\x01`;const template_literal29=String.raw`\\1${0}`;const template_literal30=String.raw`\\x01${0}`;const template_literal31=String.raw`${0}\\1`;const template_literal32=String.raw`${0}\\x01`;const template_literal33=String.raw`${0}\\1${1}`;const template_literal34=String.raw`${0}\\x01${1}`;const template_literal35=`${y}`;const template_literal36=`$(y)`;const template_literal37=`{y}$`;const template_literal38=`$}y{`;const template_literal39=`\\${y}`;const template_literal40=`$\\{y}`;await tag`x`;await (tag`x`);(await tag)`x`;await tag`${x}`;await (tag`${x}`);(await tag)`${x}`;new tag`x`;new(tag`x`);new tag()`x`;(new tag)`x`;new tag`${x}`;new(tag`${x}`);new tag()`${x}`;(new tag)`${x}`;new tag`${x}`;new(tag`${x}`);new tag()`${x}`;(new tag)`${x}`;const template_literal41=`${"test`"}${'test"'}${"test'''"}`;const template_literal42="֑-ۯۺ-ࣿ‏\uD802-\uD803\uD83A-\uD83Bיִ-﷿ﹰ-ﻼ";const template_literal43="A-Za-zÀ-ÖØ-öø-ʸ̀-֐ऀ-῿‎Ⰰ-\uD801\uD804-\uD839\uD83C-\uDBFF豈-﬜︀-﹯﻽-￿";const template_literal45=`xx\`x`;const template_literal46=`${foo+2}`;const template_literal47=` foo ${bar+`baz ${qux}`}`;const template_literal48=`foo bar ↂωↂ`;const template_literal48=`This is ${undefined}`;const template_literal49=`This is ${NaN}`;const template_literal50=`This is ${null}`;const template_literal51=`This is ${Infinity}`;const template_literal60=`${4**11}`;const template_literal61=`Hello ${guest()}, welcome to ${location()}${"."}`;const template_literal62=`${1}${2}${3}${4}${5}${6}${7}${8}${9}${0}`;const template_literal63=`${foobar()}${foobar()}${foobar()}${foobar()}`;const template_literal64=`${1}${foobar()}${2}${foobar()}${3}${foobar()}`;const template_literal65="Decimals "+`${1}${2}${3}${4}${5}${6}${7}${8}${9}${0}`;const template_literal66=`${`${`${`foo`}`}`}`;const template_literal67=`before ${`innerBefore ${any} innerAfter`} after`;const template_literal68=`1 ${2+`3 ${any} 4`+5} 6`;const template_literal69=`${content}`;const template_literal70=``;const template_literal72=`\u0020\u{20}\u{00020} `;console.log(`\\n\\r\\u2028\\u2029 \r\u2028\u2029`);function a(){return`\ diff --git a/crates/swc_ecma_codegen/tests/fixture/vercel/2/output.min.js b/crates/swc_ecma_codegen/tests/fixture/vercel/2/output.min.js index 7715f1f01994..950a5f49ab8c 100644 --- a/crates/swc_ecma_codegen/tests/fixture/vercel/2/output.min.js +++ b/crates/swc_ecma_codegen/tests/fixture/vercel/2/output.min.js @@ -5,4 +5,4 @@ function isUpdateAvailable(){// __webpack_hash__ is the hash of the current comp function canApplyUpdates(){return module.hot.status()==="idle"}// This function reads code updates on the fly and hard // reloads the page when it has changed. async function tryApplyUpdates(){if(!isUpdateAvailable()||!canApplyUpdates()){return}try{const res=await fetch(typeof __webpack_runtime_id__!=="undefined"?`${hotUpdatePath}${curHash}.${__webpack_runtime_id__}.hot-update.json`:`${hotUpdatePath}${curHash}.hot-update.json`);const jsonData=await res.json();const curPage=page==="/"?"index":page;// webpack 5 uses an array instead -const pageUpdated=(Array.isArray(jsonData.c)?jsonData.c:Object.keys(jsonData.c)).some(mod=>{return(mod.indexOf(`pages${curPage.startsWith("/")?curPage:`/${curPage}`}`)!==-1||mod.indexOf(`pages${curPage.startsWith("/")?curPage:`/${curPage}`}`.replace(/\//g,"\\"))!==-1)});if(pageUpdated){document.location.reload(true)}else{curHash=mostRecentHash}}catch(err){console.error("Error occurred checking for update",err);document.location.reload(true)}}addMessageListener(event=>{if(event.data==="\uD83D\uDC93"){return}try{const message=JSON.parse(event.data);if(message.action==="sync"||message.action==="built"){if(!message.hash){return}mostRecentHash=message.hash;tryApplyUpdates()}else if(message.action==="reloadPage"){document.location.reload(true)}}catch(ex){console.warn("Invalid HMR message: "+event.data+"\n"+ex)}});connectHMR({assetPrefix,path:"/_next/webpack-hmr"});displayContent();initOnDemandEntries(data.page); +const pageUpdated=(Array.isArray(jsonData.c)?jsonData.c:Object.keys(jsonData.c)).some(mod=>{return(mod.indexOf(`pages${curPage.startsWith("/")?curPage:`/${curPage}`}`)!==-1||mod.indexOf(`pages${curPage.startsWith("/")?curPage:`/${curPage}`}`.replace(/\//g,"\\"))!==-1)});if(pageUpdated){document.location.reload(true)}else{curHash=mostRecentHash}}catch(err){console.error("Error occurred checking for update",err);document.location.reload(true)}}addMessageListener(event=>{if(event.data==="💓"){return}try{const message=JSON.parse(event.data);if(message.action==="sync"||message.action==="built"){if(!message.hash){return}mostRecentHash=message.hash;tryApplyUpdates()}else if(message.action==="reloadPage"){document.location.reload(true)}}catch(ex){console.warn("Invalid HMR message: "+event.data+"\n"+ex)}});connectHMR({assetPrefix,path:"/_next/webpack-hmr"});displayContent();initOnDemandEntries(data.page); diff --git a/crates/swc_ecma_compat_es2015/src/block_scoping/mod.rs b/crates/swc_ecma_compat_es2015/src/block_scoping/mod.rs index d0410f961543..fa8fef58ac25 100644 --- a/crates/swc_ecma_compat_es2015/src/block_scoping/mod.rs +++ b/crates/swc_ecma_compat_es2015/src/block_scoping/mod.rs @@ -782,7 +782,7 @@ impl VisitMut for FlowHelper<'_> { arg: Some( Lit::Str(Str { span, - value, + value: value.into(), raw: None, }) .into(), @@ -808,7 +808,7 @@ impl VisitMut for FlowHelper<'_> { arg: Some( Lit::Str(Str { span, - value, + value: value.into(), raw: None, }) .into(), diff --git a/crates/swc_ecma_compat_es2015/src/classes/mod.rs b/crates/swc_ecma_compat_es2015/src/classes/mod.rs index 677f7c821b02..d88b55ab1907 100644 --- a/crates/swc_ecma_compat_es2015/src/classes/mod.rs +++ b/crates/swc_ecma_compat_es2015/src/classes/mod.rs @@ -2,7 +2,7 @@ use std::iter; use rustc_hash::FxBuildHasher; use serde::Deserialize; -use swc_atoms::atom; +use swc_atoms::{atom, Atom}; use swc_common::{util::take::Take, BytePos, Mark, Span, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::{helper, native::is_native, perf::Check}; @@ -290,14 +290,18 @@ impl VisitMut for Classes { c.ident = Some(Ident::from(ident.clone()).into_private()); } PropName::Str(Str { value, span, .. }) => { - if is_valid_prop_ident(value) { - c.ident = Some(private_ident!(*span, value.clone())); + if let Some(value_str) = value.as_str() { + if is_valid_prop_ident(value_str) { + c.ident = Some(private_ident!(*span, value_str)); + } } } PropName::Computed(ComputedPropName { expr, .. }) => { if let Expr::Lit(Lit::Str(Str { value, span, .. })) = &**expr { - if is_valid_prop_ident(value) { - c.ident = Some(private_ident!(*span, value.clone())); + if let Some(value_str) = value.as_str() { + if is_valid_prop_ident(value_str) { + c.ident = Some(private_ident!(*span, value_str)); + } } } } @@ -568,7 +572,7 @@ impl Classes { &mut stmts, Lit::Str(Str { span: DUMMY_SP, - value: atom!("use strict"), + value: atom!("use strict").into(), raw: Some(atom!("\"use strict\"")), }) .into_stmt(), @@ -802,12 +806,18 @@ impl Classes { ident: if m.kind == MethodKind::Method && !computed { match prop_name { Expr::Ident(ident) => Some(private_ident!(ident.span, ident.sym)), - Expr::Lit(Lit::Str(Str { span, value, .. })) if is_valid_ident(&value) => { - Some(Ident::new( - value, - span, - SyntaxContext::empty().apply_mark(Mark::new()), - )) + Expr::Lit(Lit::Str(Str { span, value, .. })) => { + value.as_str().and_then(|value_str| { + if is_valid_ident(value_str) { + Some(Ident::new( + Atom::from(value_str), + span, + SyntaxContext::empty().apply_mark(Mark::new()), + )) + } else { + None + } + }) } _ => None, } diff --git a/crates/swc_ecma_compat_es2015/src/classes/prop_name.rs b/crates/swc_ecma_compat_es2015/src/classes/prop_name.rs index f5d11838a0d9..6044c7f0a0e4 100644 --- a/crates/swc_ecma_compat_es2015/src/classes/prop_name.rs +++ b/crates/swc_ecma_compat_es2015/src/classes/prop_name.rs @@ -1,10 +1,10 @@ -use swc_atoms::Atom; +use swc_atoms::Wtf8Atom; use swc_common::{Span, Spanned}; use swc_ecma_ast::*; #[derive(Debug, PartialEq, Eq, Hash)] pub enum HashKey { - Str(Atom), + Str(Wtf8Atom), /// Not for key merging Computed(Span), } @@ -12,9 +12,8 @@ pub enum HashKey { impl From<&PropName> for HashKey { fn from(p: &PropName) -> Self { match p { - PropName::Ident(IdentName { sym: value, .. }) | PropName::Str(Str { value, .. }) => { - HashKey::Str(value.clone()) - } + PropName::Ident(IdentName { sym: value, .. }) => HashKey::Str(value.clone().into()), + PropName::Str(Str { value, .. }) => HashKey::Str(value.clone()), PropName::Num(Number { value, .. }) => HashKey::Str(value.to_string().into()), PropName::BigInt(BigInt { value, .. }) => HashKey::Str(value.to_string().into()), PropName::Computed(expr) => HashKey::Computed(expr.span()), diff --git a/crates/swc_ecma_compat_es2015/src/computed_props.rs b/crates/swc_ecma_compat_es2015/src/computed_props.rs index b621892ed940..c193d973dfc8 100644 --- a/crates/swc_ecma_compat_es2015/src/computed_props.rs +++ b/crates/swc_ecma_compat_es2015/src/computed_props.rs @@ -134,7 +134,7 @@ impl VisitMut for ComputedProps { Lit::Str(Str { span: ident.span, raw: None, - value: ident.sym.clone(), + value: ident.sym.clone().into(), }) .into() }, @@ -418,7 +418,7 @@ fn prop_name_to_expr(p: PropName, loose: bool) -> (Expr, bool) { } else { Lit::Str(Str { raw: None, - value: i.sym, + value: i.sym.into(), span: i.span, }) .into() diff --git a/crates/swc_ecma_compat_es2015/src/duplicate_keys.rs b/crates/swc_ecma_compat_es2015/src/duplicate_keys.rs index c97454b93975..24c34824007c 100644 --- a/crates/swc_ecma_compat_es2015/src/duplicate_keys.rs +++ b/crates/swc_ecma_compat_es2015/src/duplicate_keys.rs @@ -1,5 +1,5 @@ use rustc_hash::FxHashSet; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::Spanned; use swc_ecma_ast::*; use swc_ecma_transforms_base::perf::Parallel; @@ -41,6 +41,14 @@ struct PropFolder { setter_props: FxHashSet, } +#[inline] +fn atom_from_wtf8(value: &Wtf8Atom) -> Atom { + value + .as_str() + .map(Atom::from) + .unwrap_or_else(|| Atom::from(value.to_string_lossy())) +} + #[swc_trace] impl VisitMut for PropFolder { noop_visit_mut_type!(fail); @@ -106,14 +114,14 @@ impl VisitMut for PropNameFolder<'_> { expr: Lit::Str(Str { span, raw: None, - value: ident.sym.clone(), + value: ident.sym.clone().into(), }) .into(), }) } } PropName::Str(s) => { - if !self.props.insert(s.value.clone()) { + if !self.props.insert(atom_from_wtf8(&s.value)) { *name = PropName::Computed(ComputedPropName { span: s.span, expr: s.clone().into(), @@ -123,7 +131,7 @@ impl VisitMut for PropNameFolder<'_> { PropName::Computed(ComputedPropName { expr, .. }) => { // Computed property might collide if let Expr::Lit(Lit::Str(Str { ref value, .. })) = &**expr { - self.props.insert(value.clone()); + self.props.insert(atom_from_wtf8(value)); } } _ => {} diff --git a/crates/swc_ecma_compat_es2015/src/object_super.rs b/crates/swc_ecma_compat_es2015/src/object_super.rs index 09e3fa4f4f6f..405174af67d1 100644 --- a/crates/swc_ecma_compat_es2015/src/object_super.rs +++ b/crates/swc_ecma_compat_es2015/src/object_super.rs @@ -196,7 +196,7 @@ impl SuperReplacer { sym: value, span, .. }) => Lit::Str(Str { raw: None, - value, + value: value.into(), span, }) .into(), diff --git a/crates/swc_ecma_compat_es2015/src/shorthand_property.rs b/crates/swc_ecma_compat_es2015/src/shorthand_property.rs index 75af36d78914..0b8d80e8de9a 100644 --- a/crates/swc_ecma_compat_es2015/src/shorthand_property.rs +++ b/crates/swc_ecma_compat_es2015/src/shorthand_property.rs @@ -85,7 +85,9 @@ impl VisitMut for Shorthand { } .into() } - PropName::Str(s @ Str { span, .. }) if s.value == "__proto__" => { + PropName::Str(s @ Str { span, .. }) + if s.value.as_str() == Some("__proto__") => + { ComputedPropName { span, expr: s.into(), diff --git a/crates/swc_ecma_compat_es2015/src/template_literal.rs b/crates/swc_ecma_compat_es2015/src/template_literal.rs index 333db09bcd34..bd51d7b3c3ba 100644 --- a/crates/swc_ecma_compat_es2015/src/template_literal.rs +++ b/crates/swc_ecma_compat_es2015/src/template_literal.rs @@ -1,7 +1,7 @@ use std::{iter, mem}; use serde_derive::Deserialize; -use swc_atoms::{atom, Atom}; +use swc_atoms::{atom, wtf8::Wtf8Buf}; use swc_common::{util::take::Take, BytePos, Spanned, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::{helper, perf::Parallel}; @@ -63,11 +63,11 @@ impl VisitMut for TemplateLiteral { let s = quasis[0] .cooked .clone() - .unwrap_or_else(|| quasis[0].raw.clone()); + .unwrap_or_else(|| quasis[0].raw.clone().into()); Str { span: quasis[0].span, - value: Atom::from(&*s), + value: s, raw: None, } }) @@ -97,12 +97,12 @@ impl VisitMut for TemplateLiteral { Some(TplElement { span, cooked, raw, .. }) => { - let s = cooked.clone().unwrap_or_else(|| raw.clone()); + let s = cooked.clone().unwrap_or_else(|| raw.clone().into()); Box::new( Lit::Str(Str { span: *span, - value: (&*s).into(), + value: s, raw: None, }) .into(), @@ -134,10 +134,13 @@ impl VisitMut for TemplateLiteral { value: r_value, .. })) => { + let mut value_buf: Wtf8Buf = (&value).into(); + value_buf.push_wtf8(&r_value); + let value = value_buf.into(); obj = Lit::Str(Str { span: span.with_hi(r_span.hi()), raw: None, - value: format!("{value}{r_value}").into(), + value, }) .into(); continue; diff --git a/crates/swc_ecma_compat_es2015/src/typeof_symbol.rs b/crates/swc_ecma_compat_es2015/src/typeof_symbol.rs index 977002f4e47c..2aad12b5e8e7 100644 --- a/crates/swc_ecma_compat_es2015/src/typeof_symbol.rs +++ b/crates/swc_ecma_compat_es2015/src/typeof_symbol.rs @@ -137,9 +137,10 @@ impl VisitMut for TypeOfSymbol { if let Some(body) = &f.body { if let Some(Stmt::Expr(first)) = body.stmts.first() { if let Expr::Lit(Lit::Str(s)) = &*first.expr { - match &*s.value { - "@swc/helpers - typeof" | "@babel/helpers - typeof" => return, - _ => {} + if let Some(text) = s.value.as_str() { + if matches!(text, "@swc/helpers - typeof" | "@babel/helpers - typeof") { + return; + } } } } @@ -153,8 +154,12 @@ impl VisitMut for TypeOfSymbol { fn is_non_symbol_literal(e: &Expr) -> bool { match e { Expr::Lit(Lit::Str(Str { value, .. })) => matches!( - &**value, - "undefined" | "boolean" | "number" | "string" | "function" + value.as_str(), + Some("undefined") + | Some("boolean") + | Some("number") + | Some("string") + | Some("function") ), _ => false, } diff --git a/crates/swc_ecma_compat_es2018/src/object_rest.rs b/crates/swc_ecma_compat_es2018/src/object_rest.rs index e1f900820168..f5623bef4d51 100644 --- a/crates/swc_ecma_compat_es2018/src/object_rest.rs +++ b/crates/swc_ecma_compat_es2018/src/object_rest.rs @@ -724,7 +724,18 @@ impl ObjectRest { ref value, span, .. }) => { let value = value.clone(); - (key, MemberProp::Ident(IdentName::new(value, span))) + ( + key, + MemberProp::Computed(ComputedPropName { + span, + expr: Lit::Str(Str { + span, + raw: None, + value: value.clone(), + }) + .into(), + }), + ) } PropName::Num(Number { span, value, .. }) => ( key, @@ -733,6 +744,7 @@ impl ObjectRest { expr: Lit::Str(Str { span, raw: None, + value: format!("{value}").into(), }) .into(), @@ -953,19 +965,21 @@ fn excluded_props(props: &[ObjectPatProp]) -> Vec> { PropName::Ident(ident) => Lit::Str(Str { span: ident.span, raw: None, - value: ident.sym.clone(), + value: ident.sym.clone().into(), }) .as_arg(), PropName::Str(s) => Lit::Str(s.clone()).as_arg(), PropName::Num(Number { span, value, .. }) => Lit::Str(Str { span: *span, raw: None, + value: format!("{value}").into(), }) .as_arg(), PropName::BigInt(BigInt { span, value, .. }) => Lit::Str(Str { span: *span, raw: None, + value: format!("{value}").into(), }) .as_arg(), @@ -976,7 +990,7 @@ fn excluded_props(props: &[ObjectPatProp]) -> Vec> { ObjectPatProp::Assign(AssignPatProp { key, .. }) => Lit::Str(Str { span: key.span, raw: None, - value: key.sym.clone(), + value: key.sym.clone().into(), }) .as_arg(), ObjectPatProp::Rest(..) => unreachable!("invalid syntax (multiple rest element)"), diff --git a/crates/swc_ecma_compat_es2022/src/class_properties/class_name_tdz.rs b/crates/swc_ecma_compat_es2022/src/class_properties/class_name_tdz.rs index 3302d644fc8a..11c53cb7a958 100644 --- a/crates/swc_ecma_compat_es2022/src/class_properties/class_name_tdz.rs +++ b/crates/swc_ecma_compat_es2022/src/class_properties/class_name_tdz.rs @@ -28,7 +28,7 @@ impl VisitMut for ClassNameTdzFolder<'_> { args: vec![Str { span: i.span, raw: None, - value: i.sym.clone(), + value: i.sym.clone().into(), } .as_arg()], diff --git a/crates/swc_ecma_compat_es3/src/member_expr_lits.rs b/crates/swc_ecma_compat_es3/src/member_expr_lits.rs index b29b0bde5de2..0bcfe86846bd 100644 --- a/crates/swc_ecma_compat_es3/src/member_expr_lits.rs +++ b/crates/swc_ecma_compat_es3/src/member_expr_lits.rs @@ -45,7 +45,7 @@ impl Fold for MemberExprLit { expr: Lit::Str(Str { span: i.span, raw: None, - value: i.sym, + value: i.sym.into(), }) .into(), }), diff --git a/crates/swc_ecma_compat_es3/src/prop_lits.rs b/crates/swc_ecma_compat_es3/src/prop_lits.rs index 81d2e213eadd..88048b3d4d2c 100644 --- a/crates/swc_ecma_compat_es3/src/prop_lits.rs +++ b/crates/swc_ecma_compat_es3/src/prop_lits.rs @@ -1,5 +1,5 @@ use swc_ecma_ast::*; -use swc_ecma_utils::is_valid_ident; +use swc_ecma_utils::{is_valid_ident, swc_atoms::Atom}; use swc_ecma_visit::{fold_pass, standard_only_fold, Fold, FoldWith}; use swc_trace_macro::swc_trace; @@ -46,11 +46,16 @@ impl Fold for PropertyLiteral { match n { PropName::Str(Str { raw, value, span, .. - }) => { - if value.is_reserved() || !is_valid_ident(&value) { + }) if value.as_str().is_some() => { + let v = value.as_str().unwrap(); + if v.is_reserved() || !is_valid_ident(v) { PropName::Str(Str { span, raw, value }) } else { - PropName::Ident(IdentName::new(value, span)) + PropName::Ident(IdentName::new( + // SAFETY: checked above + unsafe { Atom::from_wtf8_unchecked(value.clone()) }, + span, + )) } } PropName::Ident(i) => { @@ -59,7 +64,7 @@ impl Fold for PropertyLiteral { PropName::Str(Str { span, raw: None, - value: sym, + value: sym.into(), }) } else { PropName::Ident(IdentName { span, sym }) diff --git a/crates/swc_ecma_compiler/src/es2020/export_namespace_from.rs b/crates/swc_ecma_compiler/src/es2020/export_namespace_from.rs index ea6ed58a8f7d..6882c970c3ee 100644 --- a/crates/swc_ecma_compiler/src/es2020/export_namespace_from.rs +++ b/crates/swc_ecma_compiler/src/es2020/export_namespace_from.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use swc_atoms::Atom; use swc_ecma_ast::*; use swc_ecma_utils::private_ident; @@ -113,10 +115,17 @@ impl<'a> CompilerImpl<'a> { } } -fn normalize_name(module_export_name: &ModuleExportName) -> &Atom { +fn normalize_name(module_export_name: &ModuleExportName) -> Cow { match module_export_name { - ModuleExportName::Ident(Ident { sym: name, .. }) - | ModuleExportName::Str(Str { value: name, .. }) => name, + ModuleExportName::Ident(Ident { sym: name, .. }) => Cow::Borrowed(name), + ModuleExportName::Str(Str { value: name, .. }) => { + // Normally, the export name should be valid UTF-8. But it might also contain + // unpaired surrogates. Node would give an error in this case: + // `SyntaxError: Invalid module export name: contains unpaired + // surrogate`. Here, we temporarily replace the unpaired surrogates + // with U+FFFD REPLACEMENT CHARACTER by using Wtf8::to_string_lossy. + Cow::Owned(Atom::from(name.to_string_lossy())) + } #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), } diff --git a/crates/swc_ecma_ext_transforms/src/jest.rs b/crates/swc_ecma_ext_transforms/src/jest.rs index b5c13bbd4853..fc8a44008a92 100644 --- a/crates/swc_ecma_ext_transforms/src/jest.rs +++ b/crates/swc_ecma_ext_transforms/src/jest.rs @@ -107,7 +107,7 @@ impl VisitMut for Jest { is_type_only: false, .. }) => { - if HOIST_METHODS.contains(exported.atom()) { + if HOIST_METHODS.contains(&exported.atom()) { self.imported.push(local.to_id()); } } diff --git a/crates/swc_ecma_lexer/Cargo.toml b/crates/swc_ecma_lexer/Cargo.toml index fab9827d26d4..57741f41c3bb 100644 --- a/crates/swc_ecma_lexer/Cargo.toml +++ b/crates/swc_ecma_lexer/Cargo.toml @@ -28,7 +28,6 @@ typescript = [] verify = ["swc_ecma_visit"] [dependencies] -arrayvec = { workspace = true } bitflags = { workspace = true } either = { workspace = true } num-bigint = { workspace = true } diff --git a/crates/swc_ecma_lexer/src/common/lexer/char.rs b/crates/swc_ecma_lexer/src/common/lexer/char.rs index 53e433e4d7ee..705a3fd05f70 100644 --- a/crates/swc_ecma_lexer/src/common/lexer/char.rs +++ b/crates/swc_ecma_lexer/src/common/lexer/char.rs @@ -1,102 +1,3 @@ -use std::iter::FusedIterator; - -use arrayvec::ArrayVec; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Char(u32); - -impl From for Char { - fn from(c: char) -> Self { - Char(c as u32) - } -} - -impl From for Char { - fn from(c: u32) -> Self { - Char(c) - } -} - -pub struct CharIter(ArrayVec); - -/// Ported from https://github.com/web-infra-dev/oxc/blob/99a4816ce7b6132b2667257984f9d92ae3768f03/crates/oxc_parser/src/lexer/mod.rs#L1349-L1374 -impl IntoIterator for Char { - type IntoIter = CharIter; - type Item = char; - - #[allow(unsafe_code)] - fn into_iter(self) -> Self::IntoIter { - // // TODO: Check if this is correct - // fn to_char(v: u8) -> char { - // char::from_digit(v as _, 16).unwrap_or('0') - // } - - CharIter(match char::from_u32(self.0) { - Some(c) => { - let mut buf = ArrayVec::new(); - // Safety: we can make sure that `buf` has enough capacity - unsafe { - buf.push_unchecked(c); - } - buf - } - None => { - let mut buf = ArrayVec::new(); - - let high = self.0 & 0xffff0000 >> 16; - - let low = self.0 & 0x0000ffff; - - // The second code unit of a surrogate pair is always in the range from 0xDC00 - // to 0xDFFF, and is called a low surrogate or a trail surrogate. - if !(0xdc00..=0xdfff).contains(&low) { - // Safety: we can make sure that `buf` has enough capacity - unsafe { - buf.push_unchecked('\\'); - buf.push_unchecked('u'); - for c in format!("{high:x}").chars() { - buf.push_unchecked(c); - } - buf.push_unchecked('\\'); - buf.push_unchecked('u'); - for c in format!("{low:x}").chars() { - buf.push_unchecked(c); - } - } - } else { - // `https://tc39.es/ecma262/#sec-utf16decodesurrogatepair` - let astral_code_point = (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000; - - // Safety: we can make sure that `buf` has enough capacity - unsafe { - buf.push_unchecked('\\'); - buf.push_unchecked('u'); - for c in format!("{astral_code_point:x}").chars() { - buf.push_unchecked(c); - } - } - } - - buf - } - }) - } -} - -impl Iterator for CharIter { - type Item = char; - - fn next(&mut self) -> Option { - if self.0.is_empty() { - None - } else { - Some(self.0.remove(0)) - } - } -} - -impl FusedIterator for CharIter {} - /// Implemented for `char`. pub trait CharExt: Copy { fn to_char(self) -> Option; @@ -164,13 +65,6 @@ pub trait CharExt: Copy { } } -impl CharExt for Char { - #[inline(always)] - fn to_char(self) -> Option { - char::from_u32(self.0) - } -} - impl CharExt for char { #[inline(always)] fn to_char(self) -> Option { diff --git a/crates/swc_ecma_lexer/src/common/lexer/mod.rs b/crates/swc_ecma_lexer/src/common/lexer/mod.rs index 7be808407c43..c80ef7a412aa 100644 --- a/crates/swc_ecma_lexer/src/common/lexer/mod.rs +++ b/crates/swc_ecma_lexer/src/common/lexer/mod.rs @@ -1,11 +1,14 @@ use std::borrow::Cow; -use char::{Char, CharExt}; +use char::CharExt; use either::Either::{self, Left, Right}; use num_bigint::BigInt as BigIntValue; use smartstring::{LazyCompact, SmartString}; use state::State; -use swc_atoms::Atom; +use swc_atoms::{ + wtf8::{CodePoint, Wtf8, Wtf8Buf}, + Atom, +}; use swc_common::{ comments::{Comment, CommentKind}, input::{Input, StringInput}, @@ -62,6 +65,40 @@ static NOT_ASCII_ID_CONTINUE_TABLE: SafeByteMatchTable = static TEMPLATE_LITERAL_TABLE: SafeByteMatchTable = safe_byte_match_table!(|b| matches!(b, b'$' | b'`' | b'\\' | b'\r')); +/// Converts UTF-16 surrogate pair to Unicode code point. +/// `https://tc39.es/ecma262/#sec-utf16decodesurrogatepair` +#[inline] +const fn pair_to_code_point(high: u32, low: u32) -> u32 { + (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000 +} + +/// A Unicode escape sequence. +/// +/// `\u Hex4Digits`, `\u Hex4Digits \u Hex4Digits`, or `\u{ HexDigits }`. +#[derive(Debug)] +pub enum UnicodeEscape { + // `\u Hex4Digits` or `\u{ HexDigits }`, which forms a valid Unicode code point. + // Char cannot be in range 0xD800..=0xDFFF. + CodePoint(char), + // `\u Hex4Digits \u Hex4Digits`, which forms a valid Unicode astral code point. + // Char is in the range 0x10000..=0x10FFFF. + SurrogatePair(char), + // `\u Hex4Digits` or `\u{ HexDigits }`, which forms an invalid Unicode code point. + // Code unit is in the range 0xD800..=0xDFFF. + LoneSurrogate(u32), +} + +impl From for CodePoint { + fn from(value: UnicodeEscape) -> Self { + match value { + UnicodeEscape::CodePoint(c) | UnicodeEscape::SurrogatePair(c) => { + CodePoint::from_char(c) + } + UnicodeEscape::LoneSurrogate(u) => unsafe { CodePoint::from_u32_unchecked(u) }, + } + } +} + pub type LexResult = Result; fn remove_underscore(s: &str, has_underscore: bool) -> Cow<'_, str> { @@ -92,6 +129,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { unsafe fn input_slice(&mut self, start: BytePos, end: BytePos) -> &'a str; fn input_uncons_while(&mut self, f: impl FnMut(char) -> bool) -> &'a str; fn atom<'b>(&self, s: impl Into>) -> swc_atoms::Atom; + fn wtf8_atom<'b>(&self, s: impl Into>) -> swc_atoms::Wtf8Atom; fn push_error(&mut self, error: crate::error::Error); #[inline(always)] @@ -1116,13 +1154,74 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { self.input_slice(start, end) }; let raw = self.atom(raw); - Ok(Self::Token::str(value, raw, self)) + Ok(Self::Token::str(value.into(), raw, self)) + } + + // Modified based on + /// Unicode code unit (`\uXXXX`). + /// + /// The opening `\u` must already have been consumed before calling this + /// method. + /// + /// See background info on surrogate pairs: + /// * `https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae` + /// * `https://mathiasbynens.be/notes/javascript-identifiers-es6` + fn read_unicode_code_unit(&mut self) -> LexResult> { + const MIN_HIGH: u32 = 0xd800; + const MAX_HIGH: u32 = 0xdbff; + const MIN_LOW: u32 = 0xdc00; + const MAX_LOW: u32 = 0xdfff; + + let Some(high) = self.read_int_u32::<16>(4)? else { + return Ok(None); + }; + if let Some(ch) = char::from_u32(high) { + return Ok(Some(UnicodeEscape::CodePoint(ch))); + } + + // The first code unit of a surrogate pair is always in the range from 0xD800 to + // 0xDBFF, and is called a high surrogate or a lead surrogate. + // Note: `high` must be >= `MIN_HIGH`, otherwise `char::from_u32` would have + // returned `Some`, and already exited. + debug_assert!(high >= MIN_HIGH); + let is_pair = high <= MAX_HIGH + && self.input().cur() == Some('\\') + && self.input().peek() == Some('u'); + if !is_pair { + return Ok(Some(UnicodeEscape::LoneSurrogate(high))); + } + + let before_second = self.input().cur_pos(); + + // Bump `\u` + self.input_mut().bump_bytes(2); + + let Some(low) = self.read_int_u32::<16>(4)? else { + return Ok(None); + }; + + // The second code unit of a surrogate pair is always in the range from 0xDC00 + // to 0xDFFF, and is called a low surrogate or a trail surrogate. + // If this isn't a valid pair, rewind to before the 2nd, and return the first + // only. The 2nd could be the first part of a valid pair. + if !(MIN_LOW..=MAX_LOW).contains(&low) { + unsafe { + // Safety: state is valid position because we got it from cur_pos() + self.input_mut().reset_to(before_second); + } + return Ok(Some(UnicodeEscape::LoneSurrogate(high))); + } + + let code_point = pair_to_code_point(high, low); + // SAFETY: `high` and `low` have been checked to be in ranges which always yield + // a `code_point` which is a valid `char` + let ch = unsafe { char::from_u32_unchecked(code_point) }; + Ok(Some(UnicodeEscape::SurrogatePair(ch))) } - fn read_unicode_escape(&mut self) -> LexResult> { + fn read_unicode_escape(&mut self) -> LexResult { debug_assert_eq!(self.cur(), Some('u')); - let mut chars = Vec::with_capacity(4); let mut is_curly = false; self.bump(); // 'u' @@ -1169,7 +1268,11 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { match c { Some(c) => { - chars.push(c.into()); + if is_curly && !self.eat(b'}') { + self.error(state, SyntaxError::InvalidUnicodeEscape)? + } + + Ok(UnicodeEscape::CodePoint(c)) } _ => { unsafe { @@ -1177,44 +1280,26 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { self.input_mut().reset_to(state); } - chars.push(Char::from('\\')); - chars.push(Char::from('u')); - - if is_curly { - chars.push(Char::from('{')); - - for _ in 0..6 { - if let Some(c) = self.input().cur() { - if c == '}' { - break; - } - - self.bump(); - - chars.push(Char::from(c)); - } else { - break; - } - } - - chars.push(Char::from('}')); - } else { - for _ in 0..4 { - if let Some(c) = self.input().cur() { - self.bump(); + let Some(value) = self.read_unicode_code_unit()? else { + self.error( + state, + SyntaxError::BadCharacterEscapeSequence { + expected: if is_curly { + "1-6 hex characters" + } else { + "4 hex characters" + }, + }, + )? + }; - chars.push(Char::from(c)); - } - } + if is_curly && !self.eat(b'}') { + self.error(state, SyntaxError::InvalidUnicodeEscape)? } - } - } - if is_curly && !self.eat(b'}') { - self.error(state, SyntaxError::InvalidUnicodeEscape)? + Ok(value) + } } - - Ok(chars) } #[cold] @@ -1231,7 +1316,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { fn read_tmpl_token(&mut self, start_of_tpl: BytePos) -> LexResult { let start = self.cur_pos(); - let mut cooked = Ok(String::new()); + let mut cooked = Ok(Wtf8Buf::new()); let mut cooked_slice_start = start; let raw_slice_start = start; @@ -1285,10 +1370,10 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { // got them from `self.input` self.input_slice(cooked_slice_start, last_pos) }; - Ok(self.atom(s)) + Ok(self.wtf8_atom(Wtf8::from_str(s))) } else { consume_cooked!(); - cooked.map(|s| self.atom(s)) + cooked.map(|s| self.wtf8_atom(&*s)) }; let end = self.input().cur_pos(); @@ -1310,10 +1395,10 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { let cooked = if cooked_slice_start == raw_slice_start { let last_pos = self.cur_pos(); let s = unsafe { self.input_slice(cooked_slice_start, last_pos) }; - Ok(self.atom(s)) + Ok(self.wtf8_atom(Wtf8::from_str(s))) } else { consume_cooked!(); - cooked.map(|s| self.atom(s)) + cooked.map(|s| self.wtf8_atom(&*s)) }; let end = self.input().cur_pos(); @@ -1333,7 +1418,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { } if let Ok(ref mut cooked) = cooked { - cooked.push('\n'); + cooked.push_char('\n'); } cooked_slice_start = self.cur_pos(); } @@ -1342,11 +1427,9 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { consume_cooked!(); match self.read_escaped_char(true) { - Ok(Some(chars)) => { + Ok(Some(escaped)) => { if let Ok(ref mut cooked) = cooked { - for c in chars { - cooked.extend(c); - } + cooked.push(escaped); } } Ok(None) => {} @@ -1365,7 +1448,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { /// Read an escaped character for string literal. /// /// In template literal, we should preserve raw string. - fn read_escaped_char(&mut self, in_template: bool) -> LexResult>> { + fn read_escaped_char(&mut self, in_template: bool) -> LexResult> { debug_assert_eq!(self.cur(), Some('\\')); let start = self.cur_pos(); @@ -1403,7 +1486,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { self.bump(); // 'x' match self.read_int_u32::<16>(2)? { - Some(val) => return Ok(Some(vec![Char::from(val)])), + Some(val) => return Ok(CodePoint::from_u32(val)), None => self.error( start, SyntaxError::BadCharacterEscapeSequence { @@ -1415,7 +1498,9 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { // read unicode escape sequences 'u' => match self.read_unicode_escape() { - Ok(chars) => return Ok(Some(chars)), + Ok(value) => { + return Ok(Some(value.into())); + } Err(err) => self.error(start, err.into_kind())?, }, @@ -1427,7 +1512,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { match self.cur() { Some(next) if next.is_digit(8) => c, // \0 is not an octal literal nor decimal literal. - _ => return Ok(Some(vec!['\u{0000}'.into()])), + _ => return Ok(Some(CodePoint::from_char('\u{0000}'))), } } else { c @@ -1454,7 +1539,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { .and_then(|value| value.checked_add(v as u8)); match new_val { Some(val) => val, - None => return Ok(Some(vec![Char::from(value as char)])), + None => return Ok(CodePoint::from_u32(value as u32)), } } else { value * 8 + v as u8 @@ -1462,7 +1547,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { self.bump(); } - _ => return Ok(Some(vec![Char::from(value as u32)])), + _ => return Ok(CodePoint::from_u32(value as u32)), } }}; } @@ -1470,7 +1555,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { one!(false); one!(true); - return Ok(Some(vec![Char::from(value as char)])); + return Ok(CodePoint::from_u32(value as u32)); } _ => c, }; @@ -1480,7 +1565,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { self.input_mut().bump(); } - Ok(Some(vec![c.into()])) + Ok(CodePoint::from_u32(c as u32)) } /// Expects current char to be '/' @@ -1665,23 +1750,29 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { } } - let chars = self.read_unicode_escape()?; + let value = self.read_unicode_escape()?; - if let Some(c) = chars.first() { - let valid = if first { - c.is_ident_start() - } else { - c.is_ident_part() - }; - - if !valid { + match value { + UnicodeEscape::CodePoint(ch) => { + let valid = if first { + ch.is_ident_start() + } else { + ch.is_ident_part() + }; + if !valid { + self.emit_error(start, SyntaxError::InvalidIdentChar); + } + buf.push(ch); + } + UnicodeEscape::SurrogatePair(ch) => { + buf.push(ch); self.emit_error(start, SyntaxError::InvalidIdentChar); } - } - - for c in chars { - buf.extend(c); - } + UnicodeEscape::LoneSurrogate(code_point) => { + buf.push_str(format!("\\u{code_point:04X}").as_str()); + self.emit_error(start, SyntaxError::InvalidIdentChar); + } + }; slice_start = self.cur_pos(); continue; @@ -1964,7 +2055,7 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { let mut slice_start = self.input().cur_pos(); - let mut buf: Option = None; + let mut buf: Option = None; loop { let table = if quote == b'"' { @@ -1988,9 +2079,10 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { let end = self.cur_pos(); let raw = unsafe { self.input_slice(start, end) }; - return Ok(Self::Token::str(self.atom(s), self.atom(raw), self)); + return Ok(Self::Token::str(self.wtf8_atom(Wtf8::from_str(s)), self.atom(raw), self)); }, }; + // dbg!(char::from_u32(fast_path_result as u32)); match fast_path_result { b'"' | b'\'' if fast_path_result == quote => { @@ -2005,10 +2097,10 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { self.input_slice(slice_start, value_end) }; buf.push_str(s); - self.atom(&*buf) + self.wtf8_atom(&**buf) } else { let s = unsafe { self.input_slice(slice_start, value_end) }; - self.atom(s) + self.wtf8_atom(Wtf8::from_str(s)) }; unsafe { @@ -2034,15 +2126,13 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { }; if buf.is_none() { - buf = Some(s.to_string()); + buf = Some(Wtf8Buf::from_str(s)); } else { buf.as_mut().unwrap().push_str(s); } - if let Some(chars) = self.read_escaped_char(false)? { - for c in chars { - buf.as_mut().unwrap().extend(c); - } + if let Some(escaped) = self.read_escaped_char(false)? { + buf.as_mut().unwrap().push(escaped); } slice_start = self.cur_pos(); @@ -2065,7 +2155,11 @@ pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { // `self.input` self.input_slice(start, end) }; - return Ok(Self::Token::str(self.atom(s), self.atom(raw), self)); + return Ok(Self::Token::str( + self.wtf8_atom(Wtf8::from_str(s)), + self.atom(raw), + self, + )); } _ => self.bump(), } diff --git a/crates/swc_ecma_lexer/src/common/lexer/token.rs b/crates/swc_ecma_lexer/src/common/lexer/token.rs index b068a4a3f8ac..392d68353033 100644 --- a/crates/swc_ecma_lexer/src/common/lexer/token.rs +++ b/crates/swc_ecma_lexer/src/common/lexer/token.rs @@ -1,5 +1,5 @@ use num_bigint::BigInt; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_ecma_ast::{AssignOp, BinaryOp}; use super::LexResult; @@ -154,14 +154,14 @@ pub trait TokenFactory<'a, TokenAndSpan, I: Tokens>: Sized + Parti fn is_jsx_name(&self) -> bool; fn take_jsx_name(self, buffer: &mut Self::Buffer) -> Atom; - fn str(value: Atom, raw: Atom, lexer: &mut Self::Lexer) -> Self; + fn str(value: Wtf8Atom, raw: Atom, lexer: &mut Self::Lexer) -> Self; fn is_str(&self) -> bool; fn is_str_raw_content(&self, content: &str, buffer: &Self::Buffer) -> bool; - fn take_str(self, buffer: &mut Self::Buffer) -> (Atom, Atom); + fn take_str(self, buffer: &mut Self::Buffer) -> (Wtf8Atom, Atom); - fn template(cooked: LexResult, raw: Atom, lexer: &mut Self::Lexer) -> Self; + fn template(cooked: LexResult, raw: Atom, lexer: &mut Self::Lexer) -> Self; fn is_template(&self) -> bool; - fn take_template(self, buffer: &mut Self::Buffer) -> (LexResult, Atom); + fn take_template(self, buffer: &mut Self::Buffer) -> (LexResult, Atom); fn jsx_text(value: Atom, raw: Atom, lexer: &mut Self::Lexer) -> Self; fn is_jsx_text(&self) -> bool; diff --git a/crates/swc_ecma_lexer/src/common/parser/buffer.rs b/crates/swc_ecma_lexer/src/common/parser/buffer.rs index b681f53995d3..16b8f0f513bb 100644 --- a/crates/swc_ecma_lexer/src/common/parser/buffer.rs +++ b/crates/swc_ecma_lexer/src/common/parser/buffer.rs @@ -1,4 +1,4 @@ -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::{BytePos, Span}; use swc_ecma_ast::EsVersion; @@ -55,10 +55,10 @@ pub trait Buffer<'a> { fn bump(&mut self); fn expect_word_token_and_bump(&mut self) -> Atom; fn expect_number_token_and_bump(&mut self) -> (f64, Atom); - fn expect_string_token_and_bump(&mut self) -> (Atom, Atom); + fn expect_string_token_and_bump(&mut self) -> (Wtf8Atom, Atom); fn expect_bigint_token_and_bump(&mut self) -> (Box, Atom); fn expect_regex_token_and_bump(&mut self) -> (Atom, Atom); - fn expect_template_token_and_bump(&mut self) -> (LexResult, Atom); + fn expect_template_token_and_bump(&mut self) -> (LexResult, Atom); fn expect_error_token_and_bump(&mut self) -> crate::error::Error; fn expect_jsx_name_token_and_bump(&mut self) -> Atom; fn expect_jsx_text_token_and_bump(&mut self) -> (Atom, Atom); diff --git a/crates/swc_ecma_lexer/src/common/parser/module_item.rs b/crates/swc_ecma_lexer/src/common/parser/module_item.rs index cd94241a5c5d..ad8e68f7ea96 100644 --- a/crates/swc_ecma_lexer/src/common/parser/module_item.rs +++ b/crates/swc_ecma_lexer/src/common/parser/module_item.rs @@ -327,7 +327,7 @@ fn parse_import_specifier<'a, P: Parser<'a>>( syntax_error!( p, orig_str.span, - SyntaxError::ImportBindingIsString(orig_str.value) + SyntaxError::ImportBindingIsString(orig_str.value.to_string_lossy().into()) ) } } @@ -687,7 +687,7 @@ fn parse_export<'a, P: Parser<'a>>( ExportSpecifier::Namespace(namespace) => { let export_name = match &namespace.name { ModuleExportName::Ident(i) => i.sym.clone(), - ModuleExportName::Str(s) => s.value.clone(), + ModuleExportName::Str(s) => s.value.to_string_lossy().into(), #[cfg(swc_ast_unknown)] _ => unreachable!(), }; diff --git a/crates/swc_ecma_lexer/src/common/parser/typescript.rs b/crates/swc_ecma_lexer/src/common/parser/typescript.rs index 67871ef284d3..03220ac0b10f 100644 --- a/crates/swc_ecma_lexer/src/common/parser/typescript.rs +++ b/crates/swc_ecma_lexer/src/common/parser/typescript.rs @@ -1,7 +1,7 @@ use std::{fmt::Write, mem}; use either::Either; -use swc_atoms::{atom, Atom}; +use swc_atoms::{atom, Atom, Wtf8Atom}; use swc_common::{BytePos, Span, Spanned}; use swc_ecma_ast::*; @@ -2233,7 +2233,7 @@ fn parse_ts_import_type<'a, P: Parser<'a>>(p: &mut P) -> PResult { p.emit_err(arg_span, SyntaxError::TS1141); Str { span: arg_span, - value: atom!(""), + value: Wtf8Atom::default(), raw: Some(atom!("\"\"")), } }; diff --git a/crates/swc_ecma_lexer/src/common/parser/util.rs b/crates/swc_ecma_lexer/src/common/parser/util.rs index ea2a02856f74..e48c6dcbcdac 100644 --- a/crates/swc_ecma_lexer/src/common/parser/util.rs +++ b/crates/swc_ecma_lexer/src/common/parser/util.rs @@ -37,16 +37,13 @@ pub fn has_use_strict(block: &BlockStmt) -> Option { } pub fn is_constructor(key: &Key) -> bool { - matches!( - &key, - Key::Public(PropName::Ident(IdentName { - sym: constructor, - .. - })) | Key::Public(PropName::Str(Str { - value: constructor, - .. - })) if atom!("constructor").eq(constructor) - ) + if let Key::Public(PropName::Ident(IdentName { sym, .. })) = key { + sym.eq("constructor") + } else if let Key::Public(PropName::Str(Str { value, .. })) = key { + value.eq("constructor") + } else { + false + } } pub fn get_qualified_jsx_name(name: &JSXElementName) -> Atom { diff --git a/crates/swc_ecma_lexer/src/input.rs b/crates/swc_ecma_lexer/src/input.rs index 42c650939d8a..a7878a3b7992 100644 --- a/crates/swc_ecma_lexer/src/input.rs +++ b/crates/swc_ecma_lexer/src/input.rs @@ -449,7 +449,7 @@ impl<'a, I: Tokens> crate::common::parser::buffer::Buffer<'a> for t.take_num(self) } - fn expect_string_token_and_bump(&mut self) -> (swc_atoms::Atom, swc_atoms::Atom) { + fn expect_string_token_and_bump(&mut self) -> (swc_atoms::Wtf8Atom, swc_atoms::Atom) { let t = self.bump(); t.take_str(self) } @@ -467,7 +467,7 @@ impl<'a, I: Tokens> crate::common::parser::buffer::Buffer<'a> for fn expect_template_token_and_bump( &mut self, ) -> ( - crate::common::lexer::LexResult, + crate::common::lexer::LexResult, swc_atoms::Atom, ) { let t = self.bump(); diff --git a/crates/swc_ecma_lexer/src/lexer/mod.rs b/crates/swc_ecma_lexer/src/lexer/mod.rs index 5833e87f7986..1f245fc57a0c 100644 --- a/crates/swc_ecma_lexer/src/lexer/mod.rs +++ b/crates/swc_ecma_lexer/src/lexer/mod.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, char, iter::FusedIterator, rc::Rc}; -use swc_atoms::AtomStoreCell; +use swc_atoms::{wtf8::Wtf8, AtomStoreCell}; use swc_common::{ comments::Comments, input::{Input, StringInput}, @@ -113,6 +113,11 @@ impl<'a> crate::common::lexer::Lexer<'a, TokenAndSpan> for Lexer<'a> { fn atom<'b>(&self, s: impl Into>) -> swc_atoms::Atom { self.atoms.atom(s) } + + #[inline(always)] + fn wtf8_atom<'b>(&self, s: impl Into>) -> swc_atoms::Wtf8Atom { + self.atoms.wtf8_atom(s) + } } impl<'a> Lexer<'a> { diff --git a/crates/swc_ecma_lexer/src/lexer/tests.rs b/crates/swc_ecma_lexer/src/lexer/tests.rs index a2df8f425bd5..fed2f63ee033 100644 --- a/crates/swc_ecma_lexer/src/lexer/tests.rs +++ b/crates/swc_ecma_lexer/src/lexer/tests.rs @@ -2,7 +2,11 @@ extern crate test; use std::{ops::Range, str}; -use swc_atoms::{atom, Atom}; +use swc_atoms::{ + atom, + wtf8::{CodePoint, Wtf8Buf}, + Atom, +}; use swc_common::{BytePos, Span}; use swc_ecma_ast::{AssignOp, AssignOp::*}; use test::{black_box, Bencher}; @@ -199,7 +203,7 @@ fn test262_lexer_error_0002() { lex(Syntax::default(), r"'use\x20strict';"), vec![ Token::Str { - value: atom!("use strict"), + value: atom!("use strict").into(), raw: atom!("'use\\x20strict'"), } .span(0..15) @@ -252,7 +256,7 @@ multiline`" vec![ tok!('`'), Token::Template { - cooked: Ok(atom!("this\nis\nmultiline")), + cooked: Ok(atom!("this\nis\nmultiline").into()), raw: atom!("this\nis\nmultiline"), }, tok!('`'), @@ -341,7 +345,7 @@ fn str_escape() { assert_eq!( lex_tokens(Syntax::default(), r"'\n'"), vec![Token::Str { - value: atom!("\n"), + value: atom!("\n").into(), raw: atom!("'\\n'"), }] ); @@ -352,7 +356,7 @@ fn str_escape_2() { assert_eq!( lex_tokens(Syntax::default(), r"'\\n'"), vec![Token::Str { - value: atom!("\\n"), + value: atom!("\\n").into(), raw: atom!("'\\\\n'"), }] ); @@ -363,7 +367,7 @@ fn str_escape_3() { assert_eq!( lex_tokens(Syntax::default(), r"'\x00'"), vec![Token::Str { - value: atom!("\x00"), + value: atom!("\x00").into(), raw: atom!("'\\x00'"), }] ); @@ -374,7 +378,7 @@ fn str_escape_hex() { assert_eq!( lex(Syntax::default(), r"'\x61'"), vec![Token::Str { - value: atom!("a"), + value: atom!("a").into(), raw: atom!("'\\x61'"), } .span(0..6) @@ -387,7 +391,7 @@ fn str_escape_octal() { assert_eq!( lex(Syntax::default(), r"'Hello\012World'"), vec![Token::Str { - value: atom!("Hello\nWorld"), + value: atom!("Hello\nWorld").into(), raw: atom!("'Hello\\012World'"), } .span(0..16) @@ -400,7 +404,7 @@ fn str_escape_unicode_long() { assert_eq!( lex(Syntax::default(), r"'\u{00000000034}'"), vec![Token::Str { - value: atom!("4"), + value: atom!("4").into(), raw: atom!("'\\u{00000000034}'"), } .span(0..17) @@ -757,133 +761,133 @@ fn str_lit() { assert_eq!( lex_tokens(Syntax::default(), "'abcde'"), vec![Token::Str { - value: atom!("abcde"), + value: atom!("abcde").into(), raw: atom!("'abcde'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "\"abcde\""), vec![Token::Str { - value: atom!("abcde"), + value: atom!("abcde").into(), raw: atom!("\"abcde\""), }], ); assert_eq!( lex_tokens(Syntax::default(), "'русский'"), vec![Token::Str { - value: atom!("русский"), + value: atom!("русский").into(), raw: atom!("'русский'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\x32'"), vec![Token::Str { - value: atom!("2"), + value: atom!("2").into(), raw: atom!("'\\x32'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\u1111'"), vec![Token::Str { - value: atom!("ᄑ"), + value: atom!("ᄑ").into(), raw: atom!("'\\u1111'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\u{1111}'"), vec![Token::Str { - value: atom!("ᄑ"), + value: atom!("ᄑ").into(), raw: atom!("'\\u{1111}'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\t'"), vec![Token::Str { - value: atom!("\t"), + value: atom!("\t").into(), raw: atom!("'\t'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\n'"), vec![Token::Str { - value: atom!("\n"), + value: atom!("\n").into(), raw: atom!("'\\n'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\\nabc'"), vec![Token::Str { - value: atom!("abc"), + value: atom!("abc").into(), raw: atom!("'\\\nabc'"), }] ); assert_eq!( lex_tokens(Syntax::default(), "''"), vec![Token::Str { - value: atom!(""), + value: atom!("").into(), raw: atom!("''"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\''"), vec![Token::Str { - value: atom!("'"), + value: atom!("'").into(), raw: atom!("'\\''"), }], ); assert_eq!( lex_tokens(Syntax::default(), "\"\""), vec![Token::Str { - value: atom!(""), + value: atom!("").into(), raw: atom!("\"\""), }], ); assert_eq!( lex_tokens(Syntax::default(), "\"\\\"\""), vec![Token::Str { - value: atom!("\""), + value: atom!("\"").into(), raw: atom!("\"\\\"\""), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\0'"), vec![Token::Str { - value: atom!("\u{0000}"), + value: atom!("\u{0000}").into(), raw: atom!("'\\0'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\n'"), vec![Token::Str { - value: atom!("\n"), + value: atom!("\n").into(), raw: atom!("'\\n'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\r'"), vec![Token::Str { - value: atom!("\r"), + value: atom!("\r").into(), raw: atom!("'\\r'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\012'"), vec![Token::Str { - value: atom!("\n"), + value: atom!("\n").into(), raw: atom!("'\\012'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\07'"), vec![Token::Str { - value: atom!("\u{0007}"), + value: atom!("\u{0007}").into(), raw: atom!("'\\07'"), }], ); assert_eq!( lex_tokens(Syntax::default(), "'\\08'"), vec![Token::Str { - value: atom!("\u{0000}8"), + value: atom!("\u{0000}8").into(), raw: atom!("'\\08'"), }], ); @@ -897,7 +901,7 @@ fn tpl_empty() { tok!('`'), Template { raw: atom!(""), - cooked: Ok(atom!("")), + cooked: Ok(atom!("").into()), }, tok!('`') ] @@ -912,14 +916,14 @@ fn tpl() { tok!('`'), Template { raw: atom!(""), - cooked: Ok(atom!("")), + cooked: Ok(atom!("").into()), }, tok!("${"), Word(Word::Ident("a".into())), tok!('}'), Template { raw: atom!(""), - cooked: Ok(atom!("")), + cooked: Ok(atom!("").into()), }, tok!('`'), ] @@ -931,14 +935,28 @@ fn tpl() { tok!('`'), Template { raw: atom!("te\\nst"), - cooked: Ok(atom!("te\nst")), + cooked: Ok(atom!("te\nst").into()), }, tok!("${"), Word(Word::Ident("a".into())), tok!('}'), Template { raw: atom!("test"), - cooked: Ok(atom!("test")), + cooked: Ok(atom!("test").into()), + }, + tok!('`'), + ] + ); + + let mut buf = Wtf8Buf::new(); + buf.push(unsafe { CodePoint::from_u32_unchecked(0xd800) }); + assert_eq!( + lex_tokens(Syntax::default(), r"`\uD800`"), + vec![ + tok!('`'), + Template { + raw: atom!("\\uD800"), + cooked: Ok(buf.into()), }, tok!('`'), ] @@ -1109,14 +1127,14 @@ fn issue_191() { tok!('`'), Token::Template { raw: atom!(""), - cooked: Ok(atom!("")), + cooked: Ok(atom!("").into()), }, tok!("${"), Token::Word(Word::Ident("foo".into())), tok!('}'), Token::Template { raw: atom!(""), - cooked: Ok(atom!("")), + cooked: Ok(atom!("").into()), }, tok!('`') ] @@ -1131,7 +1149,7 @@ fn issue_5722() { tok!('`'), Template { raw: atom!(""), - cooked: Ok(atom!("")), + cooked: Ok(atom!("").into()), }, tok!("${"), tok!('{'), @@ -1145,7 +1163,7 @@ fn issue_5722() { tok!('}'), Template { raw: atom!(""), - cooked: Ok(atom!("")), + cooked: Ok(atom!("").into()), }, tok!('`'), ] @@ -1196,7 +1214,7 @@ fn issue_299_01() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("\\ "), + value: atom!("\\ ").into(), raw: atom!("'\\ '"), }, Token::JSXTagEnd, @@ -1233,7 +1251,7 @@ fn issue_299_02() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("\\\\"), + value: atom!("\\\\").into(), raw: atom!("'\\\\'"), }, Token::JSXTagEnd, @@ -1270,7 +1288,7 @@ fn jsx_string_1() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("abc"), + value: atom!("abc").into(), raw: atom!("'abc'"), }, Token::JSXTagEnd, @@ -1307,7 +1325,7 @@ fn jsx_string_2() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("abc"), + value: atom!("abc").into(), raw: atom!("\"abc\""), }, Token::JSXTagEnd, @@ -1344,7 +1362,7 @@ fn jsx_string_3() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("\n"), + value: atom!("\n").into(), raw: atom!("'\n'"), }, Token::JSXTagEnd, @@ -1381,7 +1399,7 @@ fn jsx_string_4() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("³"), + value: atom!("³").into(), raw: atom!("'³'"), }, Token::JSXTagEnd, @@ -1418,7 +1436,7 @@ fn jsx_string_5() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("*"), + value: atom!("*").into(), raw: atom!("'*'"), }, Token::JSXTagEnd, @@ -1455,7 +1473,7 @@ fn jsx_string_6() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("#"), + value: atom!("#").into(), raw: atom!("'#'"), }, Token::JSXTagEnd, @@ -1492,7 +1510,7 @@ fn jsx_string_7() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("&"), + value: atom!("&").into(), raw: atom!("'&'"), }, Token::JSXTagEnd, @@ -1529,7 +1547,7 @@ fn jsx_string_8() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("&;"), + value: atom!("&;").into(), raw: atom!("'&;'"), }, Token::JSXTagEnd, @@ -1566,7 +1584,7 @@ fn jsx_string_9() { Token::JSXName { name: atom!("num") }, tok!('='), Token::Str { - value: atom!("&&"), + value: atom!("&&").into(), raw: atom!("'&&'"), }, Token::JSXTagEnd, @@ -1590,7 +1608,7 @@ fn issue_316() { assert_eq!( lex_tokens(Default::default(), "'Hi\\r\\n..'"), vec![Token::Str { - value: atom!("Hi\r\n.."), + value: atom!("Hi\r\n..").into(), raw: atom!("'Hi\\r\\n..'"), }] ); @@ -1602,7 +1620,7 @@ fn issue_401() { lex_tokens(Default::default(), "'17' as const"), vec![ Token::Str { - value: atom!("17"), + value: atom!("17").into(), raw: atom!("'17'"), }, tok!("as"), @@ -1652,7 +1670,7 @@ fn issue_915_1() { Word(Word::Ident("encode".into())), LParen, Token::Str { - value: atom!("\r\n"), + value: atom!("\r\n").into(), raw: atom!("\"\\r\\n\""), }, RParen @@ -1885,7 +1903,7 @@ fn issue_2853_1_js() { Word(Word::Ident("a".into())), Token::AssignOp(AssignOp::Assign), Token::Str { - value: atom!("\u{0000}a"), + value: atom!("\u{0000}a").into(), raw: atom!("\"\\0a\""), } ], @@ -1907,7 +1925,7 @@ fn issue_2853_2_ts() { Word(Word::Ident("a".into())), Token::AssignOp(AssignOp::Assign), Token::Str { - value: atom!("\u{0000}a"), + value: atom!("\u{0000}a").into(), raw: atom!("\"\\0a\""), } ], @@ -1929,7 +1947,7 @@ fn issue_2853_3_js() { Word(Word::Ident("a".into())), Token::AssignOp(AssignOp::Assign), Token::Str { - value: atom!("\u{0000}a"), + value: atom!("\u{0000}a").into(), raw: atom!("\"\u{0000}a\""), } ], @@ -1951,7 +1969,7 @@ fn issue_2853_4_ts() { Word(Word::Ident("a".into())), Token::AssignOp(AssignOp::Assign), Token::Str { - value: atom!("\u{0000}a"), + value: atom!("\u{0000}a").into(), raw: atom!("\"\u{0000}a\""), } ], @@ -1976,7 +1994,7 @@ fn issue_2853_5_jsx() { Word(Word::Ident("a".into())), Token::AssignOp(AssignOp::Assign), Token::Str { - value: atom!("\u{0000}a"), + value: atom!("\u{0000}a").into(), raw: atom!("\"\\0a\""), } ] @@ -2001,7 +2019,7 @@ fn issue_2853_6_tsx() { Word(Word::Ident("a".into())), Token::AssignOp(AssignOp::Assign), Token::Str { - value: atom!("\u{0000}a"), + value: atom!("\u{0000}a").into(), raw: atom!("\"\\0a\""), } ] @@ -2026,7 +2044,7 @@ fn issue_2853_7_jsx() { Word(Word::Ident("a".into())), Token::AssignOp(AssignOp::Assign), Token::Str { - value: atom!("\u{0000}a"), + value: atom!("\u{0000}a").into(), raw: atom!("\"\u{0000}a\""), } ] @@ -2051,7 +2069,7 @@ fn issue_2853_8_tsx() { Word(Word::Ident("a".into())), Token::AssignOp(AssignOp::Assign), Token::Str { - value: atom!("\u{0000}a"), + value: atom!("\u{0000}a").into(), raw: atom!("\"\u{0000}a\""), } ] diff --git a/crates/swc_ecma_lexer/src/token.rs b/crates/swc_ecma_lexer/src/token.rs index f560c95a9ed8..610342991e09 100644 --- a/crates/swc_ecma_lexer/src/token.rs +++ b/crates/swc_ecma_lexer/src/token.rs @@ -7,7 +7,7 @@ use std::{ }; use num_bigint::BigInt as BigIntValue; -use swc_atoms::{atom, Atom, AtomStore}; +use swc_atoms::{atom, Atom, AtomStore, Wtf8Atom}; use swc_common::{Span, Spanned}; pub(crate) use swc_ecma_ast::{AssignOp, BinaryOp}; @@ -436,7 +436,7 @@ pub enum Token { BackQuote, Template { raw: Atom, - cooked: LexResult, + cooked: LexResult, }, /// ':' Colon, @@ -459,7 +459,7 @@ pub enum Token { /// String literal. Span of this token contains quote. Str { - value: Atom, + value: Wtf8Atom, raw: Atom, }, @@ -654,12 +654,12 @@ impl<'a, I: Tokens> crate::common::lexer::token::TokenFactory<'a, } #[inline(always)] - fn str(value: Atom, raw: Atom, _: &mut crate::Lexer<'a>) -> Self { + fn str(value: Wtf8Atom, raw: Atom, _: &mut crate::Lexer<'a>) -> Self { Self::Str { value, raw } } #[inline(always)] - fn template(cooked: LexResult, raw: Atom, _: &mut crate::Lexer<'a>) -> Self { + fn template(cooked: LexResult, raw: Atom, _: &mut crate::Lexer<'a>) -> Self { Self::Template { cooked, raw } } @@ -733,7 +733,7 @@ impl<'a, I: Tokens> crate::common::lexer::token::TokenFactory<'a, } #[inline(always)] - fn take_str(self, _: &mut Self::Buffer) -> (Atom, Atom) { + fn take_str(self, _: &mut Self::Buffer) -> (Wtf8Atom, Atom) { match self { Self::Str { value, raw } => (value, raw), _ => unreachable!(), @@ -824,7 +824,7 @@ impl<'a, I: Tokens> crate::common::lexer::token::TokenFactory<'a, } #[inline(always)] - fn take_template(self, _: &mut Self::Buffer) -> (LexResult, Atom) { + fn take_template(self, _: &mut Self::Buffer) -> (LexResult, Atom) { match self { Self::Template { cooked, raw } => (cooked, raw), _ => unreachable!(), @@ -1647,7 +1647,7 @@ impl Debug for Token { PlusPlus => write!(f, "++")?, MinusMinus => write!(f, "--")?, Tilde => write!(f, "~")?, - Str { value, raw } => write!(f, "string literal ({value}, {raw})")?, + Str { value, raw } => write!(f, "string literal ({value:?}, {raw})")?, Regex(exp, flags) => write!(f, "regexp literal ({exp}, {flags})")?, Num { value, raw, .. } => write!(f, "numeric literal ({value}, {raw})")?, BigInt { value, raw } => write!(f, "bigint literal ({value}, {raw})")?, diff --git a/crates/swc_ecma_lints/src/rules/dot_notation.rs b/crates/swc_ecma_lints/src/rules/dot_notation.rs index e9794a9a993f..b065f3d2d0a0 100644 --- a/crates/swc_ecma_lints/src/rules/dot_notation.rs +++ b/crates/swc_ecma_lints/src/rules/dot_notation.rs @@ -105,7 +105,7 @@ impl Visit for DotNotation { Expr::Lit(Lit::Str(lit_str)) => { let quote_type = resolve_string_quote_type(lit_str).unwrap(); - self.check(prop.span, quote_type, &lit_str.value); + self.check(prop.span, quote_type, &lit_str.value.to_string_lossy()); } Expr::Member(member) => { member.visit_children_with(self); diff --git a/crates/swc_ecma_lints/src/rules/no_alert.rs b/crates/swc_ecma_lints/src/rules/no_alert.rs index 74d023426356..d060c73e77ac 100644 --- a/crates/swc_ecma_lints/src/rules/no_alert.rs +++ b/crates/swc_ecma_lints/src/rules/no_alert.rs @@ -122,7 +122,7 @@ impl NoAlert { } MemberProp::Computed(comp) => { if let Expr::Lit(Lit::Str(Str { value, .. })) = comp.expr.as_ref() { - self.prop = Some(value.clone()); + self.prop = value.as_atom().cloned(); } } _ => {} diff --git a/crates/swc_ecma_lints/src/rules/no_console.rs b/crates/swc_ecma_lints/src/rules/no_console.rs index b87d913fa747..f076ce5e5233 100644 --- a/crates/swc_ecma_lints/src/rules/no_console.rs +++ b/crates/swc_ecma_lints/src/rules/no_console.rs @@ -80,7 +80,8 @@ impl Visit for NoConsole { } MemberProp::Computed(ComputedPropName { expr, .. }) => { if let Expr::Lit(Lit::Str(Str { value, .. })) = expr.as_ref() { - self.check(member.span, ident, value); + let method_atom = value.to_atom_lossy(); + self.check(member.span, ident, method_atom.as_ref()); } else { expr.visit_with(self); } diff --git a/crates/swc_ecma_lints/src/rules/no_prototype_builtins.rs b/crates/swc_ecma_lints/src/rules/no_prototype_builtins.rs index b952f1bef645..cc63cf3a1f87 100644 --- a/crates/swc_ecma_lints/src/rules/no_prototype_builtins.rs +++ b/crates/swc_ecma_lints/src/rules/no_prototype_builtins.rs @@ -99,7 +99,7 @@ impl NoPrototypeBuiltins { self.extract_path(exprs.last().unwrap().as_ref()); } Expr::Lit(Lit::Str(lit_str)) => { - self.extend_chain(lit_str.span, lit_str.value.clone()); + self.extend_chain(lit_str.span, lit_str.value.to_atom_lossy().into_owned()); } Expr::Tpl(tpl) => { if tpl.exprs.is_empty() && tpl.quasis.len() == 1 { diff --git a/crates/swc_ecma_lints/src/rules/prefer_regex_literals.rs b/crates/swc_ecma_lints/src/rules/prefer_regex_literals.rs index 1f88cb18456e..6abfe77cc9aa 100644 --- a/crates/swc_ecma_lints/src/rules/prefer_regex_literals.rs +++ b/crates/swc_ecma_lints/src/rules/prefer_regex_literals.rs @@ -213,7 +213,7 @@ impl Visit for PreferRegexLiterals { } MemberProp::Computed(comp) => { if let Expr::Lit(Lit::Str(Str { value, .. })) = comp.expr.as_ref() { - self.check(value); + self.check(&value.to_string_lossy()); } } _ => {} diff --git a/crates/swc_ecma_lints/src/rules/quotes.rs b/crates/swc_ecma_lints/src/rules/quotes.rs index 54e559dc2b55..9199745ee50a 100644 --- a/crates/swc_ecma_lints/src/rules/quotes.rs +++ b/crates/swc_ecma_lints/src/rules/quotes.rs @@ -87,14 +87,14 @@ impl Quotes { match (&self.prefer, &found_quote_type) { (QuotesType::Double, QuotesType::Single) => { - if self.avoid_escape && self.is_mirroring_escape(value) { + if self.avoid_escape && self.is_mirroring_escape(&value.to_string_lossy()) { return; } self.emit_report(*span); } (QuotesType::Single, QuotesType::Double) => { - if self.avoid_escape && self.is_mirroring_escape(value) { + if self.avoid_escape && self.is_mirroring_escape(&value.to_string_lossy()) { return; } @@ -105,7 +105,7 @@ impl Quotes { return; } - if self.avoid_escape && self.is_mirroring_escape(value) { + if self.avoid_escape && self.is_mirroring_escape(&value.to_string_lossy()) { return; } @@ -153,9 +153,9 @@ impl Visit for Quotes { fn visit_expr_stmt(&mut self, expr_stmt: &ExprStmt) { if let Expr::Lit(Lit::Str(Str { value, .. })) = expr_stmt.expr.as_ref() { - let value: &str = value; + let value = value.to_string_lossy(); - if DIRECTIVES.contains(&value) { + if DIRECTIVES.contains(&&*value) { return; } } diff --git a/crates/swc_ecma_lints/src/rules/radix.rs b/crates/swc_ecma_lints/src/rules/radix.rs index 57931f85b8b4..7ffa01ba891c 100644 --- a/crates/swc_ecma_lints/src/rules/radix.rs +++ b/crates/swc_ecma_lints/src/rules/radix.rs @@ -190,7 +190,7 @@ impl Radix { MemberProp::Ident(IdentName { sym, .. }) => Some(sym.clone()), MemberProp::Computed(ComputedPropName { expr, .. }) => { if let Expr::Lit(Lit::Str(Str { value, .. })) = expr.as_ref() { - return Some(value.clone()); + return value.as_atom().cloned(); } None diff --git a/crates/swc_ecma_lints/src/rules/utils.rs b/crates/swc_ecma_lints/src/rules/utils.rs index 0a0cb0dfe744..189a31757c47 100644 --- a/crates/swc_ecma_lints/src/rules/utils.rs +++ b/crates/swc_ecma_lints/src/rules/utils.rs @@ -52,7 +52,7 @@ pub enum ArgValue { pub fn extract_arg_val(unresolved_ctxt: SyntaxContext, expr: &Expr) -> ArgValue { match expr { Expr::Ident(_) => ArgValue::Ident, - Expr::Lit(Lit::Str(Str { value, .. })) => ArgValue::Str(Atom::new(&**value)), + Expr::Lit(Lit::Str(Str { value, .. })) => ArgValue::Str(Atom::new(value.to_string_lossy())), Expr::Lit(Lit::Num(Number { value, .. })) => ArgValue::Number(*value), Expr::Lit(Lit::Regex(Regex { exp, flags, .. })) => ArgValue::RegExp { exp: exp.clone(), diff --git a/crates/swc_ecma_lints/src/rules/valid_typeof.rs b/crates/swc_ecma_lints/src/rules/valid_typeof.rs index 1188d4389a93..67c0fa333921 100644 --- a/crates/swc_ecma_lints/src/rules/valid_typeof.rs +++ b/crates/swc_ecma_lints/src/rules/valid_typeof.rs @@ -80,7 +80,7 @@ impl Visit for ValidTypeof { }), Expr::Lit(Lit::Str(Str { value, .. })), ) => { - self.check(bin_expr.span, value); + self.check(bin_expr.span, &value.to_string_lossy()); } // case "type" === typeof x ( @@ -89,7 +89,7 @@ impl Visit for ValidTypeof { op: op!("typeof"), .. }), ) => { - self.check(bin_expr.span, value); + self.check(bin_expr.span, &value.to_string_lossy()); } // case typeof x === typeof y ( diff --git a/crates/swc_ecma_minifier/fuzz/Cargo.lock b/crates/swc_ecma_minifier/fuzz/Cargo.lock new file mode 100644 index 000000000000..453507c63ab8 --- /dev/null +++ b/crates/swc_ecma_minifier/fuzz/Cargo.lock @@ -0,0 +1,2144 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "ast_node" +version = "3.0.4" +dependencies = [ + "quote", + "swc_macros_common", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "better_scoped_tls" +version = "1.0.1" +dependencies = [ + "scoped-tls", +] + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytes-str" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3" +dependencies = [ + "bytes", + "serde", +] + +[[package]] +name = "camino" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "from_variant" +version = "2.0.2" +dependencies = [ + "swc_macros_common", + "syn", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hstr" +version = "2.1.0" +dependencies = [ + "hashbrown 0.14.5", + "new_debug_unreachable", + "once_cell", + "rustc-hash", + "triomphe", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if_chain" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb" + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "is-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "owo-colors", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "par-core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96cbd21255b7fb29a5d51ef38a779b517a91abd59e2756c039583f43ef4c90f" +dependencies = [ + "once_cell", +] + +[[package]] +name = "par-iter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eae0176a010bb94b9a67f0eb9da0fd31410817d58850649c54f485124c9a71a" +dependencies = [ + "either", + "par-core", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e66fcd288453b748497d8fb18bccc83a16b0518e3906d4b8df0a8d42d93dbb1c" +dependencies = [ + "cc", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "radix_fmt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "regress" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145bb27393fe455dd64d6cbc8d059adfa392590a45eadf079c01b11857e7b010" +dependencies = [ + "hashbrown 0.15.5", + "memchr", +] + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "ryu-js" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_enum" +version = "1.0.2" +dependencies = [ + "quote", + "swc_macros_common", + "syn", +] + +[[package]] +name = "swc_allocator" +version = "4.0.1" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.14.5", + "rustc-hash", +] + +[[package]] +name = "swc_atoms" +version = "7.0.0" +dependencies = [ + "hstr", + "once_cell", + "serde", +] + +[[package]] +name = "swc_common" +version = "14.0.4" +dependencies = [ + "anyhow", + "arbitrary", + "ast_node", + "better_scoped_tls", + "bytes-str", + "either", + "from_variant", + "new_debug_unreachable", + "num-bigint", + "once_cell", + "parking_lot", + "rustc-hash", + "serde", + "siphasher 0.3.11", + "swc_atoms", + "swc_eq_ignore_macros", + "swc_visit", + "termcolor", + "tracing", + "unicode-width 0.1.14", + "url", +] + +[[package]] +name = "swc_config" +version = "3.1.2" +dependencies = [ + "anyhow", + "bytes-str", + "dashmap", + "globset", + "indexmap", + "once_cell", + "regex", + "regress", + "rustc-hash", + "serde", + "serde_json", + "swc_config_macro", + "swc_sourcemap", +] + +[[package]] +name = "swc_config_macro" +version = "1.0.1" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn", +] + +[[package]] +name = "swc_ecma_ast" +version = "15.0.0" +dependencies = [ + "arbitrary", + "bitflags", + "is-macro", + "num-bigint", + "once_cell", + "phf", + "rustc-hash", + "serde", + "string_enum", + "swc_atoms", + "swc_common", + "swc_visit", + "unicode-id-start", +] + +[[package]] +name = "swc_ecma_codegen" +version = "17.0.2" +dependencies = [ + "ascii", + "compact_str", + "memchr", + "num-bigint", + "once_cell", + "regex", + "rustc-hash", + "ryu-js", + "serde", + "swc_allocator", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen_macros", + "swc_sourcemap", + "tracing", +] + +[[package]] +name = "swc_ecma_codegen_macros" +version = "2.0.2" +dependencies = [ + "proc-macro2", + "swc_macros_common", + "syn", +] + +[[package]] +name = "swc_ecma_lexer" +version = "23.0.2" +dependencies = [ + "bitflags", + "either", + "num-bigint", + "phf", + "rustc-hash", + "seq-macro", + "serde", + "smallvec", + "smartstring", + "stacker", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "tracing", +] + +[[package]] +name = "swc_ecma_minifier" +version = "33.0.0" +dependencies = [ + "arrayvec", + "bitflags", + "indexmap", + "num-bigint", + "num_cpus", + "once_cell", + "par-core", + "par-iter", + "parking_lot", + "phf", + "radix_fmt", + "rustc-hash", + "ryu-js", + "serde", + "serde_json", + "swc_atoms", + "swc_common", + "swc_config", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_optimization", + "swc_ecma_usage_analyzer", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_timer", + "tracing", +] + +[[package]] +name = "swc_ecma_minifier-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_minifier", + "swc_ecma_parser", + "swc_ecma_testing", + "swc_ecma_transforms_base", + "swc_ecma_visit", + "testing", +] + +[[package]] +name = "swc_ecma_parser" +version = "24.0.3" +dependencies = [ + "either", + "num-bigint", + "serde", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_lexer", + "tracing", +] + +[[package]] +name = "swc_ecma_testing" +version = "15.0.0" +dependencies = [ + "anyhow", + "hex", + "sha2", + "testing", + "tracing", +] + +[[package]] +name = "swc_ecma_transforms_base" +version = "27.0.0" +dependencies = [ + "better_scoped_tls", + "indexmap", + "once_cell", + "par-core", + "phf", + "rustc-hash", + "serde", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_utils", + "swc_ecma_visit", + "tracing", +] + +[[package]] +name = "swc_ecma_transforms_optimization" +version = "29.0.0" +dependencies = [ + "bytes-str", + "dashmap", + "indexmap", + "once_cell", + "par-core", + "petgraph", + "rustc-hash", + "serde_json", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", + "tracing", +] + +[[package]] +name = "swc_ecma_usage_analyzer" +version = "22.0.1" +dependencies = [ + "bitflags", + "indexmap", + "rustc-hash", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_timer", + "tracing", +] + +[[package]] +name = "swc_ecma_utils" +version = "21.0.0" +dependencies = [ + "indexmap", + "num_cpus", + "once_cell", + "par-core", + "rustc-hash", + "ryu-js", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_visit", + "tracing", +] + +[[package]] +name = "swc_ecma_visit" +version = "15.0.0" +dependencies = [ + "new_debug_unreachable", + "num-bigint", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_visit", + "tracing", +] + +[[package]] +name = "swc_eq_ignore_macros" +version = "1.0.1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "swc_error_reporters" +version = "16.0.1" +dependencies = [ + "anyhow", + "miette", + "once_cell", + "serde", + "swc_common", +] + +[[package]] +name = "swc_macros_common" +version = "1.0.1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "swc_sourcemap" +version = "9.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de08ef00f816acdd1a58ee8a81c0e1a59eefef2093aefe5611f256fa6b64c4d7" +dependencies = [ + "base64-simd", + "bitvec", + "bytes-str", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash", + "serde", + "serde_json", + "unicode-id-start", + "url", +] + +[[package]] +name = "swc_timer" +version = "1.0.0" +dependencies = [ + "tracing", +] + +[[package]] +name = "swc_visit" +version = "2.0.1" +dependencies = [ + "either", + "new_debug_unreachable", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "testing" +version = "15.0.0" +dependencies = [ + "cargo_metadata", + "difference", + "once_cell", + "pretty_assertions", + "regex", + "rustc-hash", + "serde", + "serde_json", + "swc_common", + "swc_error_reporters", + "testing_macros", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "testing_macros" +version = "1.0.1" +dependencies = [ + "anyhow", + "glob", + "once_cell", + "proc-macro2", + "quote", + "regex", + "relative-path", + "syn", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "triomphe" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" +dependencies = [ + "serde", + "stable_deref_trait", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-id-start" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs b/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs index ec6591154fca..1d48b0615423 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/arguments.rs @@ -21,19 +21,26 @@ impl Optimizer<'_> { Expr::Member(MemberExpr { prop, .. }) => { if let MemberProp::Computed(c) = prop { if let Expr::Lit(Lit::Str(s)) = &mut *c.expr { - if !s.value.starts_with(|c: char| c.is_ascii_alphabetic()) { + let Some(value) = s.value.as_str() else { + return; + }; + + if !value.starts_with(|c: char| c.is_ascii_alphabetic()) { return; } - if !is_valid_prop_ident(&s.value) { + if !is_valid_prop_ident(value) { return; } self.changed = true; report_change!("arguments: Optimizing computed access to arguments"); + + let name = s.take().value; *prop = MemberProp::Ident(IdentName { span: s.span, - sym: s.take().value, + // SAFETY: s.value is guaranteed to be valid UTF-8 sequence from above. + sym: name.try_into_atom().unwrap(), }) } } @@ -42,19 +49,25 @@ impl Optimizer<'_> { Expr::SuperProp(SuperPropExpr { prop, .. }) => { if let SuperProp::Computed(c) = prop { if let Expr::Lit(Lit::Str(s)) = &mut *c.expr { - if !s.value.starts_with(|c: char| c.is_ascii_alphabetic()) { + let Some(value) = s.value.as_str() else { + return; + }; + if !value.starts_with(|c: char| c.is_ascii_alphabetic()) { return; } - if !is_valid_prop_ident(&s.value) { + if !is_valid_prop_ident(value) { return; } self.changed = true; report_change!("arguments: Optimizing computed access to arguments"); + + let name = s.take().value; *prop = SuperProp::Ident(IdentName { span: s.span, - sym: s.take().value, + // SAFETY: s.value is guaranteed to be valid UTF-8 sequence from above. + sym: name.try_into_atom().unwrap(), }) } } @@ -176,6 +189,9 @@ impl VisitMut for ArgReplacer<'_> { Expr::Ident(Ident { sym, .. }) if &**sym == "arguments" => { match &*c.expr { Expr::Lit(Lit::Str(Str { value, .. })) => { + let Some(value) = value.as_str() else { + return; + }; let idx = value.parse::(); let idx = match idx { Ok(v) => v, diff --git a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs index 6764c6245219..98145a084e21 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs @@ -69,7 +69,7 @@ impl Optimizer<'_> { *e = Lit::Str(Str { span: *span, - value: obj.sym.clone(), + value: obj.sym.clone().into(), raw: None, }) .into(); @@ -256,6 +256,9 @@ impl Optimizer<'_> { 0 => {} 1 => { if let Expr::Lit(Lit::Str(exp)) = &*args[0].expr { + let Some(value) = exp.value.as_str() else { + return; + }; self.changed = true; report_change!( "evaluate: Converting RegExpr call into a regexp literal `/{}/`", @@ -264,7 +267,7 @@ impl Optimizer<'_> { *e = Lit::Regex(Regex { span, - exp: exp.value.as_ref().into(), + exp: value.into(), flags: atom!(""), }) .into(); @@ -274,6 +277,13 @@ impl Optimizer<'_> { if let (Expr::Lit(Lit::Str(exp)), Expr::Lit(Lit::Str(flags))) = (&*args[0].expr, &*args[1].expr) { + let Some(value) = exp.value.as_str() else { + return; + }; + let Some(flags) = flags.value.as_str() else { + return; + }; + self.changed = true; report_change!( "evaluate: Converting RegExpr call into a regexp literal `/{}/{}`", @@ -283,8 +293,8 @@ impl Optimizer<'_> { *e = Lit::Regex(Regex { span, - exp: exp.value.as_ref().into(), - flags: flags.value.as_ref().into(), + exp: value.into(), + flags: flags.into(), }) .into(); } @@ -353,7 +363,7 @@ impl Optimizer<'_> { expr: Lit::Str(Str { span: p.span, raw: None, - value: p.sym.clone(), + value: p.sym.clone().into(), }) .into(), })); @@ -365,7 +375,7 @@ impl Optimizer<'_> { expr: Lit::Str(Str { span: key.span, raw: None, - value: key.sym.clone(), + value: key.sym.clone().into(), }) .into(), })); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs index ea52199abfa4..87bd81052933 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs @@ -4,7 +4,7 @@ use std::iter::once; use bitflags::bitflags; use rustc_hash::{FxHashMap, FxHashSet}; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::{pass::Repeated, util::take::Take, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::rename::contains_eval; @@ -250,7 +250,7 @@ struct Vars { lits: FxHashMap>, /// Used for `hoist_props`. - hoisted_props: Box>, + hoisted_props: Box>, /// Literals which are cheap to clone, but not sure if we can inline without /// making output bigger. diff --git a/crates/swc_ecma_minifier/src/compress/optimize/ops.rs b/crates/swc_ecma_minifier/src/compress/optimize/ops.rs index 19897778fbdb..69d0a1b05e27 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/ops.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/ops.rs @@ -1,4 +1,3 @@ -use swc_atoms::atom; use swc_common::{util::take::Take, EqIgnoreSpan, Spanned}; use swc_ecma_ast::*; use swc_ecma_utils::{ExprExt, Type, Value}; @@ -257,7 +256,7 @@ impl Optimizer<'_> { *e = Lit::Str(Str { span: *span, raw: None, - value, + value: value.into(), }) .into(); } @@ -269,7 +268,7 @@ impl Optimizer<'_> { *e = Lit::Str(Str { span: *span, raw: None, - value: atom!("function"), + value: "function".into(), }) .into(); } @@ -280,7 +279,7 @@ impl Optimizer<'_> { *e = Lit::Str(Str { span: *span, raw: None, - value: atom!("object"), + value: "object".into(), }) .into(); } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/props.rs b/crates/swc_ecma_minifier/src/compress/optimize/props.rs index d8a354d2e9cc..506ee7624368 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/props.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/props.rs @@ -1,3 +1,5 @@ +use std::borrow::Borrow; + use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_utils::{contains_this_expr, private_ident, prop_name_eq, ExprExt}; @@ -99,7 +101,7 @@ impl Optimizer<'_> { } } PropName::Ident(i) => { - if let Some(v) = unknown_used_props.get_mut(&i.sym) { + if let Some(v) = unknown_used_props.get_mut(i.sym.borrow()) { *v = 0; } } @@ -107,7 +109,7 @@ impl Optimizer<'_> { } } Prop::Shorthand(p) => { - if let Some(v) = unknown_used_props.get_mut(&p.sym) { + if let Some(v) = unknown_used_props.get_mut(p.sym.borrow()) { *v = 0; } } @@ -155,16 +157,22 @@ impl Optimizer<'_> { let (key, suffix) = match &**prop { Prop::KeyValue(p) => match &p.key { - PropName::Ident(i) => (i.sym.clone(), i.sym.clone()), + PropName::Ident(i) => (i.sym.clone().into(), i.sym.clone()), PropName::Str(s) => ( s.value.clone(), s.value - .replace(|c: char| !Ident::is_valid_continue(c), "$") + .code_points() + .map(|c| { + c.to_char() + .filter(|&c| Ident::is_valid_start(c)) + .unwrap_or('$') + }) + .collect::() .into(), ), _ => unreachable!(), }, - Prop::Shorthand(p) => (p.sym.clone(), p.sym.clone()), + Prop::Shorthand(p) => (p.sym.clone().into(), p.sym.clone()), _ => unreachable!(), }; @@ -203,7 +211,7 @@ impl Optimizer<'_> { }; if let Expr::Ident(obj) = &*member.obj { let sym = match &member.prop { - MemberProp::Ident(i) => &i.sym, + MemberProp::Ident(i) => i.sym.borrow(), MemberProp::Computed(e) => match &*e.expr { Expr::Lit(Lit::Str(s)) => &s.value, _ => return, diff --git a/crates/swc_ecma_minifier/src/compress/optimize/strings.rs b/crates/swc_ecma_minifier/src/compress/optimize/strings.rs index f7272f75459a..65f7380076ae 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/strings.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/strings.rs @@ -1,4 +1,4 @@ -use swc_atoms::{atom, Atom}; +use swc_atoms::{atom, Atom, Wtf8Atom}; use swc_common::{util::take::Take, Spanned, SyntaxContext}; use swc_ecma_ast::*; use swc_ecma_utils::{ExprExt, Value::Known}; @@ -149,7 +149,7 @@ impl Optimizer<'_> { } if let Expr::Lit(Lit::Str(s)) = expr { - if s.value.contains('\n') { + if s.value.contains_char('\n') { *expr = Expr::Tpl(Tpl { span: s.span, exprs: Default::default(), @@ -167,11 +167,28 @@ impl Optimizer<'_> { } } -pub(super) fn convert_str_value_to_tpl_raw(value: &Atom) -> Atom { - value - .replace('\\', "\\\\") - .replace('`', "\\`") - .replace("${", "\\${") - .replace('\r', "\\r") - .into() +pub(super) fn convert_str_value_to_tpl_raw(value: &Wtf8Atom) -> Atom { + let mut result = String::with_capacity(value.len()); + let mut code_points = value.code_points().peekable(); + + while let Some(code_point) = code_points.next() { + if let Some(ch) = code_point.to_char() { + // Valid Unicode character + match ch { + '\\' => result.push_str("\\\\"), + '`' => result.push_str("\\`"), + '\r' => result.push_str("\\r"), + '$' if code_points.peek().and_then(|cp| cp.to_char()) == Some('{') => { + result.push_str("\\${"); + code_points.next(); // Consume the '{' + } + _ => result.push(ch), + } + } else { + // Unparied surrogate, escape as \\uXXXX (two backslashes) + result.push_str(&format!("\\\\u{:04X}", code_point.to_u32())); + } + } + + Atom::new(result) } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs index 0f4fb1eec544..e0d0008e0737 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs @@ -1,3 +1,5 @@ +use std::borrow::Borrow; + use rustc_hash::FxHashSet; use swc_atoms::Atom; use swc_common::{util::take::Take, DUMMY_SP}; @@ -986,22 +988,22 @@ impl Optimizer<'_> { } } PropName::Ident(i) => { - if !can_remove_property(&i.sym) { + if !can_remove_property(i.sym.borrow()) { return None; } - if let Some(v) = unknown_used_props.get_mut(&i.sym) { + if let Some(v) = unknown_used_props.get_mut(i.sym.borrow()) { *v = 0; } } _ => return None, }, Prop::Shorthand(p) => { - if !can_remove_property(&p.sym) { + if !can_remove_property(p.sym.borrow()) { return None; } - if let Some(v) = unknown_used_props.get_mut(&p.sym) { + if let Some(v) = unknown_used_props.get_mut(p.sym.borrow()) { *v = 0; } } @@ -1014,19 +1016,21 @@ impl Optimizer<'_> { return None; } - let should_preserve_property = |sym: &Atom| { - if let "toString" = &**sym { - return true; - } + let should_preserve_property = |sym: &swc_atoms::Wtf8Atom| { + if let Some(s) = sym.as_str() { + if s == "toString" { + return true; + } - if sym.parse::().is_ok() || sym.parse::().is_ok() { - return true; + if s.parse::().is_ok() || s.parse::().is_ok() { + return true; + } } usage.accessed_props.contains_key(sym) || properties_used_via_this.contains(sym) }; let should_preserve = |key: &PropName| match key { - PropName::Ident(k) => should_preserve_property(&k.sym), + PropName::Ident(k) => should_preserve_property(k.sym.borrow()), PropName::Str(k) => should_preserve_property(&k.value), PropName::Num(..) => true, PropName::Computed(..) => true, @@ -1041,7 +1045,7 @@ impl Optimizer<'_> { unreachable!() } PropOrSpread::Prop(p) => match &**p { - Prop::Shorthand(p) => should_preserve_property(&p.sym), + Prop::Shorthand(p) => should_preserve_property(p.sym.borrow()), Prop::KeyValue(p) => should_preserve(&p.key), Prop::Assign(..) => { unreachable!() @@ -1065,8 +1069,9 @@ impl Optimizer<'_> { } } -fn can_remove_property(sym: &str) -> bool { - !matches!(sym, "toString" | "valueOf") +fn can_remove_property(sym: &swc_atoms::Wtf8Atom) -> bool { + sym.as_str() + .map_or(true, |s| !matches!(s, "toString" | "valueOf")) } #[derive(Default)] diff --git a/crates/swc_ecma_minifier/src/compress/optimize/util.rs b/crates/swc_ecma_minifier/src/compress/optimize/util.rs index fc231fc13dc7..408face51b68 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/util.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/util.rs @@ -1,10 +1,11 @@ use std::{ + borrow::Borrow, mem::take, ops::{Deref, DerefMut}, }; use rustc_hash::{FxHashMap, FxHashSet}; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::{util::take::Take, Mark, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::perf::{Parallel, ParallelExt}; @@ -232,7 +233,7 @@ pub(crate) struct Finalizer<'a> { pub lits: &'a FxHashMap>, pub lits_for_cmp: &'a FxHashMap>, pub lits_for_array_access: &'a FxHashMap>, - pub hoisted_props: &'a FxHashMap<(Id, Atom), Ident>, + pub hoisted_props: &'a FxHashMap<(Id, Wtf8Atom), Ident>, pub vars_to_remove: &'a FxHashSet, @@ -358,9 +359,9 @@ impl VisitMut for Finalizer<'_> { Expr::Member(e) => { if let Expr::Ident(obj) = &*e.obj { let sym = match &e.prop { - MemberProp::Ident(i) => &i.sym, + MemberProp::Ident(i) => i.sym.borrow(), MemberProp::Computed(e) => match &*e.expr { - Expr::Ident(ident) => &ident.sym, + Expr::Ident(ident) => ident.sym.borrow(), Expr::Lit(Lit::Str(s)) => &s.value, _ => return, }, @@ -831,7 +832,7 @@ fn prop_name_from_ident(ident: Ident) -> PropName { span: ident.span, expr: Box::new(Expr::Lit(Lit::Str(Str { span: ident.span, - value: ident.sym.clone(), + value: ident.sym.clone().into(), raw: None, }))), }) diff --git a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs index b046b84b8eea..04a70c2ae158 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/evaluate.rs @@ -2,7 +2,11 @@ use radix_fmt::Radix; use swc_atoms::atom; use swc_common::{util::take::Take, Spanned, SyntaxContext}; use swc_ecma_ast::*; -use swc_ecma_utils::{number::ToJsString, ExprExt, IsEmpty, Type, Value}; +use swc_ecma_utils::{ + number::ToJsString, + unicode::{is_high_surrogate, is_low_surrogate}, + ExprExt, IsEmpty, Type, Value, +}; use super::Pure; #[cfg(feature = "debug")] @@ -414,7 +418,7 @@ impl Pure<'_> { *e = Str { span: call.span, - value: atom!("function(){}"), + value: atom!("function(){}").into(), raw: None, } .into(); @@ -437,13 +441,15 @@ impl Pure<'_> { MemberProp::PrivateName(_) => {} MemberProp::Computed(p) => { if let Expr::Lit(Lit::Str(s)) = &*p.expr { - if let Ok(value) = s.value.parse::() { - p.expr = Lit::Num(Number { - span: s.span, - value: value as f64, - raw: None, - }) - .into(); + if let Some(value) = s.value.as_str() { + if let Ok(value) = value.parse::() { + p.expr = Lit::Num(Number { + span: s.span, + value: value as f64, + raw: None, + }) + .into(); + } } } } @@ -865,14 +871,10 @@ impl Pure<'_> { } let idx = value.round() as i64 as usize; - let c = s.value.chars().nth(idx); + let c = s.value.to_ill_formed_utf16().nth(idx); match c { Some(v) => { - let mut b = [0; 2]; - v.encode_utf16(&mut b); - let v = b[0]; - self.changed = true; report_change!( "evaluate: Evaluated `charCodeAt` of a string literal as `{}`", @@ -906,20 +908,49 @@ impl Pure<'_> { } let idx = value.round() as i64 as usize; - let c = s.value.chars().nth(idx); - match c { + let mut c = s.value.to_ill_formed_utf16().skip(idx).peekable(); + match c.next() { Some(v) => { - self.changed = true; - report_change!( - "evaluate: Evaluated `codePointAt` of a string literal as `{}`", - v - ); - *e = Lit::Num(Number { - span: call.span, - value: v as usize as f64, - raw: None, - }) - .into() + match (v, c.peek()) { + (high, Some(&low)) + if is_high_surrogate(high as u32) + && is_low_surrogate(low as u32) => + { + // Decode surrogate pair + let code_point = swc_ecma_utils::unicode::pair_to_code_point( + high as u32, + low as u32, + ); + self.changed = true; + report_change!( + "evaluate: Evaluated `codePointAt` of a string literal as \ + `{}`", + code_point + ); + *e = Lit::Num(Number { + span: call.span, + value: code_point as f64, + raw: None, + }) + .into(); + return; + } + _ => { + // Not a surrogate pair + self.changed = true; + report_change!( + "evaluate: Evaluated `codePointAt` of a string literal as \ + `{}`", + v + ); + *e = Lit::Num(Number { + span: call.span, + value: v as usize as f64, + raw: None, + }) + .into() + } + } } None => { self.changed = true; diff --git a/crates/swc_ecma_minifier/src/compress/pure/misc.rs b/crates/swc_ecma_minifier/src/compress/pure/misc.rs index 19d67023fcd8..4990f5eecc21 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/misc.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/misc.rs @@ -1,7 +1,11 @@ use std::{fmt::Write, num::FpCategory}; use rustc_hash::FxHashSet; -use swc_atoms::{atom, Atom}; +use swc_atoms::{ + atom, + wtf8::{Wtf8, Wtf8Buf}, + Atom, Wtf8Atom, +}; use swc_common::{iter::IdentifyLast, util::take::Take, Span, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_optimization::debug_assert_valid; @@ -467,18 +471,18 @@ impl Pure<'_> { }; let separator = if call.args.is_empty() { - atom!(",") + Wtf8Atom::from(",") } else if call.args.len() == 1 { if call.args[0].spread.is_some() { return; } if is_pure_undefined(self.expr_ctx, &call.args[0].expr) { - atom!(",") + Wtf8Atom::from(",") } else { match &*call.args[0].expr { Expr::Lit(Lit::Str(s)) => s.value.clone(), - Expr::Lit(Lit::Null(..)) => atom!("null"), + Expr::Lit(Lit::Null(..)) => Wtf8Atom::from("null"), _ => return, } } @@ -520,7 +524,7 @@ impl Pure<'_> { *e = Lit::Str(Str { span: call.span, raw: None, - value: atom!(""), + value: atom!("").into(), }) .into(); return; @@ -582,7 +586,7 @@ impl Pure<'_> { let mut res = Lit::Str(Str { span: DUMMY_SP, raw: None, - value: atom!(""), + value: atom!("").into(), }) .into(); @@ -626,14 +630,14 @@ impl Pure<'_> { return; } - let mut res = String::new(); + let mut res = Wtf8Buf::default(); for (last, elem) in arr.elems.iter().identify_last() { if let Some(elem) = elem { debug_assert_eq!(elem.spread, None); match &*elem.expr { Expr::Lit(Lit::Str(s)) => { - res.push_str(&s.value); + res.push_wtf8(&s.value); } Expr::Lit(Lit::Num(n)) => { write!(res, "{}", n.value).unwrap(); @@ -650,7 +654,7 @@ impl Pure<'_> { } if !last { - res.push_str(&separator); + res.push_wtf8(&separator); } } @@ -672,7 +676,7 @@ impl Pure<'_> { &mut self, _span: Span, elems: &mut Vec>, - separator: &str, + separator: &Wtf8, ) -> Option { if !self.options.evaluate { return None; @@ -806,17 +810,17 @@ impl Pure<'_> { result_parts.push(Box::new(Expr::Lit(Lit::Str(Str { span: DUMMY_SP, raw: None, - value: atom!(""), + value: atom!("").into(), })))); } for group in groups { match group { GroupType::Literals(literals) => { - let mut joined = String::new(); + let mut joined = Wtf8Buf::new(); for literal in literals.iter() { match &*literal.expr { - Expr::Lit(Lit::Str(s)) => joined.push_str(&s.value), + Expr::Lit(Lit::Str(s)) => joined.push_wtf8(&s.value), Expr::Lit(Lit::Num(n)) => write!(joined, "{}", n.value).unwrap(), Expr::Lit(Lit::Null(..)) => { // For string concatenation, null becomes @@ -865,14 +869,14 @@ impl Pure<'_> { for group in groups { match group { GroupType::Literals(literals) => { - let mut joined = String::new(); + let mut joined = Wtf8Buf::new(); for (idx, literal) in literals.iter().enumerate() { if idx > 0 { - joined.push_str(separator); + joined.push_wtf8(separator); } match &*literal.expr { - Expr::Lit(Lit::Str(s)) => joined.push_str(&s.value), + Expr::Lit(Lit::Str(s)) => joined.push_wtf8(&s.value), Expr::Lit(Lit::Num(n)) => write!(joined, "{}", n.value).unwrap(), Expr::Lit(Lit::Null(..)) => { // null becomes empty string @@ -962,14 +966,21 @@ impl Pure<'_> { fn optimize_regex(&mut self, args: &mut Vec, span: &mut Span) -> Option { fn valid_pattern(pattern: &Expr) -> Option { if let Expr::Lit(Lit::Str(s)) = pattern { - if s.value.contains(|c: char| { - // whitelist + if s.value.code_points().any(|c| { + let Some(c) = c.to_char() else { + return true; + }; + + // allowlist !c.is_ascii_alphanumeric() && !matches!(c, '$' | '[' | ']' | '(' | ')' | '{' | '}' | '-' | '+' | '_') }) { None } else { - Some(s.value.clone()) + // SAFETY: We've verified that the string is valid UTF-8 in the if condition + // above. For this branch, the string contains only ASCII alphanumeric + // characters and a few special characters. + Some(unsafe { Atom::from_wtf8_unchecked(s.value.clone()) }) } } else { None @@ -978,11 +989,11 @@ impl Pure<'_> { fn valid_flag(flag: &Expr, es_version: EsVersion) -> Option { if let Expr::Lit(Lit::Str(s)) = flag { let mut set = FxHashSet::default(); - for c in s.value.chars() { - if !(matches!(c, 'g' | 'i' | 'm') - || (es_version >= EsVersion::Es2015 && matches!(c, 'u' | 'y')) - || (es_version >= EsVersion::Es2018 && matches!(c, 's'))) - || (es_version >= EsVersion::Es2022 && matches!(c, 'd')) + for c in s.value.code_points() { + if !(matches!(c.to_char()?, 'g' | 'i' | 'm') + || (es_version >= EsVersion::Es2015 && matches!(c.to_char()?, 'u' | 'y')) + || (es_version >= EsVersion::Es2018 && matches!(c.to_char()?, 's'))) + || (es_version >= EsVersion::Es2022 && matches!(c.to_char()?, 'd')) { return None; } @@ -992,7 +1003,9 @@ impl Pure<'_> { } } - Some(s.value.clone()) + // SAFETY: matches above ensure that the string is valid UTF-8 ('g', 'i', 'm', + // 'u', 'y', 's', 'd') + Some(unsafe { Atom::from_wtf8_unchecked(s.value.clone()) }) } else { None } @@ -1218,7 +1231,7 @@ impl Pure<'_> { [] => Some( Lit::Str(Str { span: *span, - value: atom!(""), + value: atom!("").into(), raw: None, }) .into(), @@ -1232,7 +1245,7 @@ impl Pure<'_> { op: op!(bin, "+"), right: Lit::Str(Str { span: *span, - value: atom!(""), + value: atom!("").into(), raw: None, }) .into(), @@ -1351,7 +1364,7 @@ impl Pure<'_> { &mut self, span: Span, elems: &mut Vec>, - sep: &str, + sep: &Wtf8, ) -> Option { if !self.options.evaluate { return None; @@ -1374,15 +1387,15 @@ impl Pure<'_> { exprs: Vec::new(), }; let mut cur_raw = String::new(); - let mut cur_cooked = String::new(); + let mut cur_cooked = Wtf8Buf::default(); let mut first = true; for elem in elems.take().into_iter().flatten() { if first { first = false; } else { - cur_raw.push_str(sep); - cur_cooked.push_str(sep); + cur_raw.push_str(&convert_str_value_to_tpl_raw(sep)); + cur_cooked.push_wtf8(sep); } match *elem.expr { @@ -1393,7 +1406,7 @@ impl Pure<'_> { // quasis let e = tpl.quasis[idx / 2].take(); - cur_cooked.push_str(&e.cooked.unwrap()); + cur_cooked.push_wtf8(&e.cooked.unwrap()); cur_raw.push_str(&e.raw); } else { new_tpl.quasis.push(TplElement { @@ -1413,7 +1426,7 @@ impl Pure<'_> { } } Expr::Lit(Lit::Str(s)) => { - cur_cooked.push_str(&convert_str_value_to_tpl_cooked(&s.value)); + cur_cooked.push_wtf8(&convert_str_value_to_tpl_cooked(&s.value)); cur_raw.push_str(&convert_str_value_to_tpl_raw(&s.value)); } _ => { @@ -2063,7 +2076,8 @@ impl Pure<'_> { match e { Expr::Lit(Lit::Str(s)) => { - if (self.options.directives && !matches!(&*s.value, "use strict" | "use asm")) + if (self.options.directives + && !matches!(s.value.as_str(), Some(s) if s == "use strict" || s == "use asm")) || opts.contains(DropOpts::DROP_STR_LIT) || (s.value.starts_with("@swc/helpers") || s.value.starts_with("@babel/helpers")) diff --git a/crates/swc_ecma_minifier/src/compress/pure/numbers.rs b/crates/swc_ecma_minifier/src/compress/pure/numbers.rs index ebdb284a5ffb..bd68d95f71dd 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/numbers.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/numbers.rs @@ -8,6 +8,9 @@ impl Pure<'_> { pub(super) fn optimize_expr_in_num_ctx(&mut self, e: &mut Expr) { match e { Expr::Lit(Lit::Str(Str { span, value, .. })) => { + let Some(value) = value.as_str() else { + return; + }; let value = if value.is_empty() { 0f64 } else { diff --git a/crates/swc_ecma_minifier/src/compress/pure/properties.rs b/crates/swc_ecma_minifier/src/compress/pure/properties.rs index cf9f35b52617..4a1d394571e0 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/properties.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/properties.rs @@ -1,4 +1,4 @@ -use swc_atoms::atom; +use swc_atoms::Atom; use swc_ecma_ast::*; use super::Pure; @@ -18,20 +18,25 @@ impl Pure<'_> { } match &*c.expr { - Expr::Lit(Lit::Str(s)) - if s.value.is_reserved() - || s.value.is_reserved_in_es3() - || is_valid_identifier(&s.value, true) => - { - self.changed = true; - report_change!( - "properties: Computed member => member expr with identifier as a prop" - ); + Expr::Lit(Lit::Str(s)) => { + let value = s.value.as_str()?; + if value.is_reserved() + || value.is_reserved_in_es3() + || is_valid_identifier(value, true) + { + self.changed = true; + report_change!( + "properties: Computed member => member expr with identifier as a prop" + ); - Some(IdentName { - span: s.span, - sym: s.value.clone(), - }) + Some(IdentName { + span: s.span, + // SAFETY: We just checked that s.value is valid UTF-8. + sym: unsafe { Atom::from_wtf8_unchecked(s.value.clone()) }, + }) + } else { + None + } } _ => None, @@ -48,15 +53,22 @@ impl Pure<'_> { if let PropName::Computed(c) = p { match &mut *c.expr { Expr::Lit(Lit::Str(s)) => { - if s.value == *"constructor" || s.value == *"__proto__" { + let Some(value) = s.value.as_str() else { + return; + }; + if value == "constructor" || value == "__proto__" { return; } - if s.value.is_reserved() - || s.value.is_reserved_in_es3() - || is_valid_identifier(&s.value, false) + if value.is_reserved() + || value.is_reserved_in_es3() + || is_valid_identifier(value, false) { - *p = PropName::Ident(IdentName::new(s.value.clone(), s.span)); + *p = PropName::Ident(IdentName::new( + // SAFETY: reserved words and valid identifiers are valid UTF-8. + unsafe { Atom::from_wtf8_unchecked(s.value.clone()) }, + s.span, + )); } else { *p = PropName::Str(s.clone()); } @@ -73,21 +85,25 @@ impl Pure<'_> { pub(super) fn optimize_prop_name(&mut self, name: &mut PropName) { if let PropName::Str(s) = name { - if s.value.is_reserved() - || s.value.is_reserved_in_es3() - || is_valid_identifier(&s.value, false) + let Some(value) = s.value.as_str() else { + return; + }; + if value.is_reserved() + || value.is_reserved_in_es3() + || is_valid_identifier(value, false) { self.changed = true; report_change!("misc: Optimizing string property name"); *name = PropName::Ident(IdentName { span: s.span, - sym: s.value.clone(), + // SAFETY: reserved words and valid identifiers are valid UTF-8. + sym: unsafe { Atom::from_wtf8_unchecked(s.value.clone()) }, }); return; } - if (!s.value.starts_with('0') && !s.value.starts_with('+')) || s.value.len() <= 1 { - if let Ok(v) = s.value.parse::() { + if (!value.starts_with('0') && !value.starts_with('+')) || value.len() <= 1 { + if let Ok(v) = value.parse::() { self.changed = true; report_change!("misc: Optimizing numeric property name"); *name = PropName::Num(Number { @@ -110,9 +126,10 @@ impl Pure<'_> { match &*c.expr { Expr::Lit(Lit::Str(s)) => { - if s.value == atom!("") - || s.value.starts_with(|c: char| c.is_ascii_digit()) - || s.value + let value = s.value.as_str()?; + if value.is_empty() + || value.starts_with(|c: char| c.is_ascii_digit()) + || value .contains(|c: char| !matches!(c, '0'..='9' | 'a'..='z' | 'A'..='Z' | '$')) { return None; @@ -120,7 +137,11 @@ impl Pure<'_> { self.changed = true; - Some(IdentName::new(s.value.clone(), s.span)) + Some(IdentName::new( + // SAFETY: We just checked that s.value is valid UTF-8. + unsafe { Atom::from_wtf8_unchecked(s.value.clone()) }, + s.span, + )) } _ => None, } diff --git a/crates/swc_ecma_minifier/src/compress/pure/strings.rs b/crates/swc_ecma_minifier/src/compress/pure/strings.rs index 9e3be0025c99..f4c9d0e34f13 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/strings.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/strings.rs @@ -1,6 +1,9 @@ use std::{borrow::Cow, mem::take}; -use swc_atoms::Atom; +use swc_atoms::{ + wtf8::{Wtf8, Wtf8Buf}, + Atom, Wtf8Atom, +}; use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_utils::{ExprExt, Type, Value}; @@ -89,7 +92,7 @@ impl Pure<'_> { left: tpl.quasis[0] .cooked .clone() - .unwrap_or_else(|| tpl.quasis[0].raw.clone()) + .unwrap_or_else(|| tpl.quasis[0].raw.clone().into()) .into(), right: tpl.exprs[0].take(), } @@ -121,7 +124,7 @@ impl Pure<'_> { quasis: Default::default(), exprs: Default::default(), }; - let mut cur_cooked_str = String::new(); + let mut cur_cooked_str = Wtf8Buf::new(); let mut cur_raw_str = String::new(); for idx in 0..(tpl.quasis.len() + tpl.exprs.len()) { @@ -146,7 +149,7 @@ impl Pure<'_> { cur_cooked_str.push_str(Str::from_tpl_raw(&q.raw).as_ref()); cur_raw_str.push_str(&q.raw); } else { - let cooked = Atom::from(&*cur_cooked_str); + let cooked = Wtf8Atom::from(&*cur_cooked_str); let raw = Atom::from(&*cur_raw_str); cur_cooked_str.clear(); cur_raw_str.clear(); @@ -165,7 +168,7 @@ impl Pure<'_> { } } _ => { - let cooked = Atom::from(&*cur_cooked_str); + let cooked = Wtf8Atom::from(&*cur_cooked_str); let raw = Atom::from(&*cur_raw_str); cur_cooked_str.clear(); cur_raw_str.clear(); @@ -183,7 +186,7 @@ impl Pure<'_> { } } - let cooked = Atom::from(&*cur_cooked_str); + let cooked = Wtf8Atom::from(&*cur_cooked_str); let raw = Atom::from(&*cur_raw_str); new_tpl.quasis.push(TplElement { span: DUMMY_SP, @@ -200,21 +203,23 @@ impl Pure<'_> { match e { Expr::Tpl(t) if t.quasis.len() == 1 && t.exprs.is_empty() => { if let Some(value) = &t.quasis[0].cooked { - if value.chars().all(|c| match c { - '\\' => false, - '\u{0020}'..='\u{007e}' => true, - '\n' | '\r' => self.config.force_str_for_tpl, - _ => false, - }) { - report_change!("converting a template literal to a string literal"); - - *e = Lit::Str(Str { - span: t.span, - raw: None, - value: value.clone(), - }) - .into(); - return; + if let Some(value) = value.as_str() { + if value.chars().all(|c| match c { + '\\' => false, + '\u{0020}'..='\u{007e}' => true, + '\n' | '\r' => self.config.force_str_for_tpl, + _ => false, + }) { + report_change!("converting a template literal to a string literal"); + + *e = Lit::Str(Str { + span: t.span, + raw: None, + value: t.quasis[0].cooked.clone().unwrap(), + }) + .into(); + return; + } } } @@ -238,7 +243,7 @@ impl Pure<'_> { *e = Lit::Str(Str { span: t.span, raw: None, - value, + value: value.into(), }) .into(); } @@ -267,7 +272,7 @@ impl Pure<'_> { let mut quasis = Vec::new(); let mut exprs = Vec::new(); let mut cur_raw = String::new(); - let mut cur_cooked = Some(String::new()); + let mut cur_cooked = Some(Wtf8Buf::new()); for i in 0..(tpl.exprs.len() + tpl.quasis.len()) { if i % 2 == 0 { @@ -298,7 +303,7 @@ impl Pure<'_> { } } _ => { - cur_cooked = Some(String::new()); + cur_cooked = Some(Wtf8Buf::new()); } } } @@ -314,7 +319,7 @@ impl Pure<'_> { cur_raw.push_str(&q.raw); if let Some(cooked) = q.cooked { if let Some(cur_cooked) = &mut cur_cooked { - cur_cooked.push_str(&cooked); + cur_cooked.push_wtf8(&cooked); } } else { // If cooked is None, it means that the template literal contains invalid escape @@ -328,7 +333,7 @@ impl Pure<'_> { match *e { Expr::Lit(Lit::Str(s)) => { if let Some(cur_cooked) = &mut cur_cooked { - cur_cooked.push_str(&convert_str_value_to_tpl_cooked(&s.value)); + cur_cooked.push_wtf8(&convert_str_value_to_tpl_cooked(&s.value)); } if let Some(raw) = &s.raw { @@ -348,7 +353,7 @@ impl Pure<'_> { cooked: cur_cooked.take().map(From::from), raw: take(&mut cur_raw).into(), }); - cur_cooked = Some(String::new()); + cur_cooked = Some(Wtf8Buf::new()); exprs.push(e); } @@ -391,9 +396,9 @@ impl Pure<'_> { ); if let Some(cooked) = &mut l_last.cooked { - *cooked = - format!("{}{}", cooked, convert_str_value_to_tpl_cooked(&rs.value)) - .into(); + let mut c = Wtf8Buf::from(&*cooked); + c.push_wtf8(&convert_str_value_to_tpl_cooked(&rs.value)); + *cooked = c.into(); } l_last.raw = format!( @@ -427,9 +432,10 @@ impl Pure<'_> { ); if let Some(cooked) = &mut r_first.cooked { - *cooked = - format!("{}{}", convert_str_value_to_tpl_cooked(&ls.value), cooked) - .into() + let mut c = Wtf8Buf::new(); + c.push_wtf8(&convert_str_value_to_tpl_cooked(&ls.value)); + c.push_wtf8(&*cooked); + *cooked = c.into(); } let new: Atom = format!( @@ -492,10 +498,9 @@ impl Pure<'_> { if let Value::Known(Type::Str) = type_of_second { if let Value::Known(Type::Str) = type_of_third { - if let Value::Known(second_str) = left.right.as_pure_string(self.expr_ctx) { - if let Value::Known(third_str) = bin.right.as_pure_string(self.expr_ctx) - { - let new_str = format!("{second_str}{third_str}"); + if let Value::Known(second_str) = left.right.as_pure_wtf8(self.expr_ctx) { + if let Value::Known(third_str) = bin.right.as_pure_wtf8(self.expr_ctx) { + let new_str = second_str.into_owned() + &*third_str; let left_span = left.span; self.changed = true; @@ -570,22 +575,83 @@ impl Pure<'_> { } } -pub(super) fn convert_str_value_to_tpl_cooked(value: &Atom) -> Cow { - value - .replace("\\\\", "\\") - .replace("\\`", "`") - .replace("\\$", "$") - .into() +pub(super) fn convert_str_value_to_tpl_cooked(value: &Wtf8) -> Cow { + let mut result = Wtf8Buf::default(); + let mut need_replace = false; + + let mut iter = value.code_points().peekable(); + while let Some(code_point) = iter.next() { + if let Some(ch) = code_point.to_char() { + match ch { + '\\' => { + if let Some(next) = iter.peek().and_then(|c| c.to_char()) { + match next { + '\\' => { + need_replace = true; + result.push_char('\\'); + iter.next(); + } + '`' => { + need_replace = true; + result.push_char('`'); + iter.next(); + } + '$' => { + need_replace = true; + result.push_char('$'); + iter.next(); + } + _ => result.push_char(ch), + } + } else { + result.push_char(ch); + } + } + _ => result.push_char(ch), + } + } else { + need_replace = true; + result.push_str(&format!("\\u{:04X}", code_point.to_u32())); + } + } + + if need_replace { + result.into() + } else { + Cow::Borrowed(value) + } } -pub(super) fn convert_str_value_to_tpl_raw(value: &Atom) -> Cow { - value - .replace('\\', "\\\\") - .replace('`', "\\`") - .replace('$', "\\$") - .replace('\n', "\\n") - .replace('\r', "\\r") - .into() +pub(super) fn convert_str_value_to_tpl_raw(value: &Wtf8) -> Cow { + let mut result = String::default(); + + let iter = value.code_points(); + for code_point in iter { + if let Some(ch) = code_point.to_char() { + match ch { + '\\' => { + result.push_str("\\\\"); + } + '`' => { + result.push_str("\\`"); + } + '$' => { + result.push_str("\\$"); + } + '\n' => { + result.push_str("\\n"); + } + '\r' => { + result.push_str("\\r"); + } + _ => result.push(ch), + } + } else { + result.push_str(&format!("\\u{:04X}", code_point.to_u32())); + } + } + + result.into() } pub(super) fn convert_str_raw_to_tpl_raw(value: &str) -> Atom { diff --git a/crates/swc_ecma_minifier/src/eval.rs b/crates/swc_ecma_minifier/src/eval.rs index ab89d366cd52..fc2fb10d8900 100644 --- a/crates/swc_ecma_minifier/src/eval.rs +++ b/crates/swc_ecma_minifier/src/eval.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use parking_lot::Mutex; use rustc_hash::FxHashMap; -use swc_atoms::atom; use swc_common::{SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_usage_analyzer::marks::Marks; @@ -263,7 +262,7 @@ impl Evaluator { fn is_truthy(lit: &EvalResult) -> Option { match lit { EvalResult::Lit(v) => match v { - Lit::Str(v) => Some(v.value != atom!("")), + Lit::Str(v) => Some(!v.value.is_empty()), Lit::Bool(v) => Some(v.value), Lit::Null(_) => Some(false), Lit::Num(v) => Some(v.value != 0.0 && v.value != -0.0), diff --git a/crates/swc_ecma_minifier/src/option/terser.rs b/crates/swc_ecma_minifier/src/option/terser.rs index e0a4131a0989..ee8ce8415730 100644 --- a/crates/swc_ecma_minifier/src/option/terser.rs +++ b/crates/swc_ecma_minifier/src/option/terser.rs @@ -490,7 +490,7 @@ fn value_to_expr(v: Value) -> Box { .into() } Value::String(v) => { - let value: Atom = v.into(); + let value = v.into(); Lit::Str(Str { span: DUMMY_SP, diff --git a/crates/swc_ecma_minifier/src/pass/mangle_props.rs b/crates/swc_ecma_minifier/src/pass/mangle_props.rs index 7322b5fed930..034980984487 100644 --- a/crates/swc_ecma_minifier/src/pass/mangle_props.rs +++ b/crates/swc_ecma_minifier/src/pass/mangle_props.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use once_cell::sync::Lazy; use rustc_hash::{FxHashMap, FxHashSet}; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_ecma_ast::*; use swc_ecma_usage_analyzer::util::get_mut_object_define_property_name_arg; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; @@ -29,18 +29,18 @@ struct ManglePropertiesState<'a> { chars: Base54Chars, options: &'a ManglePropertiesOptions, - names_to_mangle: FxHashSet, - unmangleable: FxHashSet, + names_to_mangle: FxHashSet, + unmangleable: FxHashSet, // Cache of already mangled names - cache: FxHashMap, + cache: FxHashMap, // Numbers to pass to base54() n: usize, } impl<'a> ManglePropertiesState<'a> { - fn add(&mut self, name: Atom) { + fn add(&mut self, name: Wtf8Atom) { let can_mangle = self.can_mangle(&name); let should_mangle = self.should_mangle(&name); match (can_mangle, !should_mangle) { @@ -58,19 +58,23 @@ impl<'a> ManglePropertiesState<'a> { } } - fn can_mangle(&self, name: &Atom) -> bool { + fn can_mangle(&self, name: &Wtf8Atom) -> bool { !(self.unmangleable.contains(name) || self.is_reserved(name)) } - fn matches_regex_option(&self, name: &Atom) -> bool { + fn matches_regex_option(&self, name: &Wtf8Atom) -> bool { if let Some(regex) = &self.options.regex { - regex.is_match(name) + if let Some(utf8_str) = name.as_str() { + regex.is_match(utf8_str) + } else { + false + } } else { true } } - fn should_mangle(&self, name: &Atom) -> bool { + fn should_mangle(&self, name: &Wtf8Atom) -> bool { if !self.matches_regex_option(name) || self.is_reserved(name) { false } else { @@ -78,11 +82,16 @@ impl<'a> ManglePropertiesState<'a> { } } - fn is_reserved(&self, name: &Atom) -> bool { - JS_ENVIRONMENT_PROPS.contains(name) || self.options.reserved.contains(name) + fn is_reserved(&self, name: &Wtf8Atom) -> bool { + if let Some(utf8_str) = name.as_str() { + let atom = Atom::from(utf8_str); + JS_ENVIRONMENT_PROPS.contains(&atom) || self.options.reserved.contains(&atom) + } else { + false + } } - fn gen_name(&mut self, name: &Atom) -> Option { + fn gen_name(&mut self, name: &Wtf8Atom) -> Option { if self.should_mangle(name) { if let Some(cached) = self.cache.get(name) { Some(cached.clone()) @@ -127,14 +136,15 @@ struct Mangler<'a, 'b> { impl Mangler<'_, '_> { fn mangle_ident(&mut self, ident: &mut IdentName) { - if let Some(mangled) = self.state.gen_name(&ident.sym) { + let wtf8_name = Wtf8Atom::from(ident.sym.clone()); + if let Some(mangled) = self.state.gen_name(&wtf8_name) { ident.sym = mangled; } } fn mangle_str(&mut self, string: &mut Str) { if let Some(mangled) = self.state.gen_name(&string.value) { - string.value = mangled; + string.value = mangled.into(); string.raw = None; } } diff --git a/crates/swc_ecma_minifier/src/pass/postcompress.rs b/crates/swc_ecma_minifier/src/pass/postcompress.rs index d36cb804c148..ea9491e37e8a 100644 --- a/crates/swc_ecma_minifier/src/pass/postcompress.rs +++ b/crates/swc_ecma_minifier/src/pass/postcompress.rs @@ -85,7 +85,7 @@ impl ImportKey { }); Self { - src: decl.src.value.to_string(), + src: decl.src.value.to_string_lossy().to_string(), type_only: decl.type_only, phase: decl.phase, with_hash, @@ -113,7 +113,7 @@ impl SpecifierKey { .as_ref() .map(|n| match n { ModuleExportName::Ident(id) => id.sym.to_string(), - ModuleExportName::Str(s) => s.value.to_string(), + ModuleExportName::Str(s) => s.value.to_string_lossy().to_string(), }) .unwrap_or_else(|| named.local.sym.to_string()); diff --git a/crates/swc_ecma_minifier/src/program_data.rs b/crates/swc_ecma_minifier/src/program_data.rs index 724c12838653..4f26bb85710e 100644 --- a/crates/swc_ecma_minifier/src/program_data.rs +++ b/crates/swc_ecma_minifier/src/program_data.rs @@ -2,7 +2,7 @@ use std::collections::hash_map::Entry; use indexmap::IndexSet; use rustc_hash::{FxBuildHasher, FxHashMap}; -use swc_atoms::Atom; +use swc_atoms::Wtf8Atom; use swc_common::SyntaxContext; use swc_ecma_ast::*; use swc_ecma_usage_analyzer::{ @@ -44,7 +44,7 @@ pub(crate) struct ProgramData { initialized_vars: IndexSet, - pub(crate) property_atoms: Option>, + pub(crate) property_atoms: Option>, } bitflags::bitflags! { @@ -127,7 +127,7 @@ pub(crate) struct VarUsageInfo { /// PR. (because it's hard to review) infects_to: Vec, /// Only **string** properties. - pub(crate) accessed_props: FxHashMap, + pub(crate) accessed_props: FxHashMap, } impl Default for VarUsageInfo { @@ -545,7 +545,7 @@ impl Storage for ProgramData { } } - fn add_property_atom(&mut self, atom: Atom) { + fn add_property_atom(&mut self, atom: Wtf8Atom) { if let Some(atoms) = self.property_atoms.as_mut() { atoms.push(atom); } @@ -617,7 +617,7 @@ impl VarDataLike for VarUsageInfo { .insert(VarUsageInfoFlags::INDEXED_WITH_DYNAMIC_KEY); } - fn add_accessed_property(&mut self, name: swc_atoms::Atom) { + fn add_accessed_property(&mut self, name: swc_atoms::Wtf8Atom) { *self.accessed_props.entry(name).or_default() += 1; } diff --git a/crates/swc_ecma_minifier/tests/eval.rs b/crates/swc_ecma_minifier/tests/eval.rs index 5bcd12952723..c6e202764f15 100644 --- a/crates/swc_ecma_minifier/tests/eval.rs +++ b/crates/swc_ecma_minifier/tests/eval.rs @@ -1,6 +1,6 @@ #![deny(warnings)] -use swc_atoms::Atom; +use swc_atoms::{wtf8::Wtf8, Atom}; use swc_common::{sync::Lrc, FileName, Mark, SourceMap}; use swc_ecma_ast::*; use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; @@ -13,6 +13,21 @@ use swc_ecma_transforms_base::resolver; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; use testing::{assert_eq, DebugUsingDisplay}; +fn convert_wtf8_to_raw(s: &Wtf8) -> String { + let mut result = String::new(); + let iter = s.code_points(); + + for code_point in iter { + if let Some(c) = code_point.to_char() { + result.push(c); + } else { + result.push_str(format!("\\u{:04X}", code_point.to_u32()).as_str()); + } + } + + result +} + fn eval(module: &str, expr: &str) -> Option { testing::run_test2(false, |cm, _handler| { let fm = cm.new_source_file(FileName::Anon.into(), module.to_string()); @@ -46,7 +61,7 @@ fn eval(module: &str, expr: &str) -> Option { match res { Some(res) => match res { EvalResult::Lit(l) => match l { - swc_ecma_ast::Lit::Str(v) => Ok(Some(v.value.to_string())), + swc_ecma_ast::Lit::Str(v) => Ok(Some(convert_wtf8_to_raw(&v.value))), swc_ecma_ast::Lit::Bool(v) => Ok(Some(v.value.to_string())), swc_ecma_ast::Lit::Num(v) => Ok(Some(v.value.to_string())), swc_ecma_ast::Lit::Null(_) => Ok(Some("null".into())), @@ -64,6 +79,16 @@ fn eval(module: &str, expr: &str) -> Option { fn simple() { assert_eq!(eval("const foo = 4", "foo").unwrap(), "4"); + assert_eq!( + eval( + "const high = '\\uD83D'; const low = '\\uDCA9'; const result = high + low;", + "result" + ) + .unwrap(), + "💩" + ); + assert_eq!(eval("const high = '\\uD83D';", "high").unwrap(), "\\uD83D"); + assert_eq!(eval("const low = '\\uDCA9';", "low").unwrap(), "\\uDCA9"); } #[test] @@ -190,9 +215,9 @@ impl VisitMut for PartialInliner { let el = TplElement { span: s.span, tail: true, - // TODO possible bug for quotes - raw: Atom::new(&*s.value), - cooked: Some(Atom::new(&*s.value)), + // TODO possible bug for quotes and surrogates + raw: Atom::new(s.value.to_string_lossy()), + cooked: Some(s.value.clone()), }; tt.tpl = Box::new(Tpl { span: el.span, diff --git a/crates/swc_ecma_minifier/tests/fixture/next/swc-4559/output.js b/crates/swc_ecma_minifier/tests/fixture/next/swc-4559/output.js index 7611206e62e5..c3dd5e3004a0 100644 --- a/crates/swc_ecma_minifier/tests/fixture/next/swc-4559/output.js +++ b/crates/swc_ecma_minifier/tests/fixture/next/swc-4559/output.js @@ -4,10 +4,7 @@ ], { /***/ 4816: /***/ function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { - return Error(`MUI: \`\` is not a valid prop. -Only page size below ${MAX_PAGE_SIZE} is available in the MIT version. - -You need to upgrade to the DataGridPro component to unlock this feature.`); + return Error(`MUI: \`\` is not a valid prop.\nOnly page size below ${MAX_PAGE_SIZE} is available in the MIT version.\n\nYou need to upgrade to the DataGridPro component to unlock this feature.`); } } ]); diff --git a/crates/swc_ecma_minifier/tests/libs-size.snapshot.md b/crates/swc_ecma_minifier/tests/libs-size.snapshot.md index aa2185e2b3df..561c402979ab 100644 --- a/crates/swc_ecma_minifier/tests/libs-size.snapshot.md +++ b/crates/swc_ecma_minifier/tests/libs-size.snapshot.md @@ -1,10 +1,10 @@ | File | Original Size | Compressed Size | Gzipped Size | | --- | --- | --- | --- | -| antd.js | 6.38 MiB | 2.06 MiB | 445.41 KiB | +| antd.js | 6.38 MiB | 2.06 MiB | 445.40 KiB | | d3.js | 542.74 KiB | 261.45 KiB | 85.34 KiB | | echarts.js | 3.41 MiB | 977.31 KiB | 314.18 KiB | | jquery.js | 280.89 KiB | 87.80 KiB | 30.21 KiB | -| lodash.js | 531.35 KiB | 68.91 KiB | 24.60 KiB | +| lodash.js | 531.35 KiB | 68.92 KiB | 24.60 KiB | | moment.js | 169.83 KiB | 57.39 KiB | 18.26 KiB | | react.js | 70.45 KiB | 22.44 KiB | 8.04 KiB | | terser.js | 1.08 MiB | 446.65 KiB | 120.49 KiB | diff --git a/crates/swc_ecma_minifier/tests/terser/compress/ascii/ascii_only_true_identifier_es5/output.js b/crates/swc_ecma_minifier/tests/terser/compress/ascii/ascii_only_true_identifier_es5/output.js index 828892c733c8..ac74819e2798 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/ascii/ascii_only_true_identifier_es5/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/ascii/ascii_only_true_identifier_es5/output.js @@ -1,4 +1,6 @@ function f() { - var o = { "\ud835\udc9c": true }; + var o = { + 𝒜: true + }; return o["\ud835\udc9c"]; } diff --git a/crates/swc_ecma_minifier/tests/terser/compress/ascii/ascii_only_true_identifier_es5/output.mangleOnly.js b/crates/swc_ecma_minifier/tests/terser/compress/ascii/ascii_only_true_identifier_es5/output.mangleOnly.js index 4bc0c565104b..abe2f3820324 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/ascii/ascii_only_true_identifier_es5/output.mangleOnly.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/ascii/ascii_only_true_identifier_es5/output.mangleOnly.js @@ -1,6 +1,6 @@ -function u() { - var u = { +function r() { + var r = { "\ud835\udc9c": true }; - return u["\ud835\udc9c"]; + return r["\ud835\udc9c"]; } diff --git a/crates/swc_ecma_parser/src/lexer/mod.rs b/crates/swc_ecma_parser/src/lexer/mod.rs index bb0cd16e1c55..879df9f1f2dc 100644 --- a/crates/swc_ecma_parser/src/lexer/mod.rs +++ b/crates/swc_ecma_parser/src/lexer/mod.rs @@ -2,7 +2,10 @@ use std::{char, iter::FusedIterator, rc::Rc}; -use swc_atoms::AtomStoreCell; +use swc_atoms::{ + wtf8::{Wtf8, Wtf8Buf}, + AtomStoreCell, +}; use swc_common::{ comments::Comments, input::{Input, StringInput}, @@ -116,6 +119,11 @@ impl<'a> swc_ecma_lexer::common::lexer::Lexer<'a, TokenAndSpan> for Lexer<'a> { fn atom<'b>(&self, s: impl Into>) -> swc_atoms::Atom { self.atoms.atom(s) } + + #[inline(always)] + fn wtf8_atom<'b>(&self, s: impl Into>) -> swc_atoms::Wtf8Atom { + self.atoms.wtf8_atom(s) + } } impl<'a> Lexer<'a> { @@ -332,7 +340,7 @@ impl Lexer<'_> { started_with_backtick: bool, ) -> LexResult { debug_assert!(self.cur() == Some(if started_with_backtick { '`' } else { '}' })); - let mut cooked = Ok(String::with_capacity(8)); + let mut cooked = Ok(Wtf8Buf::with_capacity(8)); self.bump(); // `}` or `\`` let mut cooked_slice_start = self.cur_pos(); let raw_slice_start = cooked_slice_start; @@ -357,7 +365,7 @@ impl Lexer<'_> { while let Some(c) = self.cur() { if c == '`' { consume_cooked!(); - let cooked = cooked.map(|cooked| self.atoms.atom(cooked)); + let cooked = cooked.map(|cooked| self.atoms.wtf8_atom(&*cooked)); let raw = raw_atom(self); self.bump(); return Ok(if started_with_backtick { @@ -369,7 +377,7 @@ impl Lexer<'_> { }); } else if c == '$' && self.input.peek() == Some('{') { consume_cooked!(); - let cooked = cooked.map(|cooked| self.atoms.atom(cooked)); + let cooked = cooked.map(|cooked| self.atoms.wtf8_atom(&*cooked)); let raw = raw_atom(self); self.input.bump_bytes(2); return Ok(if started_with_backtick { @@ -383,11 +391,9 @@ impl Lexer<'_> { consume_cooked!(); match self.read_escaped_char(true) { - Ok(Some(chars)) => { + Ok(Some(escaped)) => { if let Ok(ref mut cooked) = cooked { - for c in chars { - cooked.extend(c); - } + cooked.push(escaped); } } Ok(None) => {} @@ -416,7 +422,7 @@ impl Lexer<'_> { self.bump(); if let Ok(ref mut cooked) = cooked { - cooked.push(c); + cooked.push_char(c); } cooked_slice_start = self.cur_pos(); } else { diff --git a/crates/swc_ecma_parser/src/lexer/state.rs b/crates/swc_ecma_parser/src/lexer/state.rs index 72b40ef9dadf..f12304fac8b7 100644 --- a/crates/swc_ecma_parser/src/lexer/state.rs +++ b/crates/swc_ecma_parser/src/lexer/state.rs @@ -1,5 +1,6 @@ use std::mem::take; +use swc_atoms::wtf8::CodePoint; use swc_common::BytePos; use swc_ecma_ast::EsVersion; use swc_ecma_lexer::{ @@ -521,7 +522,10 @@ impl Lexer<'_> { let raw: swc_atoms::Atom = self.atom(raw); - self.state.set_token_value(TokenValue::Str { raw, value }); + self.state.set_token_value(TokenValue::Str { + raw, + value: value.into(), + }); self.state.start = start; @@ -553,12 +557,14 @@ impl Lexer<'_> { continue; } self.bump(); // bump 'u' - let Ok(chars) = self.read_unicode_escape() else { + let Ok(value) = self.read_unicode_escape() else { self.emit_error(self.cur_pos(), SyntaxError::InvalidUnicodeEscape); break; }; - for c in chars { - v.extend(c); + if let Some(c) = CodePoint::from(value).to_char() { + v.push(c); + } else { + self.emit_error(self.cur_pos(), SyntaxError::InvalidUnicodeEscape); } self.token_flags |= swc_ecma_lexer::lexer::TokenFlags::UNICODE; } else { diff --git a/crates/swc_ecma_parser/src/lexer/token.rs b/crates/swc_ecma_parser/src/lexer/token.rs index 5c0f4b86d59a..3fa9052d0d47 100644 --- a/crates/swc_ecma_parser/src/lexer/token.rs +++ b/crates/swc_ecma_parser/src/lexer/token.rs @@ -1,5 +1,5 @@ use num_bigint::BigInt; -use swc_atoms::{atom, Atom}; +use swc_atoms::{atom, Atom, Wtf8Atom}; use swc_common::Span; use swc_ecma_ast::AssignOp; use swc_ecma_lexer::common::context::Context; @@ -13,11 +13,11 @@ pub enum TokenValue { Word(Atom), Template { raw: Atom, - cooked: LexResult, + cooked: LexResult, }, // string, jsx text Str { - value: Atom, + value: Wtf8Atom, raw: Atom, }, // regexp @@ -659,13 +659,13 @@ impl<'a, I: Tokens> swc_ecma_lexer::common::lexer::token::TokenFactory<'a, Token } #[inline(always)] - fn str(value: Atom, raw: Atom, lexer: &mut crate::Lexer<'a>) -> Self { + fn str(value: Wtf8Atom, raw: Atom, lexer: &mut crate::Lexer<'a>) -> Self { lexer.set_token_value(Some(TokenValue::Str { value, raw })); Token::Str } #[inline(always)] - fn template(cooked: LexResult, raw: Atom, lexer: &mut crate::Lexer<'a>) -> Self { + fn template(cooked: LexResult, raw: Atom, lexer: &mut crate::Lexer<'a>) -> Self { lexer.set_token_value(Some(TokenValue::Template { cooked, raw })); Token::Template } @@ -734,7 +734,7 @@ impl<'a, I: Tokens> swc_ecma_lexer::common::lexer::token::TokenFactory<'a, Token } #[inline(always)] - fn take_str(self, buffer: &mut Self::Buffer) -> (Atom, Atom) { + fn take_str(self, buffer: &mut Self::Buffer) -> (Wtf8Atom, Atom) { buffer.expect_string_token_value() } @@ -809,13 +809,16 @@ impl<'a, I: Tokens> swc_ecma_lexer::common::lexer::token::TokenFactory<'a, Token } #[inline(always)] - fn take_template(self, buffer: &mut Self::Buffer) -> (LexResult, Atom) { + fn take_template(self, buffer: &mut Self::Buffer) -> (LexResult, Atom) { buffer.expect_template_token_value() } #[inline(always)] fn jsx_text(value: Atom, raw: Atom, lexer: &mut Self::Lexer) -> Self { - lexer.set_token_value(Some(TokenValue::Str { value, raw })); + lexer.set_token_value(Some(TokenValue::Str { + value: value.into(), + raw, + })); Token::JSXText } @@ -826,7 +829,9 @@ impl<'a, I: Tokens> swc_ecma_lexer::common::lexer::token::TokenFactory<'a, Token #[inline(always)] fn take_jsx_text(self, buffer: &mut Self::Buffer) -> (Atom, Atom) { - buffer.expect_string_token_value() + let (value, raw) = buffer.expect_string_token_value(); + // SAFETY: We set value as Atom in `jsx_text` method. + (value.as_atom().cloned().unwrap(), raw) } #[inline(always)] @@ -1058,7 +1063,7 @@ impl Token { let Some(TokenValue::Str { value, raw, .. }) = value else { unreachable!("{:#?}", value) }; - return format!("string literal ({value}, {raw})"); + return format!("string literal ({value:?}, {raw})"); } Token::Num => { let Some(TokenValue::Num { value, raw, .. }) = value else { diff --git a/crates/swc_ecma_parser/src/parser/expr/tests.rs b/crates/swc_ecma_parser/src/parser/expr/tests.rs index c192d5743c0a..81924fcd5c64 100644 --- a/crates/swc_ecma_parser/src/parser/expr/tests.rs +++ b/crates/swc_ecma_parser/src/parser/expr/tests.rs @@ -384,7 +384,7 @@ fn issue_328() { spread: None, expr: Box::new(Expr::Lit(Lit::Str(Str { span, - value: atom!("test"), + value: atom!("test").into(), raw: Some(atom!("'test'")), }))), }], @@ -413,7 +413,7 @@ hehe.";"#, ), Box::new(Expr::Lit(Lit::Str(Str { span, - value: atom!("okokhehe."), + value: atom!("okokhehe.").into(), raw: Some(atom!("\"ok\\\nok\\\nhehe.\"")), }))) ); diff --git a/crates/swc_ecma_parser/src/parser/input.rs b/crates/swc_ecma_parser/src/parser/input.rs index b6ed2357760a..292a921b5e80 100644 --- a/crates/swc_ecma_parser/src/parser/input.rs +++ b/crates/swc_ecma_parser/src/parser/input.rs @@ -1,4 +1,4 @@ -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::{BytePos, Span}; use swc_ecma_lexer::common::{ lexer::{token::TokenFactory, LexResult}, @@ -59,7 +59,7 @@ impl Buffer { (value, raw) } - pub fn expect_string_token_value(&mut self) -> (Atom, Atom) { + pub fn expect_string_token_value(&mut self) -> (Wtf8Atom, Atom) { let Some(crate::lexer::TokenValue::Str { value, raw }) = self.iter.take_token_value() else { unreachable!() @@ -83,7 +83,7 @@ impl Buffer { (value, flags) } - pub fn expect_template_token_value(&mut self) -> (LexResult, Atom) { + pub fn expect_template_token_value(&mut self) -> (LexResult, Atom) { let Some(crate::lexer::TokenValue::Template { cooked, raw }) = self.iter.take_token_value() else { unreachable!() @@ -287,7 +287,7 @@ impl<'a, I: Tokens> swc_ecma_lexer::common::parser::buffer::Buffer<'a> for Buffe ret } - fn expect_string_token_and_bump(&mut self) -> (Atom, Atom) { + fn expect_string_token_and_bump(&mut self) -> (Wtf8Atom, Atom) { let cur = *self.cur(); let ret = cur.take_str(self); self.bump(); @@ -308,7 +308,7 @@ impl<'a, I: Tokens> swc_ecma_lexer::common::parser::buffer::Buffer<'a> for Buffe ret } - fn expect_template_token_and_bump(&mut self) -> (LexResult, Atom) { + fn expect_template_token_and_bump(&mut self) -> (LexResult, Atom) { let cur = *self.cur(); let ret = cur.take_template(self); self.bump(); diff --git a/crates/swc_ecma_parser/src/parser/jsx/mod.rs b/crates/swc_ecma_parser/src/parser/jsx/mod.rs index a9248fa435ff..894dd55eee64 100644 --- a/crates/swc_ecma_parser/src/parser/jsx/mod.rs +++ b/crates/swc_ecma_parser/src/parser/jsx/mod.rs @@ -505,7 +505,7 @@ mod tests { name: JSXAttrName::Ident(IdentName::new(atom!("id"), span)), value: Some(JSXAttrValue::Lit(Lit::Str(Str { span, - value: atom!("w < w"), + value: atom!("w < w").into(), raw: Some(atom!("\"w < w\"")), }))), })], diff --git a/crates/swc_ecma_parser/src/parser/pat.rs b/crates/swc_ecma_parser/src/parser/pat.rs index 4894c367aa82..4570b37b345c 100644 --- a/crates/swc_ecma_parser/src/parser/pat.rs +++ b/crates/swc_ecma_parser/src/parser/pat.rs @@ -304,26 +304,26 @@ mod tests { prop( PropName::Str(Str { span, - value: atom!(""), + value: atom!("").into(), raw: Some(atom!("''")), }), "sym", Expr::Lit(Lit::Str(Str { span, - value: atom!(""), + value: atom!("").into(), raw: Some(atom!("''")), })) ), prop( PropName::Str(Str { span, - value: atom!(" "), + value: atom!(" ").into(), raw: Some(atom!("\" \"")), }), "quote", Expr::Lit(Lit::Str(Str { span, - value: atom!(" "), + value: atom!(" ").into(), raw: Some(atom!("\" \"")), })) ), diff --git a/crates/swc_ecma_parser/tests/common/mod.rs b/crates/swc_ecma_parser/tests/common/mod.rs index 530a28614701..c958cb1a53f8 100644 --- a/crates/swc_ecma_parser/tests/common/mod.rs +++ b/crates/swc_ecma_parser/tests/common/mod.rs @@ -70,6 +70,7 @@ impl Fold for Normalizer { Number { value, raw: None, + ..n } } else { @@ -105,7 +106,7 @@ impl Fold for Normalizer { match n { PropName::Ident(IdentName { span, sym, .. }) => PropName::Str(Str { span, - value: sym, + value: sym.into(), raw: None, }), PropName::Num(num) => PropName::Str(Str { diff --git a/crates/swc_ecma_parser/tests/jsx/basic/22/input.js b/crates/swc_ecma_parser/tests/jsx/basic/22/input.js index 519afd034a02..66adaad772f5 100644 --- a/crates/swc_ecma_parser/tests/jsx/basic/22/input.js +++ b/crates/swc_ecma_parser/tests/jsx/basic/22/input.js @@ -1 +1 @@ -
this should not parse as unicode: \u00a0
; \ No newline at end of file +
this should not parse as unicode: \u00a0
; diff --git a/crates/swc_ecma_parser/tests/jsx/basic/string/input.js.json b/crates/swc_ecma_parser/tests/jsx/basic/string/input.js.json index 4bc945beb1c8..712d9bd3fa85 100644 --- a/crates/swc_ecma_parser/tests/jsx/basic/string/input.js.json +++ b/crates/swc_ecma_parser/tests/jsx/basic/string/input.js.json @@ -226,7 +226,7 @@ "start": 79, "end": 89 }, - "value": "\\u{D800}", + "value": "\\uD800", "raw": "\"\\u{D800}\"" } } diff --git a/crates/swc_ecma_parser/tests/jsx/basic/template/input.js.json b/crates/swc_ecma_parser/tests/jsx/basic/template/input.js.json index 5c7d67be35aa..3d4ae69f8a3b 100644 --- a/crates/swc_ecma_parser/tests/jsx/basic/template/input.js.json +++ b/crates/swc_ecma_parser/tests/jsx/basic/template/input.js.json @@ -246,7 +246,7 @@ "end": 88 }, "tail": true, - "cooked": "\\u{D800}", + "cooked": "\\uD800", "raw": "\\u{D800}" } ] diff --git a/crates/swc_ecma_parser/tests/test262-error-references/fail/89036b2edb64c00c.js.swc-stderr b/crates/swc_ecma_parser/tests/test262-error-references/fail/89036b2edb64c00c.js.swc-stderr index 608b72236730..50c481b75257 100644 --- a/crates/swc_ecma_parser/tests/test262-error-references/fail/89036b2edb64c00c.js.swc-stderr +++ b/crates/swc_ecma_parser/tests/test262-error-references/fail/89036b2edb64c00c.js.swc-stderr @@ -1,4 +1,4 @@ - x Expected ',', got 'string literal (), '))' + x Expected ',', got 'string literal (")", '))' ,-[$DIR/tests/test262-parser/fail/89036b2edb64c00c.js:2:1] 1 | (' 2 | ') diff --git a/crates/swc_ecma_parser/tests/test262-error-references/fail/abc46381e4e6bcca.js.swc-stderr b/crates/swc_ecma_parser/tests/test262-error-references/fail/abc46381e4e6bcca.js.swc-stderr index d4361e6704d3..006950e56d81 100644 --- a/crates/swc_ecma_parser/tests/test262-error-references/fail/abc46381e4e6bcca.js.swc-stderr +++ b/crates/swc_ecma_parser/tests/test262-error-references/fail/abc46381e4e6bcca.js.swc-stderr @@ -1,10 +1,5 @@ x Invalid character in identifier ,-[$DIR/tests/test262-parser/fail/abc46381e4e6bcca.js:1:1] 1 | var \uD83B\uDE00 - : ^^^^^^ - `---- - x Invalid character in identifier - ,-[$DIR/tests/test262-parser/fail/abc46381e4e6bcca.js:1:1] - 1 | var \uD83B\uDE00 - : ^^^^^^ + : ^^^^^^^^^^^^ `---- diff --git a/crates/swc_ecma_parser/tests/test262-error-references/fail/c3afed3cb0fb92ab.js.swc-stderr b/crates/swc_ecma_parser/tests/test262-error-references/fail/c3afed3cb0fb92ab.js.swc-stderr index 8318446b25bd..61fbd2052130 100644 --- a/crates/swc_ecma_parser/tests/test262-error-references/fail/c3afed3cb0fb92ab.js.swc-stderr +++ b/crates/swc_ecma_parser/tests/test262-error-references/fail/c3afed3cb0fb92ab.js.swc-stderr @@ -1,9 +1,5 @@ x Bad character escape sequence, expected 4 hex characters ,-[$DIR/tests/test262-parser/fail/c3afed3cb0fb92ab.js:1:1] 1 | \uD800\u - `---- - x Invalid character in identifier - ,-[$DIR/tests/test262-parser/fail/c3afed3cb0fb92ab.js:1:1] - 1 | \uD800\u - : ^^^^^^ + : ^^^^^^ `---- diff --git a/crates/swc_ecma_parser/tests/test262-error-references/fail/d4cf8ae9018f6a28.js.swc-stderr b/crates/swc_ecma_parser/tests/test262-error-references/fail/d4cf8ae9018f6a28.js.swc-stderr index f6fa9b0e5385..6adcb8b78dc2 100644 --- a/crates/swc_ecma_parser/tests/test262-error-references/fail/d4cf8ae9018f6a28.js.swc-stderr +++ b/crates/swc_ecma_parser/tests/test262-error-references/fail/d4cf8ae9018f6a28.js.swc-stderr @@ -1,10 +1,5 @@ x Invalid character in identifier ,-[$DIR/tests/test262-parser/fail/d4cf8ae9018f6a28.js:1:1] 1 | \uD800\uDC00 - : ^^^^^^ - `---- - x Invalid character in identifier - ,-[$DIR/tests/test262-parser/fail/d4cf8ae9018f6a28.js:1:1] - 1 | \uD800\uDC00 - : ^^^^^^ + : ^^^^^^^^^^^^ `---- diff --git a/crates/swc_ecma_parser/tests/tsc/parserS7.2_A1.5_T2.json b/crates/swc_ecma_parser/tests/tsc/parserS7.2_A1.5_T2.json index bf787279517d..1b57bdb43fb9 100644 --- a/crates/swc_ecma_parser/tests/tsc/parserS7.2_A1.5_T2.json +++ b/crates/swc_ecma_parser/tests/tsc/parserS7.2_A1.5_T2.json @@ -125,7 +125,7 @@ "start": 380, "end": 442 }, - "value": "#1: eval(\"\\u00A0var x\\u00A0= 1\\u00A0\"); x === 1. Actual: ", + "value": "#1: eval(\"\\\\u00A0var x\\\\u00A0= 1\\\\u00A0\"); x === 1. Actual: ", "raw": "'#1: eval(\"\\\\u00A0var x\\\\u00A0= 1\\\\u00A0\"); x === 1. Actual: '" }, "right": { diff --git a/crates/swc_ecma_parser/tests/tsc/parserUnicode1.json b/crates/swc_ecma_parser/tests/tsc/parserUnicode1.json index 522dc3df44f3..61292dbcb83b 100644 --- a/crates/swc_ecma_parser/tests/tsc/parserUnicode1.json +++ b/crates/swc_ecma_parser/tests/tsc/parserUnicode1.json @@ -2,14 +2,14 @@ "type": "Script", "span": { "start": 1, - "end": 196 + "end": 194 }, "body": [ { "type": "TryStatement", "span": { "start": 1, - "end": 196 + "end": 194 }, "block": { "type": "BlockStatement", @@ -139,7 +139,7 @@ "start": 57, "end": 101 }, - "value": "#6.1: var \\u0078x = 1; xx === 6. Actual: ", + "value": "#6.1: var \\\\u0078x = 1; xx === 6. Actual: ", "raw": "'#6.1: var \\\\u0078x = 1; xx === 6. Actual: '" }, "right": { @@ -175,7 +175,7 @@ "type": "CatchClause", "span": { "start": 117, - "end": 196 + "end": 194 }, "param": { "type": "Identifier", @@ -192,28 +192,28 @@ "type": "BlockStatement", "span": { "start": 127, - "end": 196 + "end": 194 }, "ctxt": 0, "stmts": [ { "type": "ExpressionStatement", "span": { - "start": 133, - "end": 193 + "start": 131, + "end": 191 }, "expression": { "type": "CallExpression", "span": { - "start": 133, - "end": 192 + "start": 131, + "end": 190 }, "ctxt": 0, "callee": { "type": "Identifier", "span": { - "start": 133, - "end": 139 + "start": 131, + "end": 137 }, "ctxt": 0, "value": "$ERROR", @@ -225,30 +225,30 @@ "expression": { "type": "BinaryExpression", "span": { - "start": 140, - "end": 191 + "start": 138, + "end": 189 }, "operator": "+", "left": { "type": "StringLiteral", "span": { - "start": 140, - "end": 184 + "start": 138, + "end": 182 }, - "value": "#6.2: var \\u0078x = 1; xx === 6. Actual: ", + "value": "#6.2: var \\\\u0078x = 1; xx === 6. Actual: ", "raw": "'#6.2: var \\\\u0078x = 1; xx === 6. Actual: '" }, "right": { "type": "ParenthesisExpression", "span": { - "start": 187, - "end": 191 + "start": 185, + "end": 189 }, "expression": { "type": "Identifier", "span": { - "start": 188, - "end": 190 + "start": 186, + "end": 188 }, "ctxt": 0, "value": "xx", diff --git a/crates/swc_ecma_parser/tests/tsc/parserUnicode1.ts b/crates/swc_ecma_parser/tests/tsc/parserUnicode1.ts index 4d4793716dbb..544d1e705af9 100644 --- a/crates/swc_ecma_parser/tests/tsc/parserUnicode1.ts +++ b/crates/swc_ecma_parser/tests/tsc/parserUnicode1.ts @@ -7,6 +7,6 @@ try { } } catch (e) { - $ERROR('#6.2: var \\u0078x = 1; xx === 6. Actual: ' + (xx)); + $ERROR('#6.2: var \\u0078x = 1; xx === 6. Actual: ' + (xx)); -} \ No newline at end of file +} diff --git a/crates/swc_ecma_parser/tests/tsc/scannerS7.2_A1.5_T2.json b/crates/swc_ecma_parser/tests/tsc/scannerS7.2_A1.5_T2.json index bf787279517d..1b57bdb43fb9 100644 --- a/crates/swc_ecma_parser/tests/tsc/scannerS7.2_A1.5_T2.json +++ b/crates/swc_ecma_parser/tests/tsc/scannerS7.2_A1.5_T2.json @@ -125,7 +125,7 @@ "start": 380, "end": 442 }, - "value": "#1: eval(\"\\u00A0var x\\u00A0= 1\\u00A0\"); x === 1. Actual: ", + "value": "#1: eval(\"\\\\u00A0var x\\\\u00A0= 1\\\\u00A0\"); x === 1. Actual: ", "raw": "'#1: eval(\"\\\\u00A0var x\\\\u00A0= 1\\\\u00A0\"); x === 1. Actual: '" }, "right": { diff --git a/crates/swc_ecma_parser/tests/typescript/next/stack-overflow/1/input.ts.json b/crates/swc_ecma_parser/tests/typescript/next/stack-overflow/1/input.ts.json index 77ae320ac77c..3d6f26def0ee 100644 --- a/crates/swc_ecma_parser/tests/typescript/next/stack-overflow/1/input.ts.json +++ b/crates/swc_ecma_parser/tests/typescript/next/stack-overflow/1/input.ts.json @@ -42779,7 +42779,7 @@ "start": 37860, "end": 37869 }, - "value": "\\u0026", + "value": "\\\\u0026", "raw": "\"\\\\u0026\"" } }, @@ -42800,7 +42800,7 @@ "start": 37892, "end": 37901 }, - "value": "\\u003e", + "value": "\\\\u003e", "raw": "\"\\\\u003e\"" } }, @@ -42821,7 +42821,7 @@ "start": 37924, "end": 37933 }, - "value": "\\u003c", + "value": "\\\\u003c", "raw": "\"\\\\u003c\"" } }, @@ -42842,7 +42842,7 @@ "start": 37961, "end": 37970 }, - "value": "\\u2028", + "value": "\\\\u2028", "raw": "\"\\\\u2028\"" } }, @@ -42863,7 +42863,7 @@ "start": 37998, "end": 38007 }, - "value": "\\u2029", + "value": "\\\\u2029", "raw": "\"\\\\u2029\"" } } diff --git a/crates/swc_ecma_preset_env/src/corejs2/entry.rs b/crates/swc_ecma_preset_env/src/corejs2/entry.rs index ebdab7ea7162..082c4a25d668 100644 --- a/crates/swc_ecma_preset_env/src/corejs2/entry.rs +++ b/crates/swc_ecma_preset_env/src/corejs2/entry.rs @@ -71,10 +71,10 @@ impl VisitMut for Entry { noop_visit_mut_type!(fail); fn visit_mut_import_decl(&mut self, i: &mut ImportDecl) { - let remove = i.specifiers.is_empty() && self.add_all(&i.src.value); + let remove = i.specifiers.is_empty() && self.add_all(&i.src.value.to_string_lossy()); if remove { - i.src.value = atom!(""); + i.src.value = atom!("").into(); i.src.span = DUMMY_SP; } } diff --git a/crates/swc_ecma_preset_env/src/corejs2/mod.rs b/crates/swc_ecma_preset_env/src/corejs2/mod.rs index 9c4bc6326f31..9fd6d8da7409 100644 --- a/crates/swc_ecma_preset_env/src/corejs2/mod.rs +++ b/crates/swc_ecma_preset_env/src/corejs2/mod.rs @@ -232,7 +232,7 @@ impl Visit for UsageVisitor { } MemberProp::Computed(ComputedPropName { expr, .. }) => { if let Expr::Lit(Lit::Str(Str { value, .. })) = &**expr { - if let Some(imports) = data::instance_properties_get(value) { + if let Some(imports) = data::instance_properties_get(&value.to_string_lossy()) { self.add(imports); } } @@ -244,7 +244,9 @@ impl Visit for UsageVisitor { match &node.prop { MemberProp::Computed(ComputedPropName { expr, .. }) => { if let Expr::Lit(Lit::Str(Str { value, .. })) = &**expr { - if let Some(imports) = data::instance_properties_get(value) { + if let Some(imports) = + data::instance_properties_get(&value.to_string_lossy()) + { self.add(imports); } } diff --git a/crates/swc_ecma_preset_env/src/corejs3/entry.rs b/crates/swc_ecma_preset_env/src/corejs3/entry.rs index 08691095134e..dba968758ff6 100644 --- a/crates/swc_ecma_preset_env/src/corejs3/entry.rs +++ b/crates/swc_ecma_preset_env/src/corejs3/entry.rs @@ -101,11 +101,11 @@ impl Entry { impl VisitMut for Entry { fn visit_mut_import_decl(&mut self, i: &mut ImportDecl) { - let remove = i.specifiers.is_empty() && self.add(&i.src.value); + let remove = i.specifiers.is_empty() && self.add(&i.src.value.to_string_lossy()); if remove { i.src.span = DUMMY_SP; - i.src.value = atom!(""); + i.src.value = atom!("").into(); } } } diff --git a/crates/swc_ecma_preset_env/src/corejs3/usage.rs b/crates/swc_ecma_preset_env/src/corejs3/usage.rs index 7043c2a29d47..87f96dc59eb0 100644 --- a/crates/swc_ecma_preset_env/src/corejs3/usage.rs +++ b/crates/swc_ecma_preset_env/src/corejs3/usage.rs @@ -187,7 +187,8 @@ impl Visit for UsageVisitor { // 'entries' in Object // 'entries' in [1, 2, 3] if let Expr::Lit(Lit::Str(s)) = &*e.left { - self.add_property_deps(&e.right, &s.value); + let prop_atom = s.value.to_atom_lossy(); + self.add_property_deps(&e.right, prop_atom.as_ref()); } } } @@ -237,7 +238,8 @@ impl Visit for UsageVisitor { e.obj.visit_with(self); if let MemberProp::Computed(c) = &e.prop { if let Expr::Lit(Lit::Str(s)) = &*c.expr { - self.add_property_deps(&e.obj, &s.value); + let prop_atom = s.value.to_atom_lossy(); + self.add_property_deps(&e.obj, prop_atom.as_ref()); } c.visit_with(self); } diff --git a/crates/swc_ecma_preset_env/src/lib.rs b/crates/swc_ecma_preset_env/src/lib.rs index 975f75e6e32a..77ba6e1eaf4c 100644 --- a/crates/swc_ecma_preset_env/src/lib.rs +++ b/crates/swc_ecma_preset_env/src/lib.rs @@ -533,7 +533,7 @@ impl VisitMut for Polyfills { src: Str { span: DUMMY_SP, raw: None, - value: src, + value: src.into(), } .into(), type_only: false, @@ -553,7 +553,7 @@ impl VisitMut for Polyfills { src: Str { span: DUMMY_SP, raw: None, - value: src, + value: src.into(), } .into(), type_only: false, @@ -565,7 +565,7 @@ impl VisitMut for Polyfills { ); } - m.body.retain(|item| !matches!(item, ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { src, .. })) if src.span == DUMMY_SP && src.value == atom!(""))); + m.body.retain(|item| !matches!(item, ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { src, .. })) if src.span == DUMMY_SP && src.value .is_empty())); } fn visit_mut_script(&mut self, m: &mut Script) { @@ -589,7 +589,7 @@ impl VisitMut for Polyfills { .as_callee(), args: vec![Str { span: DUMMY_SP, - value: src, + value: src.into(), raw: None, } .as_arg()], @@ -617,7 +617,7 @@ impl VisitMut for Polyfills { .as_callee(), args: vec![Str { span: DUMMY_SP, - value: src, + value: src.into(), raw: None, } .as_arg()], diff --git a/crates/swc_ecma_preset_env/src/node_colon_prefix_strip.rs b/crates/swc_ecma_preset_env/src/node_colon_prefix_strip.rs index b1c1003b54cc..be71fd88425d 100644 --- a/crates/swc_ecma_preset_env/src/node_colon_prefix_strip.rs +++ b/crates/swc_ecma_preset_env/src/node_colon_prefix_strip.rs @@ -16,7 +16,7 @@ impl VisitMut for NodeColonPrefixStrip { noop_visit_mut_type!(); fn visit_mut_import_decl(&mut self, node: &mut ImportDecl) { - if let Some(value) = Self::strip_node_colon(&node.src.value) { + if let Some(value) = Self::strip_node_colon(&node.src.value.to_string_lossy()) { node.src.value = value.into(); node.src.raw = None; } @@ -26,14 +26,14 @@ impl VisitMut for NodeColonPrefixStrip { let Some(src) = &mut node.src else { return; }; - if let Some(value) = Self::strip_node_colon(&src.value) { + if let Some(value) = Self::strip_node_colon(&src.value.to_string_lossy()) { src.value = value.into(); src.raw = None; } } fn visit_mut_export_all(&mut self, node: &mut ExportAll) { - if let Some(value) = Self::strip_node_colon(&node.src.value) { + if let Some(value) = Self::strip_node_colon(&node.src.value.to_string_lossy()) { node.src.value = value.into(); node.src.raw = None; } @@ -57,7 +57,7 @@ impl VisitMut for NodeColonPrefixStrip { .and_then(|arg| arg.expr.as_mut_lit()) .and_then(|lit| lit.as_mut_str()) { - if let Some(value) = Self::strip_node_colon(&source.value) { + if let Some(value) = Self::strip_node_colon(&source.value.to_string_lossy()) { source.value = value.into(); source.raw = None; } diff --git a/crates/swc_ecma_quote_macros/src/ast/lit.rs b/crates/swc_ecma_quote_macros/src/ast/lit.rs index f27ddd6ff6e7..0f5fc8bb34c5 100644 --- a/crates/swc_ecma_quote_macros/src/ast/lit.rs +++ b/crates/swc_ecma_quote_macros/src/ast/lit.rs @@ -1,7 +1,7 @@ use proc_macro2::Span; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_ecma_ast::*; -use syn::{parse_quote, ExprLit, LitBool, LitFloat}; +use syn::{parse_quote, ExprLit, LitBool, LitByteStr, LitFloat}; use super::ToCode; use crate::{builder::Builder, ctxt::Ctx}; @@ -11,9 +11,11 @@ fail_todo!(JSXText); impl ToCode for Str { fn to_code(&self, cx: &crate::ctxt::Ctx) -> syn::Expr { - if let Some(var_name) = self.value.strip_prefix('$') { - if let Some(var) = cx.var(crate::ctxt::VarPos::Str, var_name) { - return var.get_expr(); + if let Some(var_name) = self.value.as_str() { + if let Some(var_name) = var_name.strip_prefix('$') { + if let Some(var) = cx.var(crate::ctxt::VarPos::Str, var_name) { + return var.get_expr(); + } } } @@ -36,6 +38,15 @@ impl ToCode for Atom { } } +impl ToCode for Wtf8Atom { + fn to_code(&self, _: &Ctx) -> syn::Expr { + let bytes_literal = LitByteStr::new(self.as_bytes(), Span::call_site()); + parse_quote!(swc_atoms::wtf8::Wtf8Atom::from(unsafe { + swc_atoms::wtf8::Wtf8::from_bytes_unchecked(#bytes_literal) + })) + } +} + impl ToCode for bool { fn to_code(&self, _: &Ctx) -> syn::Expr { syn::Expr::Lit(ExprLit { diff --git a/crates/swc_ecma_transforms_base/tests/fixer_test262.rs b/crates/swc_ecma_transforms_base/tests/fixer_test262.rs index 5dc2ceda1979..3e005752899a 100644 --- a/crates/swc_ecma_transforms_base/tests/fixer_test262.rs +++ b/crates/swc_ecma_transforms_base/tests/fixer_test262.rs @@ -320,7 +320,7 @@ impl Fold for Normalizer { match name { PropName::Ident(i) => PropName::Str(Str { raw: None, - value: i.sym, + value: i.sym.into(), span: i.span, }), PropName::Num(n) => { @@ -335,7 +335,7 @@ impl Fold for Normalizer { }; PropName::Str(Str { raw: None, - value, + value: value.into(), span: n.span, }) } diff --git a/crates/swc_ecma_transforms_classes/src/super_field.rs b/crates/swc_ecma_transforms_classes/src/super_field.rs index 0551f2933b69..bee3cbb70d22 100644 --- a/crates/swc_ecma_transforms_classes/src/super_field.rs +++ b/crates/swc_ecma_transforms_classes/src/super_field.rs @@ -496,7 +496,7 @@ fn prop_arg(prop: SuperProp) -> Expr { }) => Lit::Str(Str { span, raw: None, - value, + value: value.into(), }) .into(), SuperProp::Computed(c) => *c.expr, diff --git a/crates/swc_ecma_transforms_module/src/amd.rs b/crates/swc_ecma_transforms_module/src/amd.rs index 018b0a5f8e76..9d379917da7b 100644 --- a/crates/swc_ecma_transforms_module/src/amd.rs +++ b/crates/swc_ecma_transforms_module/src/amd.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use anyhow::Context; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -267,7 +269,10 @@ where args.get_mut(0).into_iter().for_each(|x| { if let ExprOrSpread { spread: None, expr } = x { if let Expr::Lit(Lit::Str(Str { value, raw, .. })) = &mut **expr { - *value = self.resolver.resolve(value.clone()); + *value = self + .resolver + .resolve(value.to_atom_lossy().into_owned()) + .into(); *raw = None; } } @@ -292,9 +297,9 @@ where .unwrap_or_default() => { let p = match prop { - MemberProp::Ident(IdentName { sym, .. }) => &**sym, + MemberProp::Ident(IdentName { sym, .. }) => Cow::Borrowed(&**sym), MemberProp::Computed(ComputedPropName { expr, .. }) => match &**expr { - Expr::Lit(Lit::Str(s)) => &s.value, + Expr::Lit(Lit::Str(s)) => s.value.to_string_lossy(), _ => return, }, MemberProp::PrivateName(..) => return, @@ -303,7 +308,7 @@ where }; self.found_import_meta = true; - match p { + match &*p { // new URL(module.uri, document.baseURI).href "url" => { *n = amd_import_meta_url(*span, self.module()); @@ -319,7 +324,7 @@ where MemberProp::Computed(ComputedPropName { expr, .. }) => { match &mut **expr { Expr::Lit(Lit::Str(s)) => { - s.value = atom!("toUrl"); + s.value = atom!("toUrl").into(); s.raw = None; } _ => unreachable!(), @@ -345,7 +350,7 @@ where MemberProp::Computed(ComputedPropName { expr, .. }) => { match &mut **expr { Expr::Lit(Lit::Str(s)) => { - s.value = atom!("toUrl"); + s.value = atom!("toUrl").into(); s.raw = None; } _ => unreachable!(), diff --git a/crates/swc_ecma_transforms_module/src/common_js.rs b/crates/swc_ecma_transforms_module/src/common_js.rs index 5ae4ab524f74..b8cdeabd3bcc 100644 --- a/crates/swc_ecma_transforms_module/src/common_js.rs +++ b/crates/swc_ecma_transforms_module/src/common_js.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use rustc_hash::FxHashSet; use swc_common::{ source_map::PURE_SP, util::take::Take, Mark, Span, Spanned, SyntaxContext, DUMMY_SP, @@ -188,7 +190,10 @@ impl VisitMut for Cjs { if let Expr::Lit(Lit::Str(Str { value, raw, .. })) = &mut **expr { is_lit_path = true; - *value = self.resolver.resolve(value.clone()); + *value = self + .resolver + .resolve(value.to_atom_lossy().into_owned()) + .into(); *raw = None; } } @@ -213,9 +218,9 @@ impl VisitMut for Cjs { .unwrap_or_default() => { let p = match prop { - MemberProp::Ident(IdentName { sym, .. }) => &**sym, + MemberProp::Ident(IdentName { sym, .. }) => Cow::Borrowed(&**sym), MemberProp::Computed(ComputedPropName { expr, .. }) => match &**expr { - Expr::Lit(Lit::Str(s)) => &s.value, + Expr::Lit(Lit::Str(s)) => s.value.to_string_lossy(), _ => return, }, MemberProp::PrivateName(..) => return, @@ -223,7 +228,7 @@ impl VisitMut for Cjs { _ => panic!("unable to access unknown nodes"), }; - match p { + match &*p { "url" => { let require = quote_ident!( SyntaxContext::empty().apply_mark(self.unresolved_mark), @@ -443,9 +448,11 @@ impl Cjs { *has_ts_import_equals = true; - let require = self - .resolver - .make_require_call(self.unresolved_mark, src, src_span); + let require = self.resolver.make_require_call( + self.unresolved_mark, + src.to_atom_lossy().into_owned(), + src_span, + ); if is_export { // exports.foo = require("mod") diff --git a/crates/swc_ecma_transforms_module/src/import_analysis.rs b/crates/swc_ecma_transforms_module/src/import_analysis.rs index 25d960a40b9d..994beeaa9f9b 100644 --- a/crates/swc_ecma_transforms_module/src/import_analysis.rs +++ b/crates/swc_ecma_transforms_module/src/import_analysis.rs @@ -6,7 +6,7 @@ use swc_ecma_visit::{ noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitWith, }; -use crate::{module_decl_strip::LinkFlag, util::ImportInterop}; +use crate::{module_decl_strip::LinkFlag, util::ImportInterop, wtf8::str_to_atom}; pub fn import_analyzer(import_interop: ImportInterop, ignore_dynamic: bool) -> impl Pass { visit_mut_pass(ImportAnalyzer { @@ -80,15 +80,17 @@ impl Visit for ImportAnalyzer { } fn visit_import_decl(&mut self, n: &ImportDecl) { - let flag = self.flag_record.entry(n.src.value.clone()).or_default(); + let src = str_to_atom(&n.src); + let flag = self.flag_record.entry(src).or_default(); for s in &n.specifiers { *flag |= s.into(); } } fn visit_named_export(&mut self, n: &NamedExport) { - if let Some(src) = n.src.clone() { - let flag = self.flag_record.entry(src.value).or_default(); + if let Some(src) = &n.src { + let src_atom = str_to_atom(src); + let flag = self.flag_record.entry(src_atom).or_default(); for s in &n.specifiers { *flag |= s.into(); } @@ -96,7 +98,8 @@ impl Visit for ImportAnalyzer { } fn visit_export_all(&mut self, n: &ExportAll) { - *self.flag_record.entry(n.src.value.clone()).or_default() |= LinkFlag::EXPORT_STAR; + let src = str_to_atom(&n.src); + *self.flag_record.entry(src).or_default() |= LinkFlag::EXPORT_STAR; } fn visit_import(&mut self, _: &Import) { diff --git a/crates/swc_ecma_transforms_module/src/lib.rs b/crates/swc_ecma_transforms_module/src/lib.rs index 714d652e7cb3..045f8f4c7e33 100644 --- a/crates/swc_ecma_transforms_module/src/lib.rs +++ b/crates/swc_ecma_transforms_module/src/lib.rs @@ -21,6 +21,7 @@ pub mod rewriter; pub mod system_js; mod top_level_this; pub mod umd; +pub(crate) mod wtf8; #[derive(Debug, Default, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/swc_ecma_transforms_module/src/module_decl_strip.rs b/crates/swc_ecma_transforms_module/src/module_decl_strip.rs index 54cc9c87ae35..10552d64656f 100644 --- a/crates/swc_ecma_transforms_module/src/module_decl_strip.rs +++ b/crates/swc_ecma_transforms_module/src/module_decl_strip.rs @@ -112,7 +112,7 @@ impl VisitMut for ModuleDeclStrip { } = n.take(); self.link - .entry(src.value) + .entry(src.value.to_atom_lossy().into_owned()) .or_default() .mut_dummy_span(src.span) .extend(specifiers.into_iter().map(From::from)); @@ -168,7 +168,7 @@ impl VisitMut for ModuleDeclStrip { if let Some(src) = src { self.link - .entry(src.value) + .entry(src.value.to_atom_lossy().into_owned()) .or_default() .mut_dummy_span(src.span) .extend(specifiers.into_iter().map(From::from)); @@ -195,9 +195,10 @@ impl VisitMut for ModuleDeclStrip { ModuleExportName::Ident(Ident { ctxt, span, sym, .. }) => (sym, (span, ctxt)), - ModuleExportName::Str(Str { span, value, .. }) => { - (value, (span, Default::default())) - } + ModuleExportName::Str(Str { span, value, .. }) => ( + value.to_atom_lossy().into_owned(), + (span, Default::default()), + ), #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), }; @@ -295,7 +296,7 @@ impl VisitMut for ModuleDeclStrip { } = *n.take().src; self.link - .entry(src_key) + .entry(src_key.to_atom_lossy().into_owned()) .or_default() .mut_dummy_span(src_span) .insert(LinkSpecifier::ExportStar); @@ -335,7 +336,7 @@ impl VisitMut for ModuleDeclStrip { } self.link - .entry(src_key.clone()) + .entry(src_key.to_atom_lossy().into_owned()) .or_default() .mut_dummy_span(*span) .insert(LinkSpecifier::ImportEqual(id.to_id())); @@ -426,21 +427,26 @@ impl From for LinkSpecifier { ImportSpecifier::Named(ImportNamedSpecifier { is_type_only: false, local, - imported: - Some(ModuleExportName::Ident(Ident { sym: s, .. })) - | Some(ModuleExportName::Str(Str { value: s, .. })), + imported: Some(ModuleExportName::Ident(Ident { sym: s, .. })), .. }) if &*s == "default" => Self::ImportDefault(local.to_id()), + ImportSpecifier::Named(ImportNamedSpecifier { + is_type_only: false, + local, + imported: Some(ModuleExportName::Str(Str { value: s, .. })), + .. + }) if &s == "default" => Self::ImportDefault(local.to_id()), + ImportSpecifier::Named(ImportNamedSpecifier { is_type_only: false, local, imported, .. }) => { - let imported = imported.map(|e| match e { - ModuleExportName::Ident(Ident { sym, .. }) => sym, - ModuleExportName::Str(Str { value, .. }) => value, + let imported = imported.and_then(|e| match e { + ModuleExportName::Ident(Ident { sym, .. }) => Some(sym), + ModuleExportName::Str(Str { value, .. }) => value.as_atom().cloned(), #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), }); @@ -460,11 +466,17 @@ impl From for LinkSpecifier { match e { ExportSpecifier::Namespace(ExportNamespaceSpecifier { name: - ModuleExportName::Ident(Ident { span, sym, .. }) - | ModuleExportName::Str(Str { + ModuleExportName::Str(Str { span, value: sym, .. }), .. + }) => Self::ExportStarAs( + sym.to_atom_lossy().into_owned(), + (span, SyntaxContext::empty()), + ), + ExportSpecifier::Namespace(ExportNamespaceSpecifier { + name: ModuleExportName::Ident(Ident { span, sym, .. }), + .. }) => Self::ExportStarAs(sym, (span, SyntaxContext::empty())), ExportSpecifier::Default(ExportDefaultSpecifier { exported }) => { @@ -483,19 +495,29 @@ impl From for LinkSpecifier { .. }) => { let orig = match orig { - ModuleExportName::Ident(Ident { span, sym, .. }) - | ModuleExportName::Str(Str { + ModuleExportName::Ident(Ident { span, sym, .. }) => { + (sym, (span, SyntaxContext::empty().apply_mark(Mark::new()))) + } + ModuleExportName::Str(Str { span, value: sym, .. - }) => (sym, (span, SyntaxContext::empty().apply_mark(Mark::new()))), + }) => ( + sym.to_atom_lossy().into_owned(), + (span, SyntaxContext::empty().apply_mark(Mark::new())), + ), #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), }; let exported = exported.map(|exported| match exported { - ModuleExportName::Ident(Ident { span, sym, .. }) - | ModuleExportName::Str(Str { + ModuleExportName::Ident(Ident { span, sym, .. }) => { + (sym, (span, SyntaxContext::empty().apply_mark(Mark::new()))) + } + ModuleExportName::Str(Str { span, value: sym, .. - }) => (sym, (span, SyntaxContext::empty().apply_mark(Mark::new()))), + }) => ( + sym.to_atom_lossy().into_owned(), + (span, SyntaxContext::empty().apply_mark(Mark::new())), + ), #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), }); @@ -579,12 +601,16 @@ impl From<&ImportSpecifier> for LinkFlag { ImportSpecifier::Named(ImportNamedSpecifier { is_type_only: false, - imported: - Some(ModuleExportName::Ident(Ident { sym: default, .. })) - | Some(ModuleExportName::Str(Str { value: default, .. })), + imported: Some(ModuleExportName::Ident(Ident { sym: default, .. })), .. }) if &**default == "default" => Self::DEFAULT, + ImportSpecifier::Named(ImportNamedSpecifier { + is_type_only: false, + imported: Some(ModuleExportName::Str(Str { value: default, .. })), + .. + }) if default == "default" => Self::DEFAULT, + ImportSpecifier::Named(ImportNamedSpecifier { is_type_only: false, .. @@ -602,11 +628,16 @@ impl From<&ExportSpecifier> for LinkFlag { // https://github.com/tc39/proposal-export-default-from ExportSpecifier::Default(..) => Self::DEFAULT, + + ExportSpecifier::Named(ExportNamedSpecifier { + is_type_only: false, + orig: ModuleExportName::Str(Str { value: s, .. }), + .. + }) if s == "default" => Self::DEFAULT, + ExportSpecifier::Named(ExportNamedSpecifier { is_type_only: false, - orig: - ModuleExportName::Ident(Ident { sym: s, .. }) - | ModuleExportName::Str(Str { value: s, .. }), + orig: ModuleExportName::Ident(Ident { sym: s, .. }), .. }) if &**s == "default" => Self::DEFAULT, diff --git a/crates/swc_ecma_transforms_module/src/path.rs b/crates/swc_ecma_transforms_module/src/path.rs index 1900e40a77d0..4b0f7df47f32 100644 --- a/crates/swc_ecma_transforms_module/src/path.rs +++ b/crates/swc_ecma_transforms_module/src/path.rs @@ -56,7 +56,7 @@ impl Resolver { args: vec![Lit::Str(Str { span: src_span, raw: None, - value: src, + value: src.into(), }) .as_arg()], ..Default::default() diff --git a/crates/swc_ecma_transforms_module/src/rewriter/import_rewriter_swc.rs b/crates/swc_ecma_transforms_module/src/rewriter/import_rewriter_swc.rs index 0ad3971f9236..076a03244d62 100644 --- a/crates/swc_ecma_transforms_module/src/rewriter/import_rewriter_swc.rs +++ b/crates/swc_ecma_transforms_module/src/rewriter/import_rewriter_swc.rs @@ -5,7 +5,7 @@ use swc_common::FileName; use swc_ecma_ast::*; use swc_ecma_visit::{visit_mut_pass, VisitMut, VisitMutWith}; -use crate::path::ImportResolver; +use crate::{path::ImportResolver, wtf8::wtf8_to_cow_str}; /// Import rewriter, which rewrites imports as es modules. pub fn swc_import_rewriter(base: FileName, resolver: Arc) -> impl Pass { @@ -27,52 +27,65 @@ impl VisitMut for Rewriter { if let Some(ExprOrSpread { spread: None, expr }) = &mut e.args.get_mut(0) { if let Expr::Lit(Lit::Str(s)) = &mut **expr { + let spec = wtf8_to_cow_str(&s.value); let src = self .resolver - .resolve_import(&self.base, &s.value) - .with_context(|| format!("failed to resolve import `{}`", s.value)) + .resolve_import(&self.base, &spec) + .with_context(|| { + format!("failed to resolve import `{}`", s.value.to_string_lossy()) + }) .unwrap(); s.raw = None; - s.value = src; + s.value = src.into(); } } } fn visit_mut_import_decl(&mut self, i: &mut ImportDecl) { + let spec = wtf8_to_cow_str(&i.src.value); let src = self .resolver - .resolve_import(&self.base, &i.src.value) - .with_context(|| format!("failed to resolve import `{}`", i.src.value)) + .resolve_import(&self.base, &spec) + .with_context(|| { + format!( + "failed to resolve import `{}`", + i.src.value.to_string_lossy() + ) + }) .unwrap(); i.src.raw = None; - i.src.value = src; + i.src.value = src.into(); } fn visit_mut_named_export(&mut self, e: &mut NamedExport) { if let Some(src) = &mut e.src { + let spec = wtf8_to_cow_str(&src.value); let new = self .resolver - .resolve_import(&self.base, &src.value) - .with_context(|| format!("failed to resolve import `{}`", src.value)) + .resolve_import(&self.base, &spec) + .with_context(|| { + format!("failed to resolve import `{}`", src.value.to_string_lossy()) + }) .unwrap(); src.raw = None; - src.value = new; + src.value = new.into(); } } fn visit_mut_export_all(&mut self, n: &mut ExportAll) { let src = &mut n.src; + let spec = wtf8_to_cow_str(&src.value); let new = self .resolver - .resolve_import(&self.base, &src.value) - .with_context(|| format!("failed to resolve import `{}`", src.value)) + .resolve_import(&self.base, &spec) + .with_context(|| format!("failed to resolve import `{}`", src.value.to_string_lossy())) .unwrap(); src.raw = None; - src.value = new; + src.value = new.into(); } } diff --git a/crates/swc_ecma_transforms_module/src/rewriter/import_rewriter_typescript.rs b/crates/swc_ecma_transforms_module/src/rewriter/import_rewriter_typescript.rs index 6294817c63de..3ab53cd445a6 100644 --- a/crates/swc_ecma_transforms_module/src/rewriter/import_rewriter_typescript.rs +++ b/crates/swc_ecma_transforms_module/src/rewriter/import_rewriter_typescript.rs @@ -1,6 +1,6 @@ use std::path::Path; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::helper; @@ -32,7 +32,7 @@ impl VisitMut for Rewriter { if let Some(ExprOrSpread { spread: None, expr }) = &mut e.args.get_mut(0) { if let Expr::Lit(Lit::Str(s)) = &mut **expr { - if let Some(src) = get_output_extension(&s.value) { + if let Some(src) = get_output_extension_wtf8(&s.value) { s.raw = None; s.value = src; } @@ -52,7 +52,7 @@ impl VisitMut for Rewriter { } fn visit_mut_import_decl(&mut self, i: &mut ImportDecl) { - if let Some(src) = get_output_extension(&i.src.value) { + if let Some(src) = get_output_extension_wtf8(&i.src.value) { i.src.value = src; i.src.raw = None; } @@ -60,7 +60,7 @@ impl VisitMut for Rewriter { fn visit_mut_named_export(&mut self, e: &mut NamedExport) { if let Some(src) = &mut e.src { - if let Some(new_src) = get_output_extension(&src.value) { + if let Some(new_src) = get_output_extension_wtf8(&src.value) { src.value = new_src; src.raw = None; } @@ -68,7 +68,7 @@ impl VisitMut for Rewriter { } fn visit_mut_export_all(&mut self, n: &mut ExportAll) { - if let Some(new_src) = get_output_extension(&n.src.value) { + if let Some(new_src) = get_output_extension_wtf8(&n.src.value) { n.src.value = new_src; n.src.raw = None; } @@ -103,3 +103,8 @@ fn get_output_extension(specifier: &Atom) -> Option { Some(Atom::new(path.with_extension(ext).to_str()?)) } + +fn get_output_extension_wtf8(specifier: &Wtf8Atom) -> Option { + let normalized = specifier.to_atom_lossy(); + get_output_extension(normalized.as_ref()).map(Into::into) +} diff --git a/crates/swc_ecma_transforms_module/src/system_js.rs b/crates/swc_ecma_transforms_module/src/system_js.rs index 043defe2b8e0..03b682cac2c3 100644 --- a/crates/swc_ecma_transforms_module/src/system_js.rs +++ b/crates/swc_ecma_transforms_module/src/system_js.rs @@ -14,6 +14,7 @@ use crate::{ path::Resolver, top_level_this::top_level_this, util::{local_name_for_src, use_strict}, + wtf8::{normalize_wtf8_atom, wtf8_to_cow_str}, }; #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] @@ -640,13 +641,19 @@ impl Fold for SystemJs { ModuleItem::ModuleDecl(decl) => match decl { ModuleDecl::Import(import) => { let src = match &self.resolver { - Resolver::Real { resolver, base } => resolver - .resolve_import(base, &import.src.value) - .with_context(|| { - format!("failed to resolve import `{}`", import.src.value) - }) - .unwrap(), - Resolver::Default => import.src.value, + Resolver::Real { resolver, base } => { + let spec = wtf8_to_cow_str(&import.src.value); + resolver + .resolve_import(base, &spec) + .with_context(|| { + format!( + "failed to resolve import `{}`", + import.src.value.to_string_lossy() + ) + }) + .unwrap() + } + Resolver::Default => normalize_wtf8_atom(&import.src.value), }; let source_alias = local_name_for_src(&src); @@ -723,13 +730,19 @@ impl Fold for SystemJs { ModuleDecl::ExportNamed(decl) => match decl.src { Some(s) => { let src = match &self.resolver { - Resolver::Real { resolver, base } => resolver - .resolve_import(base, &s.value) - .with_context(|| { - format!("failed to resolve import `{}`", s.value) - }) - .unwrap(), - Resolver::Default => s.value, + Resolver::Real { resolver, base } => { + let spec = wtf8_to_cow_str(&s.value); + resolver + .resolve_import(base, &spec) + .with_context(|| { + format!( + "failed to resolve import `{}`", + s.value.to_string_lossy() + ) + }) + .unwrap() + } + Resolver::Default => normalize_wtf8_atom(&s.value), }; for specifier in decl.specifiers { let source_alias = local_name_for_src(&src); @@ -925,7 +938,7 @@ impl Fold for SystemJs { export_names: Vec::new(), export_values: Vec::new(), has_export_all: true, - src: decl.src.value, + src: normalize_wtf8_atom(&decl.src.value), setter_fn_stmts: Vec::new(), }); } @@ -1136,7 +1149,7 @@ impl Fold for SystemJs { fn get_module_export_name(module_export_name: &ModuleExportName) -> Id { match &module_export_name { ModuleExportName::Ident(ident) => ident.to_id(), - ModuleExportName::Str(s) => (s.value.clone(), SyntaxContext::empty()), + ModuleExportName::Str(s) => (s.value.to_atom_lossy().into_owned(), SyntaxContext::empty()), #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), } @@ -1146,7 +1159,9 @@ fn get_module_export_name(module_export_name: &ModuleExportName) -> Id { fn get_module_export_expr(module_export_name: &ModuleExportName) -> Expr { match &module_export_name { ModuleExportName::Ident(ident) => ident.clone().into(), - ModuleExportName::Str(s) => Lit::Str(quote_str!(s.value.clone())).into(), + ModuleExportName::Str(s) => { + Lit::Str(quote_str!(s.value.to_atom_lossy().into_owned())).into() + } #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), } @@ -1158,7 +1173,7 @@ fn get_module_export_member_prop(module_export_name: &ModuleExportName) -> Membe ModuleExportName::Ident(ident) => MemberProp::Ident(ident.clone().into()), ModuleExportName::Str(s) => MemberProp::Computed(ComputedPropName { span: s.span, - expr: Lit::Str(quote_str!(s.value.clone())).into(), + expr: Lit::Str(quote_str!(s.value.to_atom_lossy().into_owned())).into(), }), #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), diff --git a/crates/swc_ecma_transforms_module/src/wtf8.rs b/crates/swc_ecma_transforms_module/src/wtf8.rs new file mode 100644 index 000000000000..e282d57fdd5c --- /dev/null +++ b/crates/swc_ecma_transforms_module/src/wtf8.rs @@ -0,0 +1,29 @@ +use std::borrow::Cow; + +use swc_atoms::{Atom, Wtf8Atom}; +use swc_ecma_ast::Str; + +/// Convert a WTF-8 atom into a UTF-8 [`Atom`] by either reusing the original +/// UTF-8 bytes or falling back to Rust's lossy conversion, which replaces only +/// ill-formed code units with the replacement character. +#[inline] +pub(crate) fn normalize_wtf8_atom(value: &Wtf8Atom) -> Atom { + value + .as_str() + .map(Atom::from) + .unwrap_or_else(|| Atom::from(value.to_string_lossy())) +} + +/// Convert a [`Str`] literal into a UTF-8 [`Atom`], preserving valid UTF-8 +/// data and lossily repairing ill-formed sequences when necessary. +#[inline] +pub(crate) fn str_to_atom(value: &Str) -> Atom { + normalize_wtf8_atom(&value.value) +} + +pub(crate) fn wtf8_to_cow_str(value: &Wtf8Atom) -> Cow<'_, str> { + value + .as_str() + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(value.to_string_lossy().into_owned())) +} diff --git a/crates/swc_ecma_transforms_optimization/src/const_modules.rs b/crates/swc_ecma_transforms_optimization/src/const_modules.rs index 07ddbe040fdb..749cf36e1d30 100644 --- a/crates/swc_ecma_transforms_optimization/src/const_modules.rs +++ b/crates/swc_ecma_transforms_optimization/src/const_modules.rs @@ -7,7 +7,7 @@ use bytes_str::BytesStr; use dashmap::DashMap; use once_cell::sync::Lazy; use rustc_hash::{FxBuildHasher, FxHashMap}; -use swc_atoms::{atom, Atom}; +use swc_atoms::{atom, Atom, Wtf8Atom}; use swc_common::{ errors::HANDLER, sync::Lrc, @@ -32,11 +32,11 @@ pub fn const_modules( .map(|(key, value)| { let value = parse_option(&cm, &key, value); - (key, value) + (key.into(), value) }) .collect(); - (src, map) + (src.into(), map) }) .collect(), scope: Default::default(), @@ -82,14 +82,14 @@ fn parse_option(cm: &SourceMap, name: &str, src: BytesStr) -> Arc { } struct ConstModules { - globals: HashMap>>, + globals: HashMap>>, scope: Scope, } #[derive(Default)] struct Scope { namespace: HashSet, - imported: HashMap>, + imported: HashMap>, } impl VisitMut for ConstModules { @@ -107,16 +107,16 @@ impl VisitMut for ConstModules { .imported .as_ref() .map(|m| match m { - ModuleExportName::Ident(id) => &id.sym, - ModuleExportName::Str(s) => &s.value, + ModuleExportName::Ident(id) => id.sym.clone().into(), + ModuleExportName::Str(s) => s.value.clone(), #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), }) - .unwrap_or(&s.local.sym); - let value = entry.get(imported).cloned().unwrap_or_else(|| { + .unwrap_or_else(|| s.local.sym.clone().into()); + let value = entry.get(&imported).cloned().unwrap_or_else(|| { panic!( - "The requested const_module `{}` does not provide an \ - export named `{}`", + "The requested const_module `{:?}` does not provide an \ + export named `{:?}`", import.src.value, imported ) }); @@ -126,17 +126,17 @@ impl VisitMut for ConstModules { self.scope.namespace.insert(s.local.to_id()); } ImportSpecifier::Default(ref s) => { - let imported = &s.local.sym; - let default_import_key = atom!("default"); + let imported: Wtf8Atom = s.local.sym.clone().into(); + let default_import_key: Wtf8Atom = atom!("default").into(); let value = entry.get(&default_import_key).cloned().unwrap_or_else(|| { panic!( - "The requested const_module `{}` does not provide \ + "The requested const_module `{:?}` does not provide \ default export", import.src.value ) }); - self.scope.imported.insert(imported.clone(), value); + self.scope.imported.insert(imported, value); } #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), @@ -161,7 +161,8 @@ impl VisitMut for ConstModules { fn visit_mut_expr(&mut self, n: &mut Expr) { match n { Expr::Ident(ref id @ Ident { ref sym, .. }) => { - if let Some(value) = self.scope.imported.get(sym) { + let sym_wtf8: Wtf8Atom = sym.clone().into(); + if let Some(value) = self.scope.imported.get(&sym_wtf8) { *n = (**value).clone(); return; } @@ -178,10 +179,10 @@ impl VisitMut for ConstModules { .filter(|member_obj| self.scope.namespace.contains(&member_obj.to_id())) .map(|member_obj| &member_obj.sym) { - let imported_name = match prop { - MemberProp::Ident(ref id) => &id.sym, + let imported_name: Wtf8Atom = match prop { + MemberProp::Ident(ref id) => id.sym.clone().into(), MemberProp::Computed(ref p) => match &*p.expr { - Expr::Lit(Lit::Str(s)) => &s.value, + Expr::Lit(Lit::Str(s)) => s.value.clone(), _ => return, }, MemberProp::PrivateName(..) => return, @@ -189,14 +190,15 @@ impl VisitMut for ConstModules { _ => panic!("unable to access unknown nodes"), }; + let module_name_wtf8: Wtf8Atom = module_name.clone().into(); let value = self .globals - .get(module_name) - .and_then(|entry| entry.get(imported_name)) + .get(&module_name_wtf8) + .and_then(|entry| entry.get(&imported_name)) .unwrap_or_else(|| { panic!( "The requested const_module `{module_name}` does not provide an \ - export named `{imported_name}`" + export named `{imported_name:?}`" ) }); @@ -214,7 +216,8 @@ impl VisitMut for ConstModules { fn visit_mut_prop(&mut self, n: &mut Prop) { match n { Prop::Shorthand(id) => { - if let Some(value) = self.scope.imported.get(&id.sym) { + let sym_wtf8: Wtf8Atom = id.sym.clone().into(); + if let Some(value) = self.scope.imported.get(&sym_wtf8) { *n = Prop::KeyValue(KeyValueProp { key: id.take().into(), value: Box::new((**value).clone()), diff --git a/crates/swc_ecma_transforms_optimization/src/inline_globals.rs b/crates/swc_ecma_transforms_optimization/src/inline_globals.rs index e79187abb5e0..c12234d493d9 100644 --- a/crates/swc_ecma_transforms_optimization/src/inline_globals.rs +++ b/crates/swc_ecma_transforms_optimization/src/inline_globals.rs @@ -1,5 +1,5 @@ use rustc_hash::{FxHashMap, FxHashSet}; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::sync::Lrc; use swc_ecma_ast::*; use swc_ecma_transforms_base::perf::{ParVisitMut, Parallel}; @@ -22,6 +22,12 @@ pub fn inline_globals( global_exprs: GlobalExprMap, typeofs: Lrc>, ) -> impl Pass { + let envs = Lrc::new( + envs.iter() + .map(|(k, v)| (k.clone().into(), v.clone())) + .collect(), + ); + visit_mut_pass(InlineGlobals { envs, globals, @@ -33,7 +39,7 @@ pub fn inline_globals( #[derive(Clone)] struct InlineGlobals { - envs: Lrc>, + envs: Lrc>, globals: Lrc>, global_exprs: Lrc, Expr>>, @@ -101,7 +107,7 @@ impl VisitMut for InlineGlobals { *expr = Lit::Str(Str { span: *span, raw: None, - value, + value: value.into(), }) .into(); } @@ -125,7 +131,8 @@ impl VisitMut for InlineGlobals { } MemberProp::Ident(IdentName { sym, .. }) => { - if let Some(env) = self.envs.get(sym) { + let sym_wtf8: Wtf8Atom = sym.clone().into(); + if let Some(env) = self.envs.get(&sym_wtf8) { *expr = env.clone(); } } diff --git a/crates/swc_ecma_transforms_optimization/src/json_parse.rs b/crates/swc_ecma_transforms_optimization/src/json_parse.rs index cfdeb321dec2..7996cd320975 100644 --- a/crates/swc_ecma_transforms_optimization/src/json_parse.rs +++ b/crates/swc_ecma_transforms_optimization/src/json_parse.rs @@ -1,4 +1,5 @@ use serde_json::Value; +use swc_atoms::Wtf8Atom; use swc_common::{util::take::Take, Spanned, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::perf::Parallel; @@ -93,6 +94,28 @@ impl VisitMut for JsonParse { } } +/// Converts a Wtf8Atom to a JSON-safe string, escaping lone surrogates +fn wtf8_to_json_string(value: &Wtf8Atom) -> String { + if let Some(s) = value.as_str() { + // Fast path: valid UTF-8 + return s.to_string(); + } + + // Slow path: contains lone surrogates, need to escape them + let mut result = String::with_capacity(value.len()); + for cp in value.as_wtf8().code_points() { + if let Some(ch) = cp.to_char() { + // Valid Rust char, push directly + result.push(ch); + } else { + // Lone surrogate - escape as \uXXXX + use std::fmt::Write; + write!(&mut result, "\\u{:04X}", cp.to_u32()).unwrap(); + } + } + result +} + fn jsonify(e: Expr) -> Value { match e { Expr::Object(obj) => Value::Object( @@ -105,7 +128,7 @@ fn jsonify(e: Expr) -> Value { .map(|p: KeyValueProp| { let value = jsonify(*p.value); let key = match p.key { - PropName::Str(s) => s.value.to_string(), + PropName::Str(s) => wtf8_to_json_string(&s.value), PropName::Ident(id) => id.sym.to_string(), PropName::Num(n) => format!("{}", n.value), _ => unreachable!(), @@ -120,7 +143,7 @@ fn jsonify(e: Expr) -> Value { .map(|v| jsonify(*v.unwrap().expr)) .collect(), ), - Expr::Lit(Lit::Str(Str { value, .. })) => Value::String(value.to_string()), + Expr::Lit(Lit::Str(Str { value, .. })) => Value::String(wtf8_to_json_string(&value)), Expr::Lit(Lit::Num(Number { value, .. })) => { if value.fract() == 0.0 { Value::Number((value as i64).into()) @@ -137,7 +160,7 @@ fn jsonify(e: Expr) -> Value { Some(TplElement { cooked: Some(value), .. - }) => value.to_string(), + }) => wtf8_to_json_string(value), _ => String::new(), }), _ => unreachable!("jsonify: Expr {:?} cannot be converted to json", e), diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index eeaf05163d78..55a19fbd6fde 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -1,6 +1,10 @@ use std::{borrow::Cow, iter, iter::once}; -use swc_atoms::{atom, Atom}; +use swc_atoms::{ + atom, + wtf8::{CodePoint, Wtf8, Wtf8Buf}, + Atom, +}; use swc_common::{ pass::{CompilerPass, Repeated}, util::take::Take, @@ -582,39 +586,16 @@ where ctx.preserve_effects(span, Lit::Bool(Bool { value, span }).into(), orig) } -fn nth_char(s: &str, mut idx: usize) -> Option> { - if s.chars().any(|c| c.len_utf16() > 1) { - return None; - } - - if !s.contains("\\ud") && !s.contains("\\uD") { - return Some(Cow::Owned(s.chars().nth(idx).unwrap().to_string())); +/// Gets the `idx`-th UTF-16 code unit from the given [Wtf8]. +/// Surrogate pairs are splitted into high and low surrogates and counted +/// separately. +fn nth_char(s: &Wtf8, idx: usize) -> CodePoint { + match s.to_ill_formed_utf16().nth(idx) { + Some(c) => + // SAFETY: `IllFormedUtf16CodeUnits` always returns code units in the range of UTF-16. + unsafe { CodePoint::from_u32_unchecked(c as u32) }, + None => unreachable!("string is too short"), } - - let mut iter = s.chars().peekable(); - - while let Some(c) = iter.next() { - if c == '\\' && iter.peek().copied() == Some('u') { - if idx == 0 { - let mut buf = String::new(); - buf.push('\\'); - buf.extend(iter.take(5)); - return Some(Cow::Owned(buf)); - } else { - for _ in 0..5 { - iter.next(); - } - } - } - - if idx == 0 { - return Some(Cow::Owned(c.to_string())); - } - - idx -= 1; - } - - unreachable!("string is too short") } fn need_zero_for_this(e: &Expr) -> bool { @@ -702,7 +683,7 @@ pub fn optimize_member_expr( _ => return, }; - #[derive(Clone, PartialEq)] + #[derive(Clone, PartialEq, Debug)] enum KnownOp { /// [a, b].length Len, @@ -773,6 +754,10 @@ pub fn optimize_member_expr( KnownOp::Len => { *changed = true; + let Some(value) = value.as_str() else { + return; + }; + *expr = Lit::Num(Number { value: value.chars().map(|c| c.len_utf16()).sum::() as _, span: *span, @@ -789,12 +774,13 @@ pub fn optimize_member_expr( return; } - let Some(value) = nth_char(value, idx as _) else { - return; - }; - + let c = nth_char(value, idx as _); *changed = true; + // `nth_char` always returns a code point within the UTF-16 range. + let mut value = Wtf8Buf::with_capacity(2); + value.push(c); + *expr = Lit::Str(Str { raw: None, value: value.into(), @@ -1011,14 +997,13 @@ pub fn optimize_bin_expr(expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) match op { op!(bin, "+") => { // It's string concatenation if either left or right is string. - if let (Known(l), Known(r)) = ( - left.as_pure_string(expr_ctx), - right.as_pure_string(expr_ctx), - ) { + if let (Known(l), Known(r)) = + (left.as_pure_wtf8(expr_ctx), right.as_pure_wtf8(expr_ctx)) + { if left.is_str() || left.is_array_lit() || right.is_str() || right.is_array_lit() { let mut l = l.into_owned(); - l.push_str(&r); + l.push_wtf8(&r); *changed = true; @@ -1041,13 +1026,13 @@ pub fn optimize_bin_expr(expr_ctx: ExprCtx, expr: &mut Expr, changed: &mut bool) if !left.may_have_side_effects(expr_ctx) && !right.may_have_side_effects(expr_ctx) { - if let (Known(l), Known(r)) = ( - left.as_pure_string(expr_ctx), - right.as_pure_string(expr_ctx), - ) { + if let (Known(l), Known(r)) = + (left.as_pure_wtf8(expr_ctx), right.as_pure_wtf8(expr_ctx)) + { *changed = true; - let value = format!("{l}{r}"); + let mut value = l.into_owned(); + value.push_wtf8(&r); *expr = Lit::Str(Str { raw: None, diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index 3ea8195c253d..aed430f85d6a 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -1036,8 +1036,17 @@ fn test_fold_get_elem2_1() { fold("x = 'string'[5]", "x = \"g\""); fold("x = 'string'[0]", "x = \"s\""); fold("x = 's'[0]", "x = \"s\""); + + // Surrogate Pair fold("x = '\\uD83D\\uDCA9'[0]", "x = \"\\uD83D\""); fold("x = '\\uD83D\\uDCA9'[1]", "x = \"\\uDCA9\""); + + // Lone Surrogate + fold("x = '\\uD83D'[0]", "x = \"\\uD83D\""); + fold("x = 'foo\\uD83D'[3]", "x = \"\\uD83D\""); + fold("x = 'foo\\uD83D\\uD83D\\uDCA9'[4]", "x = \"\\uD83D\""); + fold("x = 'foo\\uD83D\\uD83D\\uDCA9'[5]", "x = \"\\uDCA9\""); + fold("x = 'a\\uD83Db\\uD83D\\uDCA9'[3]", "x = \"\\uD83D\""); } #[test] diff --git a/crates/swc_ecma_transforms_proposal/src/decorator_impl.rs b/crates/swc_ecma_transforms_proposal/src/decorator_impl.rs index e0424e8242b9..a3b4ce71956c 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorator_impl.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorator_impl.rs @@ -253,7 +253,7 @@ impl DecoratorPass { PropName::Ident(i) => ( Lit::Str(Str { span: i.span, - value: i.sym.clone(), + value: i.sym.clone().into(), raw: None, }) .into(), @@ -1043,7 +1043,7 @@ impl VisitMut for DecoratorPass { Key::Private(k) => { name = Lit::Str(Str { span: DUMMY_SP, - value: k.name.clone(), + value: k.name.clone().into(), raw: None, }) .into(); diff --git a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/metadata.rs b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/metadata.rs index 66c3637d34b2..257a7d159d3c 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/metadata.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/metadata.rs @@ -335,7 +335,7 @@ fn serialize_type(class_name: Option<&Ident>, param: Option<&TsTypeAnn>) -> Expr op: op!("==="), right: Box::new(Expr::Lit(Lit::Str(Str { span: DUMMY_SP, - value: atom!("undefined"), + value: atom!("undefined").into(), raw: None, }))), } @@ -358,7 +358,7 @@ fn serialize_type(class_name: Option<&Ident>, param: Option<&TsTypeAnn>) -> Expr right: Box::new( Lit::Str(Str { span: DUMMY_SP, - value: atom!("undefined"), + value: atom!("undefined").into(), raw: None, }) .into(), diff --git a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs index f7ca229ec406..9d187ed0f497 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs @@ -148,7 +148,7 @@ impl TscDecorator { return Lit::Str(Str { span: DUMMY_SP, raw: None, - value: i.sym.clone(), + value: i.sym.clone().into(), }) .into() } diff --git a/crates/swc_ecma_transforms_proposal/src/decorators/mod.rs b/crates/swc_ecma_transforms_proposal/src/decorators/mod.rs index 6f200a351de7..30a2c8e88410 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorators/mod.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorators/mod.rs @@ -2,6 +2,7 @@ use std::{iter, mem::take}; use either::Either; use serde::Deserialize; +use swc_atoms::Atom; use swc_common::{Spanned, DUMMY_SP}; use swc_ecma_ast::{Pass, *}; use swc_ecma_transforms_base::helper; @@ -440,7 +441,10 @@ impl Decorators { ClassMember::Method(method) => { let fn_name = match method.key { PropName::Ident(ref i) => Some(i.clone()), - PropName::Str(ref s) => Some(IdentName::new(s.value.clone(), s.span)), + PropName::Str(ref s) => s + .value + .as_str() + .map(|sym| IdentName::new(Atom::from(sym), s.span)), _ => None, }; let key_prop_value = Box::new(prop_name_to_expr_value(method.key.clone())); @@ -455,7 +459,7 @@ impl Decorators { let key_prop_value = Lit::Str(Str { span: method.key.span, raw: None, - value: method.key.name.clone(), + value: method.key.name.clone().into(), }) .into(); fold_method!(method, Some(fn_name), key_prop_value) @@ -466,7 +470,7 @@ impl Decorators { PropName::Ident(i) => Lit::Str(Str { span: i.span, raw: None, - value: i.sym, + value: i.sym.into(), }) .into(), _ => prop_name_to_expr(prop.key).into(), diff --git a/crates/swc_ecma_transforms_react/src/display_name/mod.rs b/crates/swc_ecma_transforms_react/src/display_name/mod.rs index 02c18625ee97..f1afcc694999 100644 --- a/crates/swc_ecma_transforms_react/src/display_name/mod.rs +++ b/crates/swc_ecma_transforms_react/src/display_name/mod.rs @@ -43,7 +43,7 @@ impl VisitMut for DisplayName { Lit::Str(Str { span: prop.span, raw: None, - value: prop.sym.clone(), + value: prop.sym.clone().into(), }) .into(), ), @@ -56,7 +56,7 @@ impl VisitMut for DisplayName { Lit::Str(Str { span: ident.span, raw: None, - value: ident.sym.clone(), + value: ident.sym.clone().into(), }) .into(), ), @@ -73,7 +73,7 @@ impl VisitMut for DisplayName { Lit::Str(Str { span: DUMMY_SP, raw: None, - value: atom!("input"), + value: atom!("input").into(), }) .into(), ), @@ -89,7 +89,7 @@ impl VisitMut for DisplayName { PropName::Ident(ref i) => Lit::Str(Str { span: i.span, raw: None, - value: i.sym.clone(), + value: i.sym.clone().into(), }) .into(), PropName::Str(ref s) => Lit::Str(s.clone()).into(), @@ -110,7 +110,7 @@ impl VisitMut for DisplayName { name: Some( Lit::Str(Str { span: ident.span, - value: ident.sym.clone(), + value: ident.sym.clone().into(), raw: None, }) .into(), @@ -198,7 +198,9 @@ fn is_key_display_name(prop: &PropOrSpread) -> bool { | Prop::Setter(SetterProp { ref key, .. }) | Prop::KeyValue(KeyValueProp { ref key, .. }) => match *key { PropName::Ident(ref i) => i.sym == "displayName", - PropName::Str(ref s) => s.value == "displayName", + PropName::Str(ref s) => { + matches!(s.value.as_str(), Some(value) if value == "displayName") + } PropName::Num(..) => false, PropName::BigInt(..) => false, PropName::Computed(..) => false, diff --git a/crates/swc_ecma_transforms_react/src/jsx/mod.rs b/crates/swc_ecma_transforms_react/src/jsx/mod.rs index a53e4de622dd..87a5dfc5fdd6 100644 --- a/crates/swc_ecma_transforms_react/src/jsx/mod.rs +++ b/crates/swc_ecma_transforms_react/src/jsx/mod.rs @@ -10,7 +10,11 @@ use once_cell::sync::Lazy; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use string_enum::StringEnum; -use swc_atoms::{atom, Atom}; +use swc_atoms::{ + atom, + wtf8::{Wtf8, Wtf8Buf}, + Atom, Wtf8Atom, +}; use swc_common::{ comments::{Comment, CommentKind, Comments}, errors::HANDLER, @@ -741,7 +745,7 @@ where PropName::Str(Str { span: i.span, raw: None, - value: i.sym, + value: i.sym.into(), }) } else { PropName::Ident(i) @@ -930,7 +934,7 @@ where Some(match c { JSXElementChild::JSXText(text) => { // TODO(kdy1): Optimize - let value = jsx_text_to_str(&text.value); + let value = jsx_text_to_str(&*text.value); let s = Str { span: text.span, raw: None, @@ -1289,7 +1293,7 @@ where Lit::Str(Str { span, raw: None, - value: i.sym, + value: i.sym.into(), }) .into() } else { @@ -1367,7 +1371,7 @@ fn to_prop_name(n: JSXAttrName) -> PropName { PropName::Str(Str { span, raw: None, - value: i.sym, + value: i.sym.into(), }) } else { PropName::Ident(i) @@ -1403,7 +1407,101 @@ fn to_prop_name(n: JSXAttrName) -> PropName { /// - Decode entities on each line (individually). /// - Remove empty lines and join the rest with " ". #[inline] -fn jsx_text_to_str(t: &str) -> Atom { +fn jsx_text_to_str<'a, T>(t: &'a T) -> Wtf8Atom +where + &'a T: Into<&'a Wtf8>, + T: ?Sized, +{ + let t = t.into(); + // Fast path: JSX text is almost always valid UTF-8 + if let Some(s) = t.as_str() { + return jsx_text_to_str_impl(s).into(); + } + + // Slow path: Handle Wtf8 with surrogates (extremely rare) + jsx_text_to_str_wtf8_impl(t) +} + +/// Handle JSX text with surrogates +fn jsx_text_to_str_wtf8_impl(t: &Wtf8) -> Wtf8Atom { + let mut acc: Option = None; + let mut only_line: Option<(usize, usize)> = None; // (start, end) byte positions + let mut first_non_whitespace: Option = Some(0); + let mut last_non_whitespace: Option = None; + + let mut byte_pos = 0; + for cp in t.code_points() { + let c = cp.to_char_lossy(); + let cp_value = cp.to_u32(); + + // Calculate byte length of this code point in WTF-8 + let cp_byte_len = if cp_value < 0x80 { + 1 + } else if cp_value < 0x800 { + 2 + } else if cp_value < 0x10000 { + 3 + } else { + 4 + }; + + if is_line_terminator(c) { + if let (Some(first), Some(last)) = (first_non_whitespace, last_non_whitespace) { + add_line_of_jsx_text_wtf8(first, last, t, &mut acc, &mut only_line); + } + first_non_whitespace = None; + } else if !is_white_space_single_line(c) { + last_non_whitespace = Some(byte_pos + cp_byte_len); + if first_non_whitespace.is_none() { + first_non_whitespace.replace(byte_pos); + } + } + + byte_pos += cp_byte_len; + } + + // Handle final line + if let Some(first) = first_non_whitespace { + add_line_of_jsx_text_wtf8(first, t.len(), t, &mut acc, &mut only_line); + } + + if let Some(acc) = acc { + acc.into() + } else if let Some((start, end)) = only_line { + t.slice(start, end).into() + } else { + Wtf8Atom::default() + } +} + +/// Helper for adding lines of JSX text when handling Wtf8 with surrogates +fn add_line_of_jsx_text_wtf8( + line_start: usize, + line_end: usize, + source: &Wtf8, + acc: &mut Option, + only_line: &mut Option<(usize, usize)>, +) { + if let Some((only_start, only_end)) = only_line.take() { + // Second line - create accumulator + let mut buffer = Wtf8Buf::with_capacity(source.len()); + buffer.push_wtf8(source.slice(only_start, only_end)); + buffer.push_str(" "); + buffer.push_wtf8(source.slice(line_start, line_end)); + *acc = Some(buffer); + } else if let Some(ref mut buffer) = acc { + // Subsequent lines + buffer.push_str(" "); + buffer.push_wtf8(source.slice(line_start, line_end)); + } else { + // First line + *only_line = Some((line_start, line_end)); + } +} + +/// Internal implementation that works with &str +#[inline] +fn jsx_text_to_str_impl(t: &str) -> Atom { let mut acc: Option = None; let mut only_line: Option<&str> = None; let mut first_non_whitespace: Option = Some(0); @@ -1514,45 +1612,53 @@ fn jsx_attr_value_to_expr(v: JSXAttrValue) -> Option> { }) } -fn transform_jsx_attr_str(v: &str) -> String { +fn transform_jsx_attr_str(v: &Wtf8) -> Wtf8Buf { let single_quote = false; - let mut buf = String::with_capacity(v.len()); - let mut iter = v.chars().peekable(); - - while let Some(c) = iter.next() { - match c { - '\u{0008}' => buf.push_str("\\b"), - '\u{000c}' => buf.push_str("\\f"), - ' ' => buf.push(' '), - - '\n' | '\r' | '\t' => { - buf.push(' '); - - while let Some(' ') = iter.peek() { - iter.next(); + let mut buf = Wtf8Buf::with_capacity(v.len()); + let mut iter = v.code_points().peekable(); + + while let Some(code_point) = iter.next() { + if let Some(c) = code_point.to_char() { + match c { + '\u{0008}' => buf.push_str("\\b"), + '\u{000c}' => buf.push_str("\\f"), + ' ' => buf.push_char(' '), + + '\n' | '\r' | '\t' => { + buf.push_char(' '); + + while let Some(next) = iter.peek() { + if next.to_char() == Some(' ') { + iter.next(); + } else { + break; + } + } } - } - '\u{000b}' => buf.push_str("\\v"), - '\0' => buf.push_str("\\x00"), + '\u{000b}' => buf.push_str("\\v"), + '\0' => buf.push_str("\\x00"), - '\'' if single_quote => buf.push_str("\\'"), - '"' if !single_quote => buf.push('\"'), + '\'' if single_quote => buf.push_str("\\'"), + '"' if !single_quote => buf.push_char('"'), - '\x01'..='\x0f' | '\x10'..='\x1f' => { - buf.push(c); - } + '\x01'..='\x0f' | '\x10'..='\x1f' => { + buf.push_char(c); + } - '\x20'..='\x7e' => { - // - buf.push(c); - } - '\u{7f}'..='\u{ff}' => { - buf.push(c); - } + '\x20'..='\x7e' => { + // + buf.push_char(c); + } + '\u{7f}'..='\u{ff}' => { + buf.push_char(c); + } - _ => { - buf.push(c); + _ => { + buf.push_char(c); + } } + } else { + buf.push(code_point); } } diff --git a/crates/swc_ecma_transforms_react/src/pure_annotations/mod.rs b/crates/swc_ecma_transforms_react/src/pure_annotations/mod.rs index 723cfb75328f..5812660b83ac 100644 --- a/crates/swc_ecma_transforms_react/src/pure_annotations/mod.rs +++ b/crates/swc_ecma_transforms_react/src/pure_annotations/mod.rs @@ -1,5 +1,5 @@ use rustc_hash::FxHashMap; -use swc_atoms::{atom, Atom}; +use swc_atoms::{atom, Atom, Wtf8Atom}; use swc_common::{comments::Comments, Span}; use swc_ecma_ast::*; use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith}; @@ -27,7 +27,7 @@ struct PureAnnotations where C: Comments, { - imports: FxHashMap, + imports: FxHashMap, comments: Option, } @@ -41,7 +41,9 @@ where // Pass 1: collect imports for item in &module.body { if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = item { - let src_str = &*import.src.value; + let Some(src_str) = import.src.value.as_str() else { + continue; + }; if src_str != "react" && src_str != "react-dom" { continue; } @@ -50,9 +52,11 @@ where let src = import.src.value.clone(); match specifier { ImportSpecifier::Named(named) => { - let imported = match &named.imported { + let imported: Atom = match &named.imported { Some(ModuleExportName::Ident(imported)) => imported.sym.clone(), - Some(ModuleExportName::Str(..)) => named.local.sym.clone(), + Some(ModuleExportName::Str(s)) => { + s.value.to_atom_lossy().into_owned() + } None => named.local.sym.clone(), #[cfg(swc_ast_unknown)] Some(_) => continue, @@ -127,10 +131,15 @@ where } } -fn is_pure(src: &Atom, specifier: &Atom) -> bool { - match &**src { +fn is_pure(src: &Wtf8Atom, specifier: &Atom) -> bool { + let Some(src) = src.as_str() else { + return false; + }; + let specifier = specifier.as_str(); + + match src { "react" => matches!( - &**specifier, + specifier, "cloneElement" | "createContext" | "createElement" @@ -141,7 +150,7 @@ fn is_pure(src: &Atom, specifier: &Atom) -> bool { | "memo" | "lazy" ), - "react-dom" => matches!(&**specifier, "createPortal"), + "react-dom" => specifier == "createPortal", _ => false, } } diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index 978542325e60..210a4abb885f 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -22,6 +22,16 @@ use crate::{ utils::{assign_value_to_this_private_prop, assign_value_to_this_prop, Factory}, }; +#[inline] +fn enum_member_id_atom(id: &TsEnumMemberId) -> Atom { + match id { + TsEnumMemberId::Ident(ident) => ident.sym.clone(), + TsEnumMemberId::Str(s) => s.value.to_atom_lossy().into_owned(), + #[cfg(swc_ast_unknown)] + _ => panic!("unable to access unknown nodes"), + } +} + /// ## This Module will transform all TypeScript specific synatx /// /// - ### [namespace]/[modules]/[enums] @@ -126,9 +136,10 @@ impl Visit for Transform { default_init = value.inc(); + let member_name = enum_member_id_atom(&m.id); let key = TsEnumRecordKey { enum_id: id.to_id(), - member_name: m.id.as_ref().clone(), + member_name: member_name.clone(), }; self.enum_record.insert(key, value); @@ -700,7 +711,7 @@ impl Transform { .into_iter() .map(|m| { let span = m.span; - let name = m.id.as_ref().clone(); + let name = enum_member_id_atom(&m.id); let key = TsEnumRecordKey { enum_id: id.to_id(), @@ -1552,9 +1563,13 @@ fn get_member_key(prop: &MemberProp) -> Option { match prop { MemberProp::Ident(ident) => Some(ident.sym.clone()), MemberProp::Computed(ComputedPropName { expr, .. }) => match &**expr { - Expr::Lit(Lit::Str(Str { value, .. })) => Some(value.clone()), + Expr::Lit(Lit::Str(Str { value, .. })) => Some(value.to_atom_lossy().into_owned()), Expr::Tpl(Tpl { exprs, quasis, .. }) => match (exprs.len(), quasis.len()) { - (0, 1) => quasis[0].cooked.as_ref().map(|v| Atom::from(&**v)), + (0, 1) => quasis[0] + .cooked + .as_ref() + .map(|cooked| cooked.to_atom_lossy().into_owned()) + .or_else(|| Some(quasis[0].raw.clone())), _ => None, }, _ => None, diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index 22035877e9b8..ff483b56b463 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -1,5 +1,5 @@ use rustc_hash::FxHashMap; -use swc_atoms::{atom, Atom}; +use swc_atoms::{atom, Atom, Wtf8Atom}; use swc_common::{SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_utils::{ @@ -8,6 +8,14 @@ use swc_ecma_utils::{ }; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; +#[inline] +fn atom_from_wtf8_atom(value: &Wtf8Atom) -> Atom { + value + .as_str() + .map(Atom::from) + .unwrap_or_else(|| Atom::from(value.to_string_lossy())) +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) struct TsEnumRecordKey { pub enum_id: Id, @@ -113,7 +121,7 @@ impl EnumValueComputer<'_> { fn compute_rec(&self, expr: Box) -> TsEnumRecordValue { match *expr { - Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(s.value), + Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(atom_from_wtf8_atom(&s.value)), Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n.value.into()), Expr::Ident(Ident { ctxt, sym, .. }) if &*sym == "NaN" && ctxt == self.unresolved_ctxt => @@ -263,7 +271,7 @@ impl EnumValueComputer<'_> { return opaque_expr; }; - s.value + atom_from_wtf8_atom(&s.value) } _ => return opaque_expr, }; diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs index b63e7a6cb90a..4cee58545145 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs @@ -369,11 +369,17 @@ where var.mark_used_as_ref(); match &*e.left { - Expr::Lit(Lit::Str(prop)) if prop.value.parse::().is_err() => { - var.add_accessed_property(prop.value.clone()); + Expr::Lit(Lit::Str(prop)) => { + if prop + .value + .as_str() + .map_or(true, |value| value.parse::().is_err()) + { + var.add_accessed_property(prop.value.clone()); + } } - Expr::Lit(Lit::Str(_) | Lit::Num(_)) => {} + Expr::Lit(Lit::Num(_)) => {} _ => { var.mark_indexed_with_dynamic_key(); } @@ -1031,11 +1037,16 @@ where if let MemberProp::Computed(prop) = &e.prop { match &*prop.expr { - Expr::Lit(Lit::Str(s)) if s.value.parse::().is_err() => { - v.add_accessed_property(s.value.clone()); + Expr::Lit(Lit::Str(s)) => { + if s.value + .as_str() + .map_or(true, |value| value.parse::().is_err()) + { + v.add_accessed_property(s.value.clone()); + } } - Expr::Lit(Lit::Str(_) | Lit::Num(_)) => {} + Expr::Lit(Lit::Num(_)) => {} _ => { v.mark_indexed_with_dynamic_key(); } @@ -1043,7 +1054,7 @@ where } if let MemberProp::Ident(prop) = &e.prop { - v.add_accessed_property(prop.sym.clone()); + v.add_accessed_property(prop.sym.clone().into()); } }); @@ -1061,7 +1072,7 @@ where if is_root_of_member_expr_declared(e, &self.data) { if let MemberProp::Ident(ident) = &e.prop { - self.data.add_property_atom(ident.sym.clone()); + self.data.add_property_atom(ident.sym.clone().into()); } } } @@ -1191,7 +1202,7 @@ where if let Prop::Shorthand(i) = n { let ctx = self.ctx.with(BitContext::IsIdRef, true); self.with_ctx(ctx).report_usage(i); - self.data.add_property_atom(i.sym.clone()); + self.data.add_property_atom(i.sym.clone().into()); } else { let ctx = self.ctx.with(BitContext::IsIdRef, true); n.visit_children_with(&mut *self.with_ctx(ctx)); @@ -1203,7 +1214,7 @@ where match node { PropName::Ident(ident) => { - self.data.add_property_atom(ident.sym.clone()); + self.data.add_property_atom(ident.sym.clone().into()); } PropName::Str(s) => { self.data.add_property_atom(s.value.clone()); diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs index c04a4bb010f3..df2e9ee5f2b1 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs @@ -1,4 +1,4 @@ -use swc_atoms::Atom; +use swc_atoms::Wtf8Atom; use swc_common::SyntaxContext; use swc_ecma_ast::*; use swc_ecma_utils::{Type, Value}; @@ -14,7 +14,7 @@ pub trait Storage: Sized + Default { fn need_collect_prop_atom(&self) -> bool; - fn add_property_atom(&mut self, atom: Atom); + fn add_property_atom(&mut self, atom: Wtf8Atom); fn scope(&mut self, ctxt: SyntaxContext) -> &mut Self::ScopeData; @@ -76,7 +76,7 @@ pub trait VarDataLike: Sized { fn mark_indexed_with_dynamic_key(&mut self); - fn add_accessed_property(&mut self, name: Atom); + fn add_accessed_property(&mut self, name: Wtf8Atom); fn mark_used_as_ref(&mut self); diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs index b9b5808ded42..6cc030aa5098 100644 --- a/crates/swc_ecma_utils/src/lib.rs +++ b/crates/swc_ecma_utils/src/lib.rs @@ -18,7 +18,10 @@ use number::ToJsString; use once_cell::sync::Lazy; use parallel::{Parallel, ParallelExt}; use rustc_hash::{FxHashMap, FxHashSet}; -use swc_atoms::{atom, Atom}; +use swc_atoms::{ + atom, + wtf8::{Wtf8, Wtf8Buf}, +}; use swc_common::{util::take::Take, Mark, Span, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_visit::{ @@ -52,6 +55,8 @@ pub mod parallel; mod value; pub mod var; +pub mod unicode; + mod node_ignore_span; pub mod number; pub mod stack_size; @@ -808,6 +813,12 @@ pub trait ExprExt { as_pure_string(self.as_expr(), ctx) } + /// Returns Known only if it's pure. + #[inline(always)] + fn as_pure_wtf8(&self, ctx: ExprCtx) -> Value> { + as_pure_wtf8(self.as_expr(), ctx) + } + /// Apply the supplied predicate against all possible result Nodes of the /// expression. #[inline(always)] @@ -1275,7 +1286,7 @@ pub fn is_simple_pure_member_expr(m: &MemberExpr, pure_getters: bool) -> bool { fn sym_for_expr(expr: &Expr) -> Option { match expr { - Expr::Lit(Lit::Str(s)) => Some(s.value.to_string()), + Expr::Lit(Lit::Str(s)) => s.value.as_str().map(ToString::to_string), Expr::This(_) => Some("this".to_string()), Expr::Ident(ident) @@ -1445,7 +1456,7 @@ pub fn prop_name_to_expr_value(p: PropName) -> Expr { PropName::Ident(i) => Lit::Str(Str { span: i.span, raw: None, - value: i.sym, + value: i.sym.into(), }) .into(), PropName::Str(s) => Lit::Str(s).into(), @@ -1687,7 +1698,7 @@ where } } -pub fn is_valid_ident(s: &Atom) -> bool { +pub fn is_valid_ident(s: &str) -> bool { if s.is_empty() { return false; } @@ -3006,7 +3017,12 @@ fn cast_to_number(expr: &Expr, ctx: ExprCtx) -> (Purity, Value) { Lit::Bool(Bool { value: true, .. }) => 1.0, Lit::Bool(Bool { value: false, .. }) | Lit::Null(..) => 0.0, Lit::Num(Number { value: n, .. }) => *n, - Lit::Str(Str { value, .. }) => return (Pure, num_from_str(value)), + Lit::Str(Str { value, .. }) => { + if let Some(value) = value.as_str() { + return (Pure, num_from_str(value)); + } + return (Pure, Unknown); + } _ => return (Pure, Unknown), }, Expr::Array(..) => { @@ -3092,23 +3108,45 @@ fn as_pure_number(expr: &Expr, ctx: ExprCtx) -> Value { } fn as_pure_string(expr: &Expr, ctx: ExprCtx) -> Value> { + match as_pure_wtf8(expr, ctx) { + Known(v) => match v { + Cow::Borrowed(v) => { + if let Some(v) = v.as_str() { + Known(Cow::Borrowed(v)) + } else { + Unknown + } + } + Cow::Owned(v) => { + if let Ok(v) = v.into_string() { + Known(Cow::Owned(v)) + } else { + Unknown + } + } + }, + Unknown => Unknown, + } +} + +fn as_pure_wtf8(expr: &Expr, ctx: ExprCtx) -> Value> { let Some(ctx) = ctx.consume_depth() else { return Unknown; }; match *expr { Expr::Lit(ref l) => match *l { - Lit::Str(Str { ref value, .. }) => Known(Cow::Borrowed(value)), + Lit::Str(Str { ref value, .. }) => Known(Cow::Borrowed(&**value)), Lit::Num(ref n) => { if n.value == -0.0 { - return Known(Cow::Borrowed("0")); + return Known(Cow::Borrowed("0".into())); } - Known(Cow::Owned(n.value.to_js_string())) + Known(Cow::Owned(Wtf8Buf::from_string(n.value.to_js_string()))) } - Lit::Bool(Bool { value: true, .. }) => Known(Cow::Borrowed("true")), - Lit::Bool(Bool { value: false, .. }) => Known(Cow::Borrowed("false")), - Lit::Null(..) => Known(Cow::Borrowed("null")), + Lit::Bool(Bool { value: true, .. }) => Known(Cow::Borrowed("true".into())), + Lit::Bool(Bool { value: false, .. }) => Known(Cow::Borrowed("false".into())), + Lit::Null(..) => Known(Cow::Borrowed("null".into())), _ => Unknown, }, Expr::Tpl(_) => { @@ -3120,13 +3158,13 @@ fn as_pure_string(expr: &Expr, ctx: ExprCtx) -> Value> { } Expr::Ident(Ident { ref sym, ctxt, .. }) => match &**sym { "undefined" | "Infinity" | "NaN" if ctxt == ctx.unresolved_ctxt => { - Known(Cow::Borrowed(&**sym)) + Known(Cow::Borrowed(Wtf8::from_str(sym))) } _ => Unknown, }, Expr::Unary(UnaryExpr { op: op!("void"), .. - }) => Known(Cow::Borrowed("undefined")), + }) => Known(Cow::Borrowed("undefined".into())), Expr::Unary(UnaryExpr { op: op!("!"), ref arg, @@ -3134,15 +3172,15 @@ fn as_pure_string(expr: &Expr, ctx: ExprCtx) -> Value> { }) => Known(Cow::Borrowed(match arg.as_pure_bool(ctx) { Known(v) => { if v { - "false" + "false".into() } else { - "true" + "true".into() } } Unknown => return Value::Unknown, })), Expr::Array(ArrayLit { ref elems, .. }) => { - let mut buf = String::new(); + let mut buf = Wtf8Buf::new(); let len = elems.len(); // null, undefined is "" in array literal. for (idx, elem) in elems.iter().enumerate() { @@ -3151,7 +3189,7 @@ fn as_pure_string(expr: &Expr, ctx: ExprCtx) -> Value> { Some(ref elem) => { let ExprOrSpread { ref expr, .. } = *elem; match &**expr { - Expr::Lit(Lit::Null(..)) => Cow::Borrowed(""), + Expr::Lit(Lit::Null(..)) => Cow::Borrowed("".into()), Expr::Unary(UnaryExpr { op: op!("void"), arg, @@ -3160,25 +3198,25 @@ fn as_pure_string(expr: &Expr, ctx: ExprCtx) -> Value> { if arg.may_have_side_effects(ctx) { return Value::Unknown; } - Cow::Borrowed("") + Cow::Borrowed("".into()) } Expr::Ident(Ident { sym: undefined, .. }) if &**undefined == "undefined" => { - Cow::Borrowed("") + Cow::Borrowed("".into()) } - _ => match expr.as_pure_string(ctx) { + _ => match expr.as_pure_wtf8(ctx) { Known(v) => v, Unknown => return Value::Unknown, }, } } - None => Cow::Borrowed(""), + None => Cow::Borrowed("".into()), }; - buf.push_str(&e); + buf.push_wtf8(&e); if !last { - buf.push(','); + buf.push_char(','); } } Known(buf.into()) @@ -3527,9 +3565,11 @@ fn may_have_side_effects(expr: &Expr, ctx: ExprCtx) -> bool { Prop::Getter(_) | Prop::Setter(_) | Prop::Method(_) => true, Prop::Shorthand(Ident { sym, .. }) | Prop::KeyValue(KeyValueProp { - key: - PropName::Ident(IdentName { sym, .. }) - | PropName::Str(Str { value: sym, .. }), + key: PropName::Ident(IdentName { sym, .. }), + .. + }) => &**sym == "__proto__", + Prop::KeyValue(KeyValueProp { + key: PropName::Str(Str { value: sym, .. }), .. }) => &**sym == "__proto__", Prop::KeyValue(KeyValueProp { diff --git a/crates/swc_ecma_utils/src/unicode.rs b/crates/swc_ecma_utils/src/unicode.rs new file mode 100644 index 000000000000..0c001a472db4 --- /dev/null +++ b/crates/swc_ecma_utils/src/unicode.rs @@ -0,0 +1,70 @@ +/// Converts a Unicode code point to UTF-16 surrogate pair. +/// Returns (high_surrogate, low_surrogate) both as u32. +/// +/// For code point < 0x10000, which is not represented by a surrogate pair, +/// returns `None` +#[inline] +pub const fn code_point_to_pair(code_point: u32) -> Option<(u32, u32)> { + if code_point < 0x10000 || code_point > 0x10_ffff { + None + } else { + let adjusted = code_point - 0x10000; + let high = 0xd800 + (adjusted >> 10); + let low = 0xdc00 + (adjusted & 0x3ff); + Some((high, low)) + } +} + +/// Converts UTF-16 surrogate pair to Unicode code point. +/// `https://tc39.es/ecma262/#sec-utf16decodesurrogatepair` +/// +/// # Panics +/// +/// Panics if `high` is not in the range 0xD800..=0xDBFF or `low` is not in the +/// range 0xDC00..=0xDFFF. +#[inline] +pub const fn pair_to_code_point(high: u32, low: u32) -> u32 { + (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000 +} + +/// Returns true if `u` is a high surrogate (in the range 0xD800..=0xDBFF). +#[inline] +pub fn is_high_surrogate(u: u32) -> bool { + (0xd800..=0xdbff).contains(&u) +} + +/// Returns true if `u` is a low surrogate (in the range 0xDC00..=0xDFFF). +#[inline] +pub fn is_low_surrogate(u: u32) -> bool { + (0xdc00..=0xdfff).contains(&u) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_code_point_to_pair() { + // Poop emoji (💩) - U+1F4A9 + let Some((high, low)) = code_point_to_pair(0x1f4a9) else { + unreachable!() + }; + assert_eq!(high, 0xd83d); + assert_eq!(low, 0xdca9); + + // Regular BMP character + assert_eq!(code_point_to_pair(0x1234), None); + } + + #[test] + fn test_roundtrip() { + let original = 0x1f4a9; + let Some((high, low)) = code_point_to_pair(original) else { + unreachable!() + }; + if low != 0 { + let recovered = pair_to_code_point(high, low); + assert_eq!(original, recovered); + } + } +} diff --git a/crates/swc_ecma_visit/src/generated.rs b/crates/swc_ecma_visit/src/generated.rs index dc14a1fe5604..814f828d58ac 100644 --- a/crates/swc_ecma_visit/src/generated.rs +++ b/crates/swc_ecma_visit/src/generated.rs @@ -1089,6 +1089,13 @@ pub trait Visit { fn visit_opt_vec_pats(&mut self, node: &[Option]) { <[Option] as VisitWith>::visit_children_with(node, self) } + #[doc = "Visit a node of type `Option < swc_atoms :: Wtf8Atom >`.\n\nBy default, this method \ + calls [`Option < swc_atoms :: Wtf8Atom >::visit_children_with`]. If you want to \ + recurse, you need to call it manually."] + #[inline] + fn visit_opt_wtf_8_atom(&mut self, node: &Option) { + as VisitWith>::visit_children_with(node, self) + } #[doc = "Visit a node of type `Param`.\n\nBy default, this method calls \ [`Param::visit_children_with`]. If you want to recurse, you need to call it manually."] #[inline] @@ -2020,6 +2027,13 @@ pub trait Visit { fn visit_with_stmt(&mut self, node: &WithStmt) { >::visit_children_with(node, self) } + #[doc = "Visit a node of type `swc_atoms :: Wtf8Atom`.\n\nBy default, this method calls \ + [`swc_atoms :: Wtf8Atom::visit_children_with`]. If you want to recurse, you need to \ + call it manually."] + #[inline] + fn visit_wtf_8_atom(&mut self, node: &swc_atoms::Wtf8Atom) { + >::visit_children_with(node, self) + } #[doc = "Visit a node of type `YieldExpr`.\n\nBy default, this method calls \ [`YieldExpr::visit_children_with`]. If you want to recurse, you need to call it \ manually."] @@ -2820,6 +2834,11 @@ where ::visit_opt_vec_pats(&mut **self, node) } + #[inline] + fn visit_opt_wtf_8_atom(&mut self, node: &Option) { + ::visit_opt_wtf_8_atom(&mut **self, node) + } + #[inline] fn visit_param(&mut self, node: &Param) { ::visit_param(&mut **self, node) @@ -3495,6 +3514,11 @@ where ::visit_with_stmt(&mut **self, node) } + #[inline] + fn visit_wtf_8_atom(&mut self, node: &swc_atoms::Wtf8Atom) { + ::visit_wtf_8_atom(&mut **self, node) + } + #[inline] fn visit_yield_expr(&mut self, node: &YieldExpr) { ::visit_yield_expr(&mut **self, node) @@ -4292,6 +4316,11 @@ where ::visit_opt_vec_pats(&mut **self, node) } + #[inline] + fn visit_opt_wtf_8_atom(&mut self, node: &Option) { + ::visit_opt_wtf_8_atom(&mut **self, node) + } + #[inline] fn visit_param(&mut self, node: &Param) { ::visit_param(&mut **self, node) @@ -4967,6 +4996,11 @@ where ::visit_with_stmt(&mut **self, node) } + #[inline] + fn visit_wtf_8_atom(&mut self, node: &swc_atoms::Wtf8Atom) { + ::visit_wtf_8_atom(&mut **self, node) + } + #[inline] fn visit_yield_expr(&mut self, node: &YieldExpr) { ::visit_yield_expr(&mut **self, node) @@ -6264,6 +6298,14 @@ where } } + #[inline] + fn visit_opt_wtf_8_atom(&mut self, node: &Option) { + match self { + swc_visit::Either::Left(visitor) => Visit::visit_opt_wtf_8_atom(visitor, node), + swc_visit::Either::Right(visitor) => Visit::visit_opt_wtf_8_atom(visitor, node), + } + } + #[inline] fn visit_param(&mut self, node: &Param) { match self { @@ -7368,6 +7410,14 @@ where } } + #[inline] + fn visit_wtf_8_atom(&mut self, node: &swc_atoms::Wtf8Atom) { + match self { + swc_visit::Either::Left(visitor) => Visit::visit_wtf_8_atom(visitor, node), + swc_visit::Either::Right(visitor) => Visit::visit_wtf_8_atom(visitor, node), + } + } + #[inline] fn visit_yield_expr(&mut self, node: &YieldExpr) { match self { @@ -8639,6 +8689,14 @@ where } } + #[inline] + fn visit_opt_wtf_8_atom(&mut self, node: &Option) { + if self.enabled { + ::visit_opt_wtf_8_atom(&mut self.visitor, node) + } else { + } + } + #[inline] fn visit_param(&mut self, node: &Param) { if self.enabled { @@ -9719,6 +9777,14 @@ where } } + #[inline] + fn visit_wtf_8_atom(&mut self, node: &swc_atoms::Wtf8Atom) { + if self.enabled { + ::visit_wtf_8_atom(&mut self.visitor, node) + } else { + } + } + #[inline] fn visit_yield_expr(&mut self, node: &YieldExpr) { if self.enabled { @@ -13266,7 +13332,7 @@ impl VisitWith for Str { >::visit_with(span, visitor) }; { - >::visit_with(value, visitor) + >::visit_with(value, visitor) }; { as VisitWith>::visit_with(raw, visitor) @@ -13496,7 +13562,7 @@ impl VisitWith for TplElement { >::visit_with(span, visitor) }; { - as VisitWith>::visit_with(cooked, visitor) + as VisitWith>::visit_with(cooked, visitor) }; { >::visit_with(raw, visitor) @@ -16191,6 +16257,21 @@ impl VisitWith for [Option] { .for_each(|item| as VisitWith>::visit_with(item, visitor)) } } +impl VisitWith for Option { + #[doc = "Calls [Visit`::visit_opt_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn visit_with(&self, visitor: &mut V) { + ::visit_opt_wtf_8_atom(visitor, self) + } + + #[inline] + fn visit_children_with(&self, visitor: &mut V) { + match self { + Some(inner) => >::visit_with(inner, visitor), + None => {} + } + } +} impl VisitWith for [ParamOrTsParamProp] { #[doc = "Calls [Visit`::visit_param_or_ts_param_props`] with `self`. (Extra impl)"] #[inline] @@ -16410,6 +16491,18 @@ impl VisitWith for [VarDeclarator] { .for_each(|item| >::visit_with(item, visitor)) } } +impl VisitWith for swc_atoms::Wtf8Atom { + #[doc = "Calls [Visit`::visit_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn visit_with(&self, visitor: &mut V) { + ::visit_wtf_8_atom(visitor, self) + } + + #[inline] + fn visit_children_with(&self, visitor: &mut V) { + {} + } +} impl VisitWith for std::boxed::Box where V: ?Sized + Visit, @@ -18314,6 +18407,19 @@ pub trait VisitAstPath { node, self, __ast_path, ) } + #[doc = "Visit a node of type `Option < swc_atoms :: Wtf8Atom >`.\n\nBy default, this method \ + calls [`Option < swc_atoms :: Wtf8Atom >::visit_children_with_ast_path`]. If you want \ + to recurse, you need to call it manually."] + #[inline] + fn visit_opt_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast Option, + __ast_path: &mut AstNodePath<'r>, + ) { + as VisitWithAstPath>::visit_children_with_ast_path( + node, self, __ast_path, + ) + } #[doc = "Visit a node of type `Param`.\n\nBy default, this method calls \ [`Param::visit_children_with_ast_path`]. If you want to recurse, you need to call it \ manually."] @@ -19927,6 +20033,19 @@ pub trait VisitAstPath { ) { >::visit_children_with_ast_path(node, self, __ast_path) } + #[doc = "Visit a node of type `swc_atoms :: Wtf8Atom`.\n\nBy default, this method calls \ + [`swc_atoms :: Wtf8Atom::visit_children_with_ast_path`]. If you want to recurse, you \ + need to call it manually."] + #[inline] + fn visit_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast swc_atoms::Wtf8Atom, + __ast_path: &mut AstNodePath<'r>, + ) { + >::visit_children_with_ast_path( + node, self, __ast_path, + ) + } #[doc = "Visit a node of type `YieldExpr`.\n\nBy default, this method calls \ [`YieldExpr::visit_children_with_ast_path`]. If you want to recurse, you need to call \ it manually."] @@ -21310,6 +21429,15 @@ where ::visit_opt_vec_pats(&mut **self, node, __ast_path) } + #[inline] + fn visit_opt_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast Option, + __ast_path: &mut AstNodePath<'r>, + ) { + ::visit_opt_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn visit_param<'ast: 'r, 'r>(&mut self, node: &'ast Param, __ast_path: &mut AstNodePath<'r>) { ::visit_param(&mut **self, node, __ast_path) @@ -22477,6 +22605,15 @@ where ::visit_with_stmt(&mut **self, node, __ast_path) } + #[inline] + fn visit_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast swc_atoms::Wtf8Atom, + __ast_path: &mut AstNodePath<'r>, + ) { + ::visit_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn visit_yield_expr<'ast: 'r, 'r>( &mut self, @@ -23857,6 +23994,15 @@ where ::visit_opt_vec_pats(&mut **self, node, __ast_path) } + #[inline] + fn visit_opt_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast Option, + __ast_path: &mut AstNodePath<'r>, + ) { + ::visit_opt_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn visit_param<'ast: 'r, 'r>(&mut self, node: &'ast Param, __ast_path: &mut AstNodePath<'r>) { ::visit_param(&mut **self, node, __ast_path) @@ -25024,6 +25170,15 @@ where ::visit_with_stmt(&mut **self, node, __ast_path) } + #[inline] + fn visit_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast swc_atoms::Wtf8Atom, + __ast_path: &mut AstNodePath<'r>, + ) { + ::visit_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn visit_yield_expr<'ast: 'r, 'r>( &mut self, @@ -27486,6 +27641,22 @@ where } } + #[inline] + fn visit_opt_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast Option, + __ast_path: &mut AstNodePath<'r>, + ) { + match self { + swc_visit::Either::Left(visitor) => { + VisitAstPath::visit_opt_wtf_8_atom(visitor, node, __ast_path) + } + swc_visit::Either::Right(visitor) => { + VisitAstPath::visit_opt_wtf_8_atom(visitor, node, __ast_path) + } + } + } + #[inline] fn visit_param<'ast: 'r, 'r>(&mut self, node: &'ast Param, __ast_path: &mut AstNodePath<'r>) { match self { @@ -29578,6 +29749,22 @@ where } } + #[inline] + fn visit_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast swc_atoms::Wtf8Atom, + __ast_path: &mut AstNodePath<'r>, + ) { + match self { + swc_visit::Either::Left(visitor) => { + VisitAstPath::visit_wtf_8_atom(visitor, node, __ast_path) + } + swc_visit::Either::Right(visitor) => { + VisitAstPath::visit_wtf_8_atom(visitor, node, __ast_path) + } + } + } + #[inline] fn visit_yield_expr<'ast: 'r, 'r>( &mut self, @@ -31448,6 +31635,18 @@ where } } + #[inline] + fn visit_opt_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast Option, + __ast_path: &mut AstNodePath<'r>, + ) { + if self.enabled { + ::visit_opt_wtf_8_atom(&mut self.visitor, node, __ast_path) + } else { + } + } + #[inline] fn visit_param<'ast: 'r, 'r>(&mut self, node: &'ast Param, __ast_path: &mut AstNodePath<'r>) { if self.enabled { @@ -33036,6 +33235,18 @@ where } } + #[inline] + fn visit_wtf_8_atom<'ast: 'r, 'r>( + &mut self, + node: &'ast swc_atoms::Wtf8Atom, + __ast_path: &mut AstNodePath<'r>, + ) { + if self.enabled { + ::visit_wtf_8_atom(&mut self.visitor, node, __ast_path) + } else { + } + } + #[inline] fn visit_yield_expr<'ast: 'r, 'r>( &mut self, @@ -41648,7 +41859,7 @@ impl VisitWithAstPath for Str { { let mut __ast_path = __ast_path .with_guard(AstParentNodeRef::Str(self, self::fields::StrField::Value)); - >::visit_with_ast_path( + >::visit_with_ast_path( value, visitor, &mut *__ast_path, @@ -42175,7 +42386,7 @@ impl VisitWithAstPath for TplElement { self, self::fields::TplElementField::Cooked, )); - as VisitWithAstPath>::visit_with_ast_path( + as VisitWithAstPath>::visit_with_ast_path( cooked, visitor, &mut *__ast_path, @@ -48304,6 +48515,33 @@ impl VisitWithAstPath for [Option] { } #[cfg(any(docsrs, feature = "path"))] #[cfg_attr(docsrs, doc(cfg(feature = "path")))] +impl VisitWithAstPath for Option { + #[doc = "Calls [VisitAstPath`::visit_opt_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn visit_with_ast_path<'ast: 'r, 'r>( + &'ast self, + visitor: &mut V, + __ast_path: &mut AstNodePath<'r>, + ) { + ::visit_opt_wtf_8_atom(visitor, self, __ast_path) + } + + #[inline] + fn visit_children_with_ast_path<'ast: 'r, 'r>( + &'ast self, + visitor: &mut V, + __ast_path: &mut AstNodePath<'r>, + ) { + match self { + Some(inner) => >::visit_with_ast_path( + inner, visitor, __ast_path, + ), + None => {} + } + } +} +#[cfg(any(docsrs, feature = "path"))] +#[cfg_attr(docsrs, doc(cfg(feature = "path")))] impl VisitWithAstPath for [ParamOrTsParamProp] { #[doc = "Calls [VisitAstPath`::visit_param_or_ts_param_props`] with `self`. (Extra impl)"] #[inline] @@ -48767,6 +49005,28 @@ impl VisitWithAstPath for [VarDeclarator] { } #[cfg(any(docsrs, feature = "path"))] #[cfg_attr(docsrs, doc(cfg(feature = "path")))] +impl VisitWithAstPath for swc_atoms::Wtf8Atom { + #[doc = "Calls [VisitAstPath`::visit_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn visit_with_ast_path<'ast: 'r, 'r>( + &'ast self, + visitor: &mut V, + __ast_path: &mut AstNodePath<'r>, + ) { + ::visit_wtf_8_atom(visitor, self, __ast_path) + } + + #[inline] + fn visit_children_with_ast_path<'ast: 'r, 'r>( + &'ast self, + visitor: &mut V, + __ast_path: &mut AstNodePath<'r>, + ) { + {} + } +} +#[cfg(any(docsrs, feature = "path"))] +#[cfg_attr(docsrs, doc(cfg(feature = "path")))] impl VisitWithAstPath for std::boxed::Box where V: ?Sized + VisitAstPath, @@ -49927,6 +50187,13 @@ pub trait VisitMut { fn visit_mut_opt_vec_pats(&mut self, node: &mut Vec>) { > as VisitMutWith>::visit_mut_children_with(node, self) } + #[doc = "Visit a node of type `Option < swc_atoms :: Wtf8Atom >`.\n\nBy default, this method \ + calls [`Option < swc_atoms :: Wtf8Atom >::visit_mut_children_with`]. If you want to \ + recurse, you need to call it manually."] + #[inline] + fn visit_mut_opt_wtf_8_atom(&mut self, node: &mut Option) { + as VisitMutWith>::visit_mut_children_with(node, self) + } #[doc = "Visit a node of type `Param`.\n\nBy default, this method calls \ [`Param::visit_mut_children_with`]. If you want to recurse, you need to call it \ manually."] @@ -50869,6 +51136,13 @@ pub trait VisitMut { fn visit_mut_with_stmt(&mut self, node: &mut WithStmt) { >::visit_mut_children_with(node, self) } + #[doc = "Visit a node of type `swc_atoms :: Wtf8Atom`.\n\nBy default, this method calls \ + [`swc_atoms :: Wtf8Atom::visit_mut_children_with`]. If you want to recurse, you need \ + to call it manually."] + #[inline] + fn visit_mut_wtf_8_atom(&mut self, node: &mut swc_atoms::Wtf8Atom) { + >::visit_mut_children_with(node, self) + } #[doc = "Visit a node of type `YieldExpr`.\n\nBy default, this method calls \ [`YieldExpr::visit_mut_children_with`]. If you want to recurse, you need to call it \ manually."] @@ -51669,6 +51943,11 @@ where ::visit_mut_opt_vec_pats(&mut **self, node) } + #[inline] + fn visit_mut_opt_wtf_8_atom(&mut self, node: &mut Option) { + ::visit_mut_opt_wtf_8_atom(&mut **self, node) + } + #[inline] fn visit_mut_param(&mut self, node: &mut Param) { ::visit_mut_param(&mut **self, node) @@ -52344,6 +52623,11 @@ where ::visit_mut_with_stmt(&mut **self, node) } + #[inline] + fn visit_mut_wtf_8_atom(&mut self, node: &mut swc_atoms::Wtf8Atom) { + ::visit_mut_wtf_8_atom(&mut **self, node) + } + #[inline] fn visit_mut_yield_expr(&mut self, node: &mut YieldExpr) { ::visit_mut_yield_expr(&mut **self, node) @@ -53141,6 +53425,11 @@ where ::visit_mut_opt_vec_pats(&mut **self, node) } + #[inline] + fn visit_mut_opt_wtf_8_atom(&mut self, node: &mut Option) { + ::visit_mut_opt_wtf_8_atom(&mut **self, node) + } + #[inline] fn visit_mut_param(&mut self, node: &mut Param) { ::visit_mut_param(&mut **self, node) @@ -53816,6 +54105,11 @@ where ::visit_mut_with_stmt(&mut **self, node) } + #[inline] + fn visit_mut_wtf_8_atom(&mut self, node: &mut swc_atoms::Wtf8Atom) { + ::visit_mut_wtf_8_atom(&mut **self, node) + } + #[inline] fn visit_mut_yield_expr(&mut self, node: &mut YieldExpr) { ::visit_mut_yield_expr(&mut **self, node) @@ -55259,6 +55553,14 @@ where } } + #[inline] + fn visit_mut_opt_wtf_8_atom(&mut self, node: &mut Option) { + match self { + swc_visit::Either::Left(visitor) => VisitMut::visit_mut_opt_wtf_8_atom(visitor, node), + swc_visit::Either::Right(visitor) => VisitMut::visit_mut_opt_wtf_8_atom(visitor, node), + } + } + #[inline] fn visit_mut_param(&mut self, node: &mut Param) { match self { @@ -56525,6 +56827,14 @@ where } } + #[inline] + fn visit_mut_wtf_8_atom(&mut self, node: &mut swc_atoms::Wtf8Atom) { + match self { + swc_visit::Either::Left(visitor) => VisitMut::visit_mut_wtf_8_atom(visitor, node), + swc_visit::Either::Right(visitor) => VisitMut::visit_mut_wtf_8_atom(visitor, node), + } + } + #[inline] fn visit_mut_yield_expr(&mut self, node: &mut YieldExpr) { match self { @@ -57796,6 +58106,14 @@ where } } + #[inline] + fn visit_mut_opt_wtf_8_atom(&mut self, node: &mut Option) { + if self.enabled { + ::visit_mut_opt_wtf_8_atom(&mut self.visitor, node) + } else { + } + } + #[inline] fn visit_mut_param(&mut self, node: &mut Param) { if self.enabled { @@ -58876,6 +59194,14 @@ where } } + #[inline] + fn visit_mut_wtf_8_atom(&mut self, node: &mut swc_atoms::Wtf8Atom) { + if self.enabled { + ::visit_mut_wtf_8_atom(&mut self.visitor, node) + } else { + } + } + #[inline] fn visit_mut_yield_expr(&mut self, node: &mut YieldExpr) { if self.enabled { @@ -62458,7 +62784,7 @@ impl VisitMutWith for Str { >::visit_mut_with(span, visitor) }; { - >::visit_mut_with(value, visitor) + >::visit_mut_with(value, visitor) }; { as VisitMutWith>::visit_mut_with(raw, visitor) @@ -62688,7 +63014,9 @@ impl VisitMutWith for TplElement { >::visit_mut_with(span, visitor) }; { - as VisitMutWith>::visit_mut_with(cooked, visitor) + as VisitMutWith>::visit_mut_with( + cooked, visitor, + ) }; { >::visit_mut_with(raw, visitor) @@ -65415,6 +65743,21 @@ impl VisitMutWith for Vec> { .for_each(|item| as VisitMutWith>::visit_mut_with(item, visitor)) } } +impl VisitMutWith for Option { + #[doc = "Calls [VisitMut`::visit_mut_opt_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn visit_mut_with(&mut self, visitor: &mut V) { + ::visit_mut_opt_wtf_8_atom(visitor, self) + } + + #[inline] + fn visit_mut_children_with(&mut self, visitor: &mut V) { + match self { + Some(inner) => >::visit_mut_with(inner, visitor), + None => {} + } + } +} impl VisitMutWith for Vec { #[doc = "Calls [VisitMut`::visit_mut_param_or_ts_param_props`] with `self`. (Extra impl)"] #[inline] @@ -65634,6 +65977,18 @@ impl VisitMutWith for Vec { .for_each(|item| >::visit_mut_with(item, visitor)) } } +impl VisitMutWith for swc_atoms::Wtf8Atom { + #[doc = "Calls [VisitMut`::visit_mut_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn visit_mut_with(&mut self, visitor: &mut V) { + ::visit_mut_wtf_8_atom(visitor, self) + } + + #[inline] + fn visit_mut_children_with(&mut self, visitor: &mut V) { + {} + } +} impl VisitMutWith for std::boxed::Box where V: ?Sized + VisitMut, @@ -67297,6 +67652,19 @@ pub trait VisitMutAstPath { node, self, __ast_path, ) } + #[doc = "Visit a node of type `Option < swc_atoms :: Wtf8Atom >`.\n\nBy default, this method \ + calls [`Option < swc_atoms :: Wtf8Atom >::visit_mut_children_with_ast_path`]. If you \ + want to recurse, you need to call it manually."] + #[inline] + fn visit_mut_opt_wtf_8_atom( + &mut self, + node: &mut Option, + __ast_path: &mut AstKindPath, + ) { + as VisitMutWithAstPath>::visit_mut_children_with_ast_path( + node, self, __ast_path, + ) + } #[doc = "Visit a node of type `Param`.\n\nBy default, this method calls \ [`Param::visit_mut_children_with_ast_path`]. If you want to recurse, you need to call \ it manually."] @@ -68746,6 +69114,19 @@ pub trait VisitMutAstPath { node, self, __ast_path, ) } + #[doc = "Visit a node of type `swc_atoms :: Wtf8Atom`.\n\nBy default, this method calls \ + [`swc_atoms :: Wtf8Atom::visit_mut_children_with_ast_path`]. If you want to recurse, \ + you need to call it manually."] + #[inline] + fn visit_mut_wtf_8_atom( + &mut self, + node: &mut swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) { + >::visit_mut_children_with_ast_path( + node, self, __ast_path, + ) + } #[doc = "Visit a node of type `YieldExpr`.\n\nBy default, this method calls \ [`YieldExpr::visit_mut_children_with_ast_path`]. If you want to recurse, you need to \ call it manually."] @@ -69787,6 +70168,15 @@ where ::visit_mut_opt_vec_pats(&mut **self, node, __ast_path) } + #[inline] + fn visit_mut_opt_wtf_8_atom( + &mut self, + node: &mut Option, + __ast_path: &mut AstKindPath, + ) { + ::visit_mut_opt_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn visit_mut_param(&mut self, node: &mut Param, __ast_path: &mut AstKindPath) { ::visit_mut_param(&mut **self, node, __ast_path) @@ -70706,6 +71096,15 @@ where ::visit_mut_with_stmt(&mut **self, node, __ast_path) } + #[inline] + fn visit_mut_wtf_8_atom( + &mut self, + node: &mut swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) { + ::visit_mut_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn visit_mut_yield_expr(&mut self, node: &mut YieldExpr, __ast_path: &mut AstKindPath) { ::visit_mut_yield_expr(&mut **self, node, __ast_path) @@ -71742,6 +72141,15 @@ where ::visit_mut_opt_vec_pats(&mut **self, node, __ast_path) } + #[inline] + fn visit_mut_opt_wtf_8_atom( + &mut self, + node: &mut Option, + __ast_path: &mut AstKindPath, + ) { + ::visit_mut_opt_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn visit_mut_param(&mut self, node: &mut Param, __ast_path: &mut AstKindPath) { ::visit_mut_param(&mut **self, node, __ast_path) @@ -72661,6 +73069,15 @@ where ::visit_mut_with_stmt(&mut **self, node, __ast_path) } + #[inline] + fn visit_mut_wtf_8_atom( + &mut self, + node: &mut swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) { + ::visit_mut_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn visit_mut_yield_expr(&mut self, node: &mut YieldExpr, __ast_path: &mut AstKindPath) { ::visit_mut_yield_expr(&mut **self, node, __ast_path) @@ -74797,6 +75214,22 @@ where } } + #[inline] + fn visit_mut_opt_wtf_8_atom( + &mut self, + node: &mut Option, + __ast_path: &mut AstKindPath, + ) { + match self { + swc_visit::Either::Left(visitor) => { + VisitMutAstPath::visit_mut_opt_wtf_8_atom(visitor, node, __ast_path) + } + swc_visit::Either::Right(visitor) => { + VisitMutAstPath::visit_mut_opt_wtf_8_atom(visitor, node, __ast_path) + } + } + } + #[inline] fn visit_mut_param(&mut self, node: &mut Param, __ast_path: &mut AstKindPath) { match self { @@ -76657,6 +77090,22 @@ where } } + #[inline] + fn visit_mut_wtf_8_atom( + &mut self, + node: &mut swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) { + match self { + swc_visit::Either::Left(visitor) => { + VisitMutAstPath::visit_mut_wtf_8_atom(visitor, node, __ast_path) + } + swc_visit::Either::Right(visitor) => { + VisitMutAstPath::visit_mut_wtf_8_atom(visitor, node, __ast_path) + } + } + } + #[inline] fn visit_mut_yield_expr(&mut self, node: &mut YieldExpr, __ast_path: &mut AstKindPath) { match self { @@ -78303,6 +78752,18 @@ where } } + #[inline] + fn visit_mut_opt_wtf_8_atom( + &mut self, + node: &mut Option, + __ast_path: &mut AstKindPath, + ) { + if self.enabled { + ::visit_mut_opt_wtf_8_atom(&mut self.visitor, node, __ast_path) + } else { + } + } + #[inline] fn visit_mut_param(&mut self, node: &mut Param, __ast_path: &mut AstKindPath) { if self.enabled { @@ -79755,6 +80216,18 @@ where } } + #[inline] + fn visit_mut_wtf_8_atom( + &mut self, + node: &mut swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) { + if self.enabled { + ::visit_mut_wtf_8_atom(&mut self.visitor, node, __ast_path) + } else { + } + } + #[inline] fn visit_mut_yield_expr(&mut self, node: &mut YieldExpr, __ast_path: &mut AstKindPath) { if self.enabled { @@ -86656,7 +87129,7 @@ impl VisitMutWithAstPath for Str { { let mut __ast_path = __ast_path.with_guard(AstParentKind::Str(self::fields::StrField::Value)); - >::visit_mut_with_ast_path( + >::visit_mut_with_ast_path( value, visitor, &mut *__ast_path, @@ -87071,7 +87544,7 @@ impl VisitMutWithAstPath for TplElement { let mut __ast_path = __ast_path.with_guard(AstParentKind::TplElement( self::fields::TplElementField::Cooked, )); - as VisitMutWithAstPath>::visit_mut_with_ast_path( + as VisitMutWithAstPath>::visit_mut_with_ast_path( cooked, visitor, &mut *__ast_path, @@ -91831,6 +92304,27 @@ impl VisitMutWithAstPath for Vec> { } #[cfg(any(docsrs, feature = "path"))] #[cfg_attr(docsrs, doc(cfg(feature = "path")))] +impl VisitMutWithAstPath for Option { + #[doc = "Calls [VisitMutAstPath`::visit_mut_opt_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn visit_mut_with_ast_path(&mut self, visitor: &mut V, __ast_path: &mut AstKindPath) { + ::visit_mut_opt_wtf_8_atom(visitor, self, __ast_path) + } + + #[inline] + fn visit_mut_children_with_ast_path(&mut self, visitor: &mut V, __ast_path: &mut AstKindPath) { + match self { + Some(inner) => { + >::visit_mut_with_ast_path( + inner, visitor, __ast_path, + ) + } + None => {} + } + } +} +#[cfg(any(docsrs, feature = "path"))] +#[cfg_attr(docsrs, doc(cfg(feature = "path")))] impl VisitMutWithAstPath for Vec { #[doc = "Calls [VisitMutAstPath`::visit_mut_param_or_ts_param_props`] with `self`. (Extra impl)"] #[inline] @@ -92174,6 +92668,20 @@ impl VisitMutWithAstPath for Vec } #[cfg(any(docsrs, feature = "path"))] #[cfg_attr(docsrs, doc(cfg(feature = "path")))] +impl VisitMutWithAstPath for swc_atoms::Wtf8Atom { + #[doc = "Calls [VisitMutAstPath`::visit_mut_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn visit_mut_with_ast_path(&mut self, visitor: &mut V, __ast_path: &mut AstKindPath) { + ::visit_mut_wtf_8_atom(visitor, self, __ast_path) + } + + #[inline] + fn visit_mut_children_with_ast_path(&mut self, visitor: &mut V, __ast_path: &mut AstKindPath) { + {} + } +} +#[cfg(any(docsrs, feature = "path"))] +#[cfg_attr(docsrs, doc(cfg(feature = "path")))] impl VisitMutWithAstPath for std::boxed::Box where V: ?Sized + VisitMutAstPath, @@ -93309,6 +93817,16 @@ pub trait Fold { fn fold_opt_vec_pats(&mut self, node: Vec>) -> Vec> { > as FoldWith>::fold_children_with(node, self) } + #[doc = "Visit a node of type `Option < swc_atoms :: Wtf8Atom >`.\n\nBy default, this method \ + calls [`Option < swc_atoms :: Wtf8Atom >::fold_children_with`]. If you want to \ + recurse, you need to call it manually."] + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + ) -> Option { + as FoldWith>::fold_children_with(node, self) + } #[doc = "Visit a node of type `Param`.\n\nBy default, this method calls \ [`Param::fold_children_with`]. If you want to recurse, you need to call it manually."] #[inline] @@ -94258,6 +94776,13 @@ pub trait Fold { fn fold_with_stmt(&mut self, node: WithStmt) -> WithStmt { >::fold_children_with(node, self) } + #[doc = "Visit a node of type `swc_atoms :: Wtf8Atom`.\n\nBy default, this method calls \ + [`swc_atoms :: Wtf8Atom::fold_children_with`]. If you want to recurse, you need to \ + call it manually."] + #[inline] + fn fold_wtf_8_atom(&mut self, node: swc_atoms::Wtf8Atom) -> swc_atoms::Wtf8Atom { + >::fold_children_with(node, self) + } #[doc = "Visit a node of type `YieldExpr`.\n\nBy default, this method calls \ [`YieldExpr::fold_children_with`]. If you want to recurse, you need to call it \ manually."] @@ -95091,6 +95616,14 @@ where ::fold_opt_vec_pats(&mut **self, node) } + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + ) -> Option { + ::fold_opt_wtf_8_atom(&mut **self, node) + } + #[inline] fn fold_param(&mut self, node: Param) -> Param { ::fold_param(&mut **self, node) @@ -95790,6 +96323,11 @@ where ::fold_with_stmt(&mut **self, node) } + #[inline] + fn fold_wtf_8_atom(&mut self, node: swc_atoms::Wtf8Atom) -> swc_atoms::Wtf8Atom { + ::fold_wtf_8_atom(&mut **self, node) + } + #[inline] fn fold_yield_expr(&mut self, node: YieldExpr) -> YieldExpr { ::fold_yield_expr(&mut **self, node) @@ -96620,6 +97158,14 @@ where ::fold_opt_vec_pats(&mut **self, node) } + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + ) -> Option { + ::fold_opt_wtf_8_atom(&mut **self, node) + } + #[inline] fn fold_param(&mut self, node: Param) -> Param { ::fold_param(&mut **self, node) @@ -97319,6 +97865,11 @@ where ::fold_with_stmt(&mut **self, node) } + #[inline] + fn fold_wtf_8_atom(&mut self, node: swc_atoms::Wtf8Atom) -> swc_atoms::Wtf8Atom { + ::fold_wtf_8_atom(&mut **self, node) + } + #[inline] fn fold_yield_expr(&mut self, node: YieldExpr) -> YieldExpr { ::fold_yield_expr(&mut **self, node) @@ -98633,6 +99184,17 @@ where } } + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + ) -> Option { + match self { + swc_visit::Either::Left(visitor) => Fold::fold_opt_wtf_8_atom(visitor, node), + swc_visit::Either::Right(visitor) => Fold::fold_opt_wtf_8_atom(visitor, node), + } + } + #[inline] fn fold_param(&mut self, node: Param) -> Param { match self { @@ -99751,6 +100313,14 @@ where } } + #[inline] + fn fold_wtf_8_atom(&mut self, node: swc_atoms::Wtf8Atom) -> swc_atoms::Wtf8Atom { + match self { + swc_visit::Either::Left(visitor) => Fold::fold_wtf_8_atom(visitor, node), + swc_visit::Either::Right(visitor) => Fold::fold_wtf_8_atom(visitor, node), + } + } + #[inline] fn fold_yield_expr(&mut self, node: YieldExpr) -> YieldExpr { match self { @@ -101212,6 +101782,18 @@ where } } + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + ) -> Option { + if self.enabled { + ::fold_opt_wtf_8_atom(&mut self.visitor, node) + } else { + node + } + } + #[inline] fn fold_param(&mut self, node: Param) -> Param { if self.enabled { @@ -102451,6 +103033,15 @@ where } } + #[inline] + fn fold_wtf_8_atom(&mut self, node: swc_atoms::Wtf8Atom) -> swc_atoms::Wtf8Atom { + if self.enabled { + ::fold_wtf_8_atom(&mut self.visitor, node) + } else { + node + } + } + #[inline] fn fold_yield_expr(&mut self, node: YieldExpr) -> YieldExpr { if self.enabled { @@ -106002,7 +106593,7 @@ impl FoldWith for Str { match self { Str { span, value, raw } => { let span = { >::fold_with(span, visitor) }; - let value = { >::fold_with(value, visitor) }; + let value = { >::fold_with(value, visitor) }; let raw = { as FoldWith>::fold_with(raw, visitor) }; Str { span, value, raw } } @@ -106212,7 +106803,7 @@ impl FoldWith for TplElement { } => { let span = { >::fold_with(span, visitor) }; let cooked = - { as FoldWith>::fold_with(cooked, visitor) }; + { as FoldWith>::fold_with(cooked, visitor) }; let raw = { >::fold_with(raw, visitor) }; TplElement { span, @@ -108829,6 +109420,18 @@ impl FoldWith for Vec> { }) } } +impl FoldWith for Option { + #[doc = "Calls [Fold`::fold_opt_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn fold_with(self, visitor: &mut V) -> Self { + ::fold_opt_wtf_8_atom(visitor, self) + } + + #[inline] + fn fold_children_with(self, visitor: &mut V) -> Self { + self.map(|inner| >::fold_with(inner, visitor)) + } +} impl FoldWith for Vec { #[doc = "Calls [Fold`::fold_param_or_ts_param_props`] with `self`. (Extra impl)"] #[inline] @@ -109063,6 +109666,18 @@ impl FoldWith for Vec { }) } } +impl FoldWith for swc_atoms::Wtf8Atom { + #[doc = "Calls [Fold`::fold_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn fold_with(self, visitor: &mut V) -> Self { + ::fold_wtf_8_atom(visitor, self) + } + + #[inline] + fn fold_children_with(self, visitor: &mut V) -> Self { + self + } +} impl FoldWith for std::boxed::Box where V: ?Sized + Fold, @@ -110682,6 +111297,19 @@ pub trait FoldAstPath { node, self, __ast_path, ) } + #[doc = "Visit a node of type `Option < swc_atoms :: Wtf8Atom >`.\n\nBy default, this method \ + calls [`Option < swc_atoms :: Wtf8Atom >::fold_children_with_ast_path`]. If you want \ + to recurse, you need to call it manually."] + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + __ast_path: &mut AstKindPath, + ) -> Option { + as FoldWithAstPath>::fold_children_with_ast_path( + node, self, __ast_path, + ) + } #[doc = "Visit a node of type `Param`.\n\nBy default, this method calls \ [`Param::fold_children_with_ast_path`]. If you want to recurse, you need to call it \ manually."] @@ -112115,6 +112743,19 @@ pub trait FoldAstPath { fn fold_with_stmt(&mut self, node: WithStmt, __ast_path: &mut AstKindPath) -> WithStmt { >::fold_children_with_ast_path(node, self, __ast_path) } + #[doc = "Visit a node of type `swc_atoms :: Wtf8Atom`.\n\nBy default, this method calls \ + [`swc_atoms :: Wtf8Atom::fold_children_with_ast_path`]. If you want to recurse, you \ + need to call it manually."] + #[inline] + fn fold_wtf_8_atom( + &mut self, + node: swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) -> swc_atoms::Wtf8Atom { + >::fold_children_with_ast_path( + node, self, __ast_path, + ) + } #[doc = "Visit a node of type `YieldExpr`.\n\nBy default, this method calls \ [`YieldExpr::fold_children_with_ast_path`]. If you want to recurse, you need to call \ it manually."] @@ -113278,6 +113919,15 @@ where ::fold_opt_vec_pats(&mut **self, node, __ast_path) } + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + __ast_path: &mut AstKindPath, + ) -> Option { + ::fold_opt_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn fold_param(&mut self, node: Param, __ast_path: &mut AstKindPath) -> Param { ::fold_param(&mut **self, node, __ast_path) @@ -114305,6 +114955,15 @@ where ::fold_with_stmt(&mut **self, node, __ast_path) } + #[inline] + fn fold_wtf_8_atom( + &mut self, + node: swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) -> swc_atoms::Wtf8Atom { + ::fold_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn fold_yield_expr(&mut self, node: YieldExpr, __ast_path: &mut AstKindPath) -> YieldExpr { ::fold_yield_expr(&mut **self, node, __ast_path) @@ -115465,6 +116124,15 @@ where ::fold_opt_vec_pats(&mut **self, node, __ast_path) } + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + __ast_path: &mut AstKindPath, + ) -> Option { + ::fold_opt_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn fold_param(&mut self, node: Param, __ast_path: &mut AstKindPath) -> Param { ::fold_param(&mut **self, node, __ast_path) @@ -116492,6 +117160,15 @@ where ::fold_with_stmt(&mut **self, node, __ast_path) } + #[inline] + fn fold_wtf_8_atom( + &mut self, + node: swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) -> swc_atoms::Wtf8Atom { + ::fold_wtf_8_atom(&mut **self, node, __ast_path) + } + #[inline] fn fold_yield_expr(&mut self, node: YieldExpr, __ast_path: &mut AstKindPath) -> YieldExpr { ::fold_yield_expr(&mut **self, node, __ast_path) @@ -118704,6 +119381,22 @@ where } } + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + __ast_path: &mut AstKindPath, + ) -> Option { + match self { + swc_visit::Either::Left(visitor) => { + FoldAstPath::fold_opt_wtf_8_atom(visitor, node, __ast_path) + } + swc_visit::Either::Right(visitor) => { + FoldAstPath::fold_opt_wtf_8_atom(visitor, node, __ast_path) + } + } + } + #[inline] fn fold_param(&mut self, node: Param, __ast_path: &mut AstKindPath) -> Param { match self { @@ -120626,6 +121319,22 @@ where } } + #[inline] + fn fold_wtf_8_atom( + &mut self, + node: swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) -> swc_atoms::Wtf8Atom { + match self { + swc_visit::Either::Left(visitor) => { + FoldAstPath::fold_wtf_8_atom(visitor, node, __ast_path) + } + swc_visit::Either::Right(visitor) => { + FoldAstPath::fold_wtf_8_atom(visitor, node, __ast_path) + } + } + } + #[inline] fn fold_yield_expr(&mut self, node: YieldExpr, __ast_path: &mut AstKindPath) -> YieldExpr { match self { @@ -122425,6 +123134,19 @@ where } } + #[inline] + fn fold_opt_wtf_8_atom( + &mut self, + node: Option, + __ast_path: &mut AstKindPath, + ) -> Option { + if self.enabled { + ::fold_opt_wtf_8_atom(&mut self.visitor, node, __ast_path) + } else { + node + } + } + #[inline] fn fold_param(&mut self, node: Param, __ast_path: &mut AstKindPath) -> Param { if self.enabled { @@ -124004,6 +124726,19 @@ where } } + #[inline] + fn fold_wtf_8_atom( + &mut self, + node: swc_atoms::Wtf8Atom, + __ast_path: &mut AstKindPath, + ) -> swc_atoms::Wtf8Atom { + if self.enabled { + ::fold_wtf_8_atom(&mut self.visitor, node, __ast_path) + } else { + node + } + } + #[inline] fn fold_yield_expr(&mut self, node: YieldExpr, __ast_path: &mut AstKindPath) -> YieldExpr { if self.enabled { @@ -131434,7 +132169,7 @@ impl FoldWithAstPath for Str { let value = { let mut __ast_path = __ast_path.with_guard(AstParentKind::Str(self::fields::StrField::Value)); - >::fold_with_ast_path( + >::fold_with_ast_path( value, visitor, &mut *__ast_path, @@ -131874,7 +132609,7 @@ impl FoldWithAstPath for TplElement { let mut __ast_path = __ast_path.with_guard(AstParentKind::TplElement( self::fields::TplElementField::Cooked, )); - as FoldWithAstPath>::fold_with_ast_path( + as FoldWithAstPath>::fold_with_ast_path( cooked, visitor, &mut *__ast_path, @@ -136922,6 +137657,24 @@ impl FoldWithAstPath for Vec> { } #[cfg(any(docsrs, feature = "path"))] #[cfg_attr(docsrs, doc(cfg(feature = "path")))] +impl FoldWithAstPath for Option { + #[doc = "Calls [FoldAstPath`::fold_opt_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn fold_with_ast_path(self, visitor: &mut V, __ast_path: &mut AstKindPath) -> Self { + ::fold_opt_wtf_8_atom(visitor, self, __ast_path) + } + + #[inline] + fn fold_children_with_ast_path(self, visitor: &mut V, __ast_path: &mut AstKindPath) -> Self { + self.map(|inner| { + >::fold_with_ast_path( + inner, visitor, __ast_path, + ) + }) + } +} +#[cfg(any(docsrs, feature = "path"))] +#[cfg_attr(docsrs, doc(cfg(feature = "path")))] impl FoldWithAstPath for Vec { #[doc = "Calls [FoldAstPath`::fold_param_or_ts_param_props`] with `self`. (Extra impl)"] #[inline] @@ -137298,6 +138051,20 @@ impl FoldWithAstPath for Vec { } #[cfg(any(docsrs, feature = "path"))] #[cfg_attr(docsrs, doc(cfg(feature = "path")))] +impl FoldWithAstPath for swc_atoms::Wtf8Atom { + #[doc = "Calls [FoldAstPath`::fold_wtf_8_atom`] with `self`. (Extra impl)"] + #[inline] + fn fold_with_ast_path(self, visitor: &mut V, __ast_path: &mut AstKindPath) -> Self { + ::fold_wtf_8_atom(visitor, self, __ast_path) + } + + #[inline] + fn fold_children_with_ast_path(self, visitor: &mut V, __ast_path: &mut AstKindPath) -> Self { + self + } +} +#[cfg(any(docsrs, feature = "path"))] +#[cfg_attr(docsrs, doc(cfg(feature = "path")))] impl FoldWithAstPath for std::boxed::Box where V: ?Sized + FoldAstPath, diff --git a/crates/swc_estree_ast/src/lit.rs b/crates/swc_estree_ast/src/lit.rs index fc26f1f68fe6..2891a8aa0a98 100644 --- a/crates/swc_estree_ast/src/lit.rs +++ b/crates/swc_estree_ast/src/lit.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use serde::{ser::SerializeMap, Deserialize, Serialize}; -use swc_atoms::Atom; +use swc_atoms::{Atom, Wtf8Atom}; use swc_common::ast_serde; use crate::{common::BaseNode, expr::Expression, flavor::Flavor, typescript::TSType}; @@ -168,7 +168,7 @@ struct AcornLiteral<'a> { #[derive(Serialize)] #[serde(untagged)] enum AcornLiteralValue { - String(Atom), + String(Wtf8Atom), Numeric(f64), Null(Option<()>), Boolean(bool), @@ -181,7 +181,7 @@ enum AcornLiteralValue { pub struct StringLiteral { #[serde(flatten)] pub base: BaseNode, - pub value: Atom, + pub value: Wtf8Atom, pub raw: Atom, } @@ -243,7 +243,7 @@ pub struct TemplateElVal { #[serde(default)] pub raw: Atom, #[serde(default)] - pub cooked: Option, + pub cooked: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] diff --git a/crates/swc_node_bundler/src/loaders/swc.rs b/crates/swc_node_bundler/src/loaders/swc.rs index 606fa956e22e..f3eeed328a20 100644 --- a/crates/swc_node_bundler/src/loaders/swc.rs +++ b/crates/swc_node_bundler/src/loaders/swc.rs @@ -72,7 +72,7 @@ impl SwcLoader { Lit::Str(Str { span: DUMMY_SP, raw: None, - value: v, + value: v.into(), }) .into(), ); diff --git a/crates/swc_plugin_backend_tests/tests/fixture/swc_internal_plugin/Cargo.lock b/crates/swc_plugin_backend_tests/tests/fixture/swc_internal_plugin/Cargo.lock index 59540d3efc62..954a733789f1 100644 --- a/crates/swc_plugin_backend_tests/tests/fixture/swc_internal_plugin/Cargo.lock +++ b/crates/swc_plugin_backend_tests/tests/fixture/swc_internal_plugin/Cargo.lock @@ -58,7 +58,7 @@ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "ast_node" -version = "3.0.3" +version = "3.0.4" dependencies = [ "quote", "swc_macros_common", @@ -508,7 +508,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hstr" -version = "2.0.1" +version = "2.1.0" dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "swc_common" -version = "14.0.3" +version = "14.0.4" dependencies = [ "anyhow", "ast_node", @@ -1377,7 +1377,7 @@ dependencies = [ [[package]] name = "swc_core" -version = "42.0.0" +version = "42.1.0" dependencies = [ "swc_allocator", "swc_atoms", diff --git a/crates/swc_plugin_backend_tests/tests/fixture/swc_internal_plugin/src/lib.rs b/crates/swc_plugin_backend_tests/tests/fixture/swc_internal_plugin/src/lib.rs index 61735eb1a62c..57356eb40a23 100644 --- a/crates/swc_plugin_backend_tests/tests/fixture/swc_internal_plugin/src/lib.rs +++ b/crates/swc_plugin_backend_tests/tests/fixture/swc_internal_plugin/src/lib.rs @@ -28,7 +28,7 @@ impl VisitMut for ConsoleOutputReplacer { if ident.sym == *"console" { call.args[0].expr = Lit::Str(Str { span: DUMMY_SP, - value: atom!("changed_via_plugin"), + value: atom!("changed_via_plugin").into(), raw: Some(atom!("\"changed_via_plugin\"")), }) .into(); diff --git a/crates/swc_plugin_backend_wasmer/src/lib.rs b/crates/swc_plugin_backend_wasmer/src/lib.rs index 32cb202f85ec..241508dad25b 100644 --- a/crates/swc_plugin_backend_wasmer/src/lib.rs +++ b/crates/swc_plugin_backend_wasmer/src/lib.rs @@ -25,6 +25,7 @@ static ENGINE: Lazy> = Lazy::new(|| { // Use empty enumset to disable simd. use enumset::EnumSet; use wasmer::sys::{BaseTunables, CompilerConfig, EngineBuilder, Target, Triple}; + #[allow(unused_mut)] let mut set = EnumSet::new(); // [TODO]: Should we use is_x86_feature_detected! macro instead? diff --git a/crates/swc_typescript/src/fast_dts/enum.rs b/crates/swc_typescript/src/fast_dts/enum.rs index c3d58a916e8a..f43b385acc18 100644 --- a/crates/swc_typescript/src/fast_dts/enum.rs +++ b/crates/swc_typescript/src/fast_dts/enum.rs @@ -37,8 +37,12 @@ impl FastDts { prev_init_value = value.clone(); if let Some(value) = &value { let member_name = match &member.id { - TsEnumMemberId::Ident(ident) => &ident.sym, - TsEnumMemberId::Str(s) => &s.value, + TsEnumMemberId::Ident(ident) => ident.sym.clone(), + TsEnumMemberId::Str(s) => s + .value + .clone() + .try_into_atom() + .unwrap_or_else(|wtf8| Atom::from(&*wtf8.to_string_lossy())), #[cfg(swc_ast_unknown)] _ => panic!("unable to access unknown nodes"), }; @@ -92,7 +96,7 @@ impl FastDts { ) -> Option { match expr { Expr::Lit(lit) => match lit { - Lit::Str(s) => Some(ConstantValue::String(s.value.to_string())), + Lit::Str(s) => Some(ConstantValue::String(s.value.to_string_lossy().to_string())), Lit::Num(number) => Some(ConstantValue::Number(number.value)), Lit::Null(_) | Lit::BigInt(_) | Lit::Bool(_) | Lit::Regex(_) | Lit::JSXText(_) => { None diff --git a/crates/swc_typescript/src/fast_dts/types.rs b/crates/swc_typescript/src/fast_dts/types.rs index 9c61cac3e0df..8fe0d6dd9e2a 100644 --- a/crates/swc_typescript/src/fast_dts/types.rs +++ b/crates/swc_typescript/src/fast_dts/types.rs @@ -454,7 +454,10 @@ impl FastDts { tpl.quasis.first().map(|element| Str { span: DUMMY_SP, - value: element.cooked.as_ref().unwrap_or(&element.raw).clone(), + value: element + .cooked + .clone() + .unwrap_or_else(|| element.raw.clone().into()), raw: None, }) } diff --git a/crates/swc_typescript/src/fast_dts/util/ast_ext.rs b/crates/swc_typescript/src/fast_dts/util/ast_ext.rs index 51609f79d739..3c303d7d8b47 100644 --- a/crates/swc_typescript/src/fast_dts/util/ast_ext.rs +++ b/crates/swc_typescript/src/fast_dts/util/ast_ext.rs @@ -143,7 +143,7 @@ impl PropNameExit for PropName { fn static_name(&self) -> Option> { match self { PropName::Ident(ident_name) => Some(Cow::Borrowed(ident_name.sym.as_str())), - PropName::Str(string) => Some(Cow::Borrowed(string.value.as_str())), + PropName::Str(string) => Some(Cow::Borrowed(string.value.as_str()?)), PropName::Num(number) => Some(Cow::Owned(number.value.to_string())), PropName::BigInt(big_int) => Some(Cow::Owned(big_int.value.to_string())), PropName::Computed(computed_prop_name) => computed_prop_name.static_name(), @@ -164,7 +164,7 @@ impl PropNameExit for ComputedPropName { fn static_name(&self) -> Option> { match self.expr.as_ref() { Expr::Lit(lit) => match lit { - Lit::Str(string) => Some(Cow::Borrowed(string.value.as_str())), + Lit::Str(string) => Some(Cow::Borrowed(string.value.as_str()?)), Lit::Bool(b) => Some(Cow::Owned(b.value.to_string())), Lit::Null(_) => Some(Cow::Borrowed("null")), Lit::Num(number) => Some(Cow::Owned(number.value.to_string())), @@ -178,7 +178,7 @@ impl PropNameExit for ComputedPropName { .quasis .first() .and_then(|e| e.cooked.as_ref()) - .map(|atom| Cow::Borrowed(atom.as_str())), + .and_then(|atom| atom.as_str().map(Cow::Borrowed)), _ => None, } } @@ -241,7 +241,7 @@ impl MemberPropExt for MemberProp { match self { MemberProp::Ident(ident_name) => Some(&ident_name.sym), MemberProp::Computed(computed_prop_name) => match computed_prop_name.expr.as_ref() { - Expr::Lit(Lit::Str(s)) => Some(&s.value), + Expr::Lit(Lit::Str(s)) => s.value.as_atom(), Expr::Tpl(tpl) if tpl.quasis.len() == 1 && tpl.exprs.is_empty() => { Some(&tpl.quasis[0].raw) } diff --git a/packages/core/scripts/npm/darwin-arm64/package.json b/packages/core/scripts/npm/darwin-arm64/package.json index ab823f92653b..b5a279974c3b 100644 --- a/packages/core/scripts/npm/darwin-arm64/package.json +++ b/packages/core/scripts/npm/darwin-arm64/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/core/scripts/npm/darwin-x64/package.json b/packages/core/scripts/npm/darwin-x64/package.json index afc094d22385..01fe8e7a63c2 100644 --- a/packages/core/scripts/npm/darwin-x64/package.json +++ b/packages/core/scripts/npm/darwin-x64/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/core/scripts/npm/linux-arm-gnueabihf/package.json b/packages/core/scripts/npm/linux-arm-gnueabihf/package.json index 7dc9e0a020d6..149ac5183e40 100644 --- a/packages/core/scripts/npm/linux-arm-gnueabihf/package.json +++ b/packages/core/scripts/npm/linux-arm-gnueabihf/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/core/scripts/npm/linux-arm64-gnu/package.json b/packages/core/scripts/npm/linux-arm64-gnu/package.json index 9f86776d918d..69596fb282e5 100644 --- a/packages/core/scripts/npm/linux-arm64-gnu/package.json +++ b/packages/core/scripts/npm/linux-arm64-gnu/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/core/scripts/npm/linux-arm64-musl/package.json b/packages/core/scripts/npm/linux-arm64-musl/package.json index 4256239cc4b8..239a62fb320b 100644 --- a/packages/core/scripts/npm/linux-arm64-musl/package.json +++ b/packages/core/scripts/npm/linux-arm64-musl/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/core/scripts/npm/linux-x64-gnu/package.json b/packages/core/scripts/npm/linux-x64-gnu/package.json index b2b97b555774..c2ad10b40e84 100644 --- a/packages/core/scripts/npm/linux-x64-gnu/package.json +++ b/packages/core/scripts/npm/linux-x64-gnu/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/core/scripts/npm/linux-x64-musl/package.json b/packages/core/scripts/npm/linux-x64-musl/package.json index 86ff8552ca8f..583477398b4c 100644 --- a/packages/core/scripts/npm/linux-x64-musl/package.json +++ b/packages/core/scripts/npm/linux-x64-musl/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/core/scripts/npm/win32-arm64-msvc/package.json b/packages/core/scripts/npm/win32-arm64-msvc/package.json index 588ec50a5b03..7cb3fa331765 100644 --- a/packages/core/scripts/npm/win32-arm64-msvc/package.json +++ b/packages/core/scripts/npm/win32-arm64-msvc/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/core/scripts/npm/win32-ia32-msvc/package.json b/packages/core/scripts/npm/win32-ia32-msvc/package.json index edb58fdfe80f..bb70c88bcd74 100644 --- a/packages/core/scripts/npm/win32-ia32-msvc/package.json +++ b/packages/core/scripts/npm/win32-ia32-msvc/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/core/scripts/npm/win32-x64-msvc/package.json b/packages/core/scripts/npm/win32-x64-msvc/package.json index 515cb6fad759..6e0e965cbaa1 100644 --- a/packages/core/scripts/npm/win32-x64-msvc/package.json +++ b/packages/core/scripts/npm/win32-x64-msvc/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/helpers/package.json b/packages/helpers/package.json index 9b12512ba0f1..58f39ec1f39a 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -624,6 +624,12 @@ "import": "./esm/_ts_param.js", "default": "./cjs/_ts_param.cjs" }, + "./_/_ts_rewrite_relative_import_extension": { + "module-sync": "./esm/_ts_rewrite_relative_import_extension.js", + "webpack": "./esm/_ts_rewrite_relative_import_extension.js", + "import": "./esm/_ts_rewrite_relative_import_extension.js", + "default": "./cjs/_ts_rewrite_relative_import_extension.cjs" + }, "./_/_ts_values": { "module-sync": "./esm/_ts_values.js", "webpack": "./esm/_ts_values.js", diff --git a/packages/html/scripts/npm/darwin-arm64/package.json b/packages/html/scripts/npm/darwin-arm64/package.json index e532a7d5c9ba..7cb13ff76080 100644 --- a/packages/html/scripts/npm/darwin-arm64/package.json +++ b/packages/html/scripts/npm/darwin-arm64/package.json @@ -37,4 +37,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/html/scripts/npm/darwin-x64/package.json b/packages/html/scripts/npm/darwin-x64/package.json index 7ea2f0f049e1..c3e2ee4f7708 100644 --- a/packages/html/scripts/npm/darwin-x64/package.json +++ b/packages/html/scripts/npm/darwin-x64/package.json @@ -37,4 +37,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/html/scripts/npm/linux-arm-gnueabihf/package.json b/packages/html/scripts/npm/linux-arm-gnueabihf/package.json index acd197b1852e..a96e42d6fbd5 100644 --- a/packages/html/scripts/npm/linux-arm-gnueabihf/package.json +++ b/packages/html/scripts/npm/linux-arm-gnueabihf/package.json @@ -37,4 +37,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/html/scripts/npm/linux-arm64-gnu/package.json b/packages/html/scripts/npm/linux-arm64-gnu/package.json index d6ba9abcad56..5b10d565a209 100644 --- a/packages/html/scripts/npm/linux-arm64-gnu/package.json +++ b/packages/html/scripts/npm/linux-arm64-gnu/package.json @@ -40,4 +40,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/html/scripts/npm/linux-arm64-musl/package.json b/packages/html/scripts/npm/linux-arm64-musl/package.json index 2933be037a73..6f6fe136fc13 100644 --- a/packages/html/scripts/npm/linux-arm64-musl/package.json +++ b/packages/html/scripts/npm/linux-arm64-musl/package.json @@ -40,4 +40,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/html/scripts/npm/linux-x64-gnu/package.json b/packages/html/scripts/npm/linux-x64-gnu/package.json index 6744f2162c05..bc8f6d13293b 100644 --- a/packages/html/scripts/npm/linux-x64-gnu/package.json +++ b/packages/html/scripts/npm/linux-x64-gnu/package.json @@ -40,4 +40,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/html/scripts/npm/linux-x64-musl/package.json b/packages/html/scripts/npm/linux-x64-musl/package.json index 4f2b7bc7cfa4..45f2f8f7ace6 100644 --- a/packages/html/scripts/npm/linux-x64-musl/package.json +++ b/packages/html/scripts/npm/linux-x64-musl/package.json @@ -40,4 +40,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/html/scripts/npm/win32-arm64-msvc/package.json b/packages/html/scripts/npm/win32-arm64-msvc/package.json index ea97c87ae571..d42f5356ab9b 100644 --- a/packages/html/scripts/npm/win32-arm64-msvc/package.json +++ b/packages/html/scripts/npm/win32-arm64-msvc/package.json @@ -37,4 +37,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/html/scripts/npm/win32-ia32-msvc/package.json b/packages/html/scripts/npm/win32-ia32-msvc/package.json index 44a8e6146b46..d735737a3b18 100644 --- a/packages/html/scripts/npm/win32-ia32-msvc/package.json +++ b/packages/html/scripts/npm/win32-ia32-msvc/package.json @@ -37,4 +37,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/html/scripts/npm/win32-x64-msvc/package.json b/packages/html/scripts/npm/win32-x64-msvc/package.json index 10ee5d4d64cd..c5fd439a1e31 100644 --- a/packages/html/scripts/npm/win32-x64-msvc/package.json +++ b/packages/html/scripts/npm/win32-x64-msvc/package.json @@ -37,4 +37,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/darwin-arm64/package.json b/packages/minifier/scripts/npm/darwin-arm64/package.json index ab81a8ad06e6..d271854f15f3 100644 --- a/packages/minifier/scripts/npm/darwin-arm64/package.json +++ b/packages/minifier/scripts/npm/darwin-arm64/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/darwin-x64/package.json b/packages/minifier/scripts/npm/darwin-x64/package.json index 5276025decb4..e4cf1b1dc039 100644 --- a/packages/minifier/scripts/npm/darwin-x64/package.json +++ b/packages/minifier/scripts/npm/darwin-x64/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/linux-arm-gnueabihf/package.json b/packages/minifier/scripts/npm/linux-arm-gnueabihf/package.json index d778a32bc6ae..5ff35314b8bb 100644 --- a/packages/minifier/scripts/npm/linux-arm-gnueabihf/package.json +++ b/packages/minifier/scripts/npm/linux-arm-gnueabihf/package.json @@ -35,4 +35,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/linux-arm64-gnu/package.json b/packages/minifier/scripts/npm/linux-arm64-gnu/package.json index 42e76b04486f..bde1152ce4ee 100644 --- a/packages/minifier/scripts/npm/linux-arm64-gnu/package.json +++ b/packages/minifier/scripts/npm/linux-arm64-gnu/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/linux-arm64-musl/package.json b/packages/minifier/scripts/npm/linux-arm64-musl/package.json index c944c5951a1f..efd2a85521e1 100644 --- a/packages/minifier/scripts/npm/linux-arm64-musl/package.json +++ b/packages/minifier/scripts/npm/linux-arm64-musl/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/linux-x64-gnu/package.json b/packages/minifier/scripts/npm/linux-x64-gnu/package.json index aef429f0fc51..133bc7e89652 100644 --- a/packages/minifier/scripts/npm/linux-x64-gnu/package.json +++ b/packages/minifier/scripts/npm/linux-x64-gnu/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/linux-x64-musl/package.json b/packages/minifier/scripts/npm/linux-x64-musl/package.json index 3d44dc716ed4..8acdb61c6925 100644 --- a/packages/minifier/scripts/npm/linux-x64-musl/package.json +++ b/packages/minifier/scripts/npm/linux-x64-musl/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/win32-arm64-msvc/package.json b/packages/minifier/scripts/npm/win32-arm64-msvc/package.json index e1d72475d9e4..77159cab4f54 100644 --- a/packages/minifier/scripts/npm/win32-arm64-msvc/package.json +++ b/packages/minifier/scripts/npm/win32-arm64-msvc/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/win32-ia32-msvc/package.json b/packages/minifier/scripts/npm/win32-ia32-msvc/package.json index 0892a4a8b28e..5a1a3745b50b 100644 --- a/packages/minifier/scripts/npm/win32-ia32-msvc/package.json +++ b/packages/minifier/scripts/npm/win32-ia32-msvc/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/minifier/scripts/npm/win32-x64-msvc/package.json b/packages/minifier/scripts/npm/win32-x64-msvc/package.json index 92c12c3fdcd8..902f084ac14a 100644 --- a/packages/minifier/scripts/npm/win32-x64-msvc/package.json +++ b/packages/minifier/scripts/npm/win32-x64-msvc/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/darwin-arm64/package.json b/packages/react-compiler/scripts/npm/darwin-arm64/package.json index 330d9281746d..6b5e614a5f3a 100644 --- a/packages/react-compiler/scripts/npm/darwin-arm64/package.json +++ b/packages/react-compiler/scripts/npm/darwin-arm64/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/darwin-x64/package.json b/packages/react-compiler/scripts/npm/darwin-x64/package.json index d7582facee44..44492debd1c5 100644 --- a/packages/react-compiler/scripts/npm/darwin-x64/package.json +++ b/packages/react-compiler/scripts/npm/darwin-x64/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/linux-arm-gnueabihf/package.json b/packages/react-compiler/scripts/npm/linux-arm-gnueabihf/package.json index 335c432d998b..2b24caf954b3 100644 --- a/packages/react-compiler/scripts/npm/linux-arm-gnueabihf/package.json +++ b/packages/react-compiler/scripts/npm/linux-arm-gnueabihf/package.json @@ -35,4 +35,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/linux-arm64-gnu/package.json b/packages/react-compiler/scripts/npm/linux-arm64-gnu/package.json index fb872065644b..c209dd09606f 100644 --- a/packages/react-compiler/scripts/npm/linux-arm64-gnu/package.json +++ b/packages/react-compiler/scripts/npm/linux-arm64-gnu/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/linux-arm64-musl/package.json b/packages/react-compiler/scripts/npm/linux-arm64-musl/package.json index 7e91868ed0b7..16d1da071241 100644 --- a/packages/react-compiler/scripts/npm/linux-arm64-musl/package.json +++ b/packages/react-compiler/scripts/npm/linux-arm64-musl/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/linux-x64-gnu/package.json b/packages/react-compiler/scripts/npm/linux-x64-gnu/package.json index 919827b43071..74af8ff0c506 100644 --- a/packages/react-compiler/scripts/npm/linux-x64-gnu/package.json +++ b/packages/react-compiler/scripts/npm/linux-x64-gnu/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/linux-x64-musl/package.json b/packages/react-compiler/scripts/npm/linux-x64-musl/package.json index a5ca15aa2c8b..74e469b9125d 100644 --- a/packages/react-compiler/scripts/npm/linux-x64-musl/package.json +++ b/packages/react-compiler/scripts/npm/linux-x64-musl/package.json @@ -42,4 +42,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/win32-arm64-msvc/package.json b/packages/react-compiler/scripts/npm/win32-arm64-msvc/package.json index 706e2ebbead9..2b792b288f17 100644 --- a/packages/react-compiler/scripts/npm/win32-arm64-msvc/package.json +++ b/packages/react-compiler/scripts/npm/win32-arm64-msvc/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/win32-ia32-msvc/package.json b/packages/react-compiler/scripts/npm/win32-ia32-msvc/package.json index deb0bed2d503..c7574ee5aedc 100644 --- a/packages/react-compiler/scripts/npm/win32-ia32-msvc/package.json +++ b/packages/react-compiler/scripts/npm/win32-ia32-msvc/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +} diff --git a/packages/react-compiler/scripts/npm/win32-x64-msvc/package.json b/packages/react-compiler/scripts/npm/win32-x64-msvc/package.json index a9255e4db5d9..5547f7dedd08 100644 --- a/packages/react-compiler/scripts/npm/win32-x64-msvc/package.json +++ b/packages/react-compiler/scripts/npm/win32-x64-msvc/package.json @@ -39,4 +39,4 @@ "bugs": { "url": "https://github.com/swc-project/swc/issues" } -} \ No newline at end of file +}