@@ -52,6 +52,9 @@ use crate::{
5252 state:: TransformState ,
5353} ;
5454
55+ const SCRIPT_TAG : & [ u8 ; 8 ] = b"</script" ;
56+ const SCRIPT_TAG_LEN : usize = SCRIPT_TAG . len ( ) ;
57+
5558pub struct TaggedTemplateTransform < ' a , ' ctx > {
5659 ctx : & ' ctx TransformCtx < ' a > ,
5760}
@@ -71,27 +74,27 @@ impl<'a, 'ctx> TaggedTemplateTransform<'a, 'ctx> {
7174
7275 /// Check if the template literal contains a `</script` tag; note it is case-insensitive.
7376 fn contains_closing_script_tag ( quasi : & TemplateLiteral ) -> bool {
74- const SCRIPT_TAG : & [ u8 ] = b"</script" ;
75-
7677 quasi. quasis . iter ( ) . any ( |quasi| {
7778 let raw = & quasi. value . raw ;
7879
7980 // The raw string must be at least as long as the script tag
80- if raw. len ( ) < SCRIPT_TAG . len ( ) {
81+ if raw. len ( ) < SCRIPT_TAG_LEN {
8182 return false ;
8283 }
8384
8485 let raw_bytes = raw. as_bytes ( ) ;
8586 // Get the bytes up to the last possible starting position of the script tag
86- let max_remain_len = raw_bytes. len ( ) . saturating_sub ( SCRIPT_TAG . len ( ) ) ;
87- let raw_bytes_iter = raw_bytes[ ..=max_remain_len] . iter ( ) . copied ( ) . enumerate ( ) ;
88- for ( idx, byte) in raw_bytes_iter {
89- if byte == b'<'
90- && SCRIPT_TAG
91- . iter ( )
92- . zip ( raw_bytes[ idx..] . iter ( ) )
93- . all ( |( a, b) | * a == b. to_ascii_lowercase ( ) )
94- {
87+
88+ let max_start_pos = raw_bytes. len ( ) - SCRIPT_TAG_LEN ;
89+ for ( i, byte) in raw_bytes[ ..=max_start_pos] . iter ( ) . copied ( ) . enumerate ( ) {
90+ // The first character must be a `<`
91+ if byte != b'<' {
92+ continue ;
93+ }
94+
95+ // Check if this position contains "</script"
96+ let slice = & raw_bytes[ i..i + SCRIPT_TAG_LEN ] ;
97+ if is_script_close_tag ( slice) {
9598 return true ;
9699 }
97100 }
@@ -101,8 +104,6 @@ impl<'a, 'ctx> TaggedTemplateTransform<'a, 'ctx> {
101104 }
102105
103106 /// Transform a tagged template expression to use the [`Helper::TaggedTemplateLiteral`] helper function
104- // `#[inline]` so that compiler can see `expr` should be a `TaggedTemplateExpression` and reduce redundant checks
105- #[ inline]
106107 fn transform_tagged_template ( & self , expr : & mut Expression < ' a > , ctx : & mut TraverseCtx < ' a > ) {
107108 if !matches ! ( expr, Expression :: TaggedTemplateExpression ( tagged) if Self :: contains_closing_script_tag( & tagged. quasi) )
108109 {
@@ -236,3 +237,24 @@ impl<'a, 'ctx> TaggedTemplateTransform<'a, 'ctx> {
236237 binding
237238 }
238239}
240+
241+ /// Check if `slice` is `</script`, regardless of case.
242+ ///
243+ /// `slice.len()` must be 8.
244+ //
245+ // NOTE: This function is copied from `oxc_codegen/src/str.rs`.
246+ //
247+ // `#[inline(always)]` so that compiler can see from caller that `slice.len() == 8`
248+ // and so `slice.try_into().unwrap()` cannot fail. This function is only 4 instructions.
249+ #[ expect( clippy:: inline_always) ]
250+ #[ inline( always) ]
251+ pub fn is_script_close_tag ( slice : & [ u8 ] ) -> bool {
252+ // Compiler condenses these operations to an 8-byte read, u64 AND, and u64 compare.
253+ // https://godbolt.org/z/K8q68WGn6
254+ let mut bytes: [ u8 ; 8 ] = slice. try_into ( ) . unwrap ( ) ;
255+ for byte in bytes. iter_mut ( ) . skip ( 2 ) {
256+ // `| 32` converts ASCII upper case letters to lower case.
257+ * byte |= 32 ;
258+ }
259+ bytes == * SCRIPT_TAG
260+ }
0 commit comments