Skip to content

Commit d973355

Browse files
committed
fix(transformer): handle escape sequences in tagged template transform
1 parent 4fe3aac commit d973355

File tree

6 files changed

+45
-12
lines changed

6 files changed

+45
-12
lines changed

crates/oxc_transformer/src/plugins/tagged_template_transform.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -132,36 +132,60 @@ impl<'a, 'ctx> TaggedTemplateTransform<'a, 'ctx> {
132132
///
133133
/// Handle its fields as follows:
134134
/// quasis:
135-
/// - Create an array expression containing the raw string literals
136-
/// - Call the helper function with the array expression
135+
/// - Create an array expression containing the cooked string literals
136+
/// - If cooked differs from raw, create a second array with raw strings
137+
/// - Call the helper function with the array expression(s)
137138
/// - Create a logical OR expression to cache the result in the binding
138139
/// - Wrap the logical OR expression as the first argument
139140
/// expressions:
140141
/// - Add each expression as the remaining arguments
141142
///
142-
/// Final arguments: `(binding || (binding = babelHelpers.taggedTemplateLiteral([<...quasis>])), <...expressions>)`
143+
/// Final arguments:
144+
/// - `(binding || (binding = babelHelpers.taggedTemplateLiteral([<...cooked>])), <...expressions>)` when cooked == raw
145+
/// - `(binding || (binding = babelHelpers.taggedTemplateLiteral([<...cooked>], [<...raw>])), <...expressions>)` when cooked != raw
143146
fn transform_template_literal(
144147
&self,
145148
binding: &BoundIdentifier<'a>,
146149
quasi: TemplateLiteral<'a>,
147150
ctx: &mut TraverseCtx<'a>,
148151
) -> ArenaVec<'a, Argument<'a>> {
149-
// `([<...quasis>])` (e.g. `["first", "second", "third"]`)
150-
let elements = ctx.ast.vec_from_iter(quasi.quasis.iter().map(|quasi| {
151-
let string = ctx.ast.expression_string_literal(SPAN, quasi.value.raw, None);
152-
ArrayExpressionElement::from(string)
152+
// Check if we need to pass the raw array separately
153+
let needs_raw_array = quasi.quasis.iter().any(|quasi| match &quasi.value.cooked {
154+
None => true, // Invalid escape sequence - cooked is None
155+
Some(cooked) => cooked.as_str() != quasi.value.raw.as_str(),
156+
});
157+
158+
// Create cooked array: `[cooked0, cooked1, ...]`
159+
// Use `void 0` for elements with invalid escape sequences (where cooked is None)
160+
let cooked_elements = ctx.ast.vec_from_iter(quasi.quasis.iter().map(|quasi| {
161+
let expr = match &quasi.value.cooked {
162+
Some(cooked) => ctx.ast.expression_string_literal(SPAN, *cooked, None),
163+
None => ctx.ast.void_0(SPAN),
164+
};
165+
ArrayExpressionElement::from(expr)
153166
}));
167+
let cooked_argument = Argument::from(ctx.ast.expression_array(SPAN, cooked_elements));
168+
169+
// Add raw array if needed: `[raw0, raw1, ...]`
170+
let raws_argument = needs_raw_array.then(|| {
171+
let elements = ctx.ast.vec_from_iter(quasi.quasis.iter().map(|quasi| {
172+
let string = ctx.ast.expression_string_literal(SPAN, quasi.value.raw, None);
173+
ArrayExpressionElement::from(string)
174+
}));
175+
Argument::from(ctx.ast.expression_array(SPAN, elements))
176+
});
177+
154178
let template_arguments =
155-
ctx.ast.vec1(Argument::from(ctx.ast.expression_array(SPAN, elements)));
179+
ctx.ast.vec_from_iter(iter::once(cooked_argument).chain(raws_argument));
156180

157-
// `babelHelpers.taggedTemplateLiteral([<...quasis>])`
181+
// `babelHelpers.taggedTemplateLiteral([<...cooked>], [<...raw>]?)`
158182
let template_call =
159183
self.ctx.helper_call_expr(Helper::TaggedTemplateLiteral, SPAN, template_arguments, ctx);
160-
// `binding || (binding = babelHelpers.taggedTemplateLiteral([<...quasis>]))`
184+
// `binding || (binding = babelHelpers.taggedTemplateLiteral([<...cooked>], [<...raw>]?))`
161185
let template_call =
162186
Argument::from(Self::create_logical_or_expression(binding, template_call, ctx));
163187

164-
// `(binding || (binding = babelHelpers.taggedTemplateLiteral([<...quasis>])), <...expressions>)`
188+
// `(binding || (binding = babelHelpers.taggedTemplateLiteral([<...cooked>], [<...raw>]?)), <...expressions>)`
165189
ctx.ast.vec_from_iter(
166190
// Add the template expressions as the remaining arguments
167191
iter::once(template_call).chain(quasi.expressions.into_iter().map(Argument::from)),

tasks/transform_conformance/snapshots/oxc.snap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
commit: c92c4919
22

3-
Passed: 198/329
3+
Passed: 200/331
44

55
# All Passed:
66
* babel-plugin-transform-class-static-block
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
foo`</script>\n`
2+
bar`</script>\t`
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
var _templateObject;
2+
var _templateObject2;
3+
foo(_templateObject || (_templateObject = babelHelpers.taggedTemplateLiteral(["</script>\n"], ["</script>\\n"])));
4+
bar(_templateObject2 || (_templateObject2 = babelHelpers.taggedTemplateLiteral(["</script>\t"], ["</script>\\t"])));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo`</script>\u`
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
var _templateObject;
2+
foo(_templateObject || (_templateObject = babelHelpers.taggedTemplateLiteral([void 0], ["</script>\\u"])));

0 commit comments

Comments
 (0)