如何在SWC Rust WASM插件中将TaggedTemplateExpression转为CallExpression
问题:将SWC中的TaggedTemplateExpression转换为CallExpression
我正在开发一款SWC Rust WASM插件以转换JavaScript代码。SWC及其API整体设计优良,但目前难以完成将TaggedTemplateExpression转换为CallExpression的操作,示例如下:
原代码:
console.log`Hello, ${name}!`;
转换后代码:
console.log("Hello, ", name, "!");
在Babel中该转换相对简单,示例代码如下:
const babel = require("@babel/core"); function transform({ types: t }) { return { visitor: { TaggedTemplateExpression(path) { const { tag, quasi } = path.node; if ( t.isMemberExpression(tag) && t.isIdentifier(tag.object, { name: "console" }) && t.isIdentifier(tag.property, { name: "log" }) ) { let args = []; quasi.quasis.forEach((element, index) => { args.push(t.stringLiteral(element.value.raw)); if (index < quasi.expressions.length) { args.push(quasi.expressions[index]); } }); path.replaceWith( t.callExpression( t.memberExpression( t.identifier("console"), t.identifier("log") ), args ) ); } }, }, }; }
我已查阅SWC CheatSheet的Changing AST Type章节,但仍不清楚如何正确实现转换,现有Rust代码如下:
use swc_core::ecma::{ ast::*, visit::{VisitMut, VisitMutWith}, }; struct TemplateToCallTransform; impl VisitMut for TemplateToCallTransform { fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) { // How do I replace the current node with the new CallExpr? } }
解决方案
要完成这个转换,需要调整SWC的访问逻辑:TaggedTpl是Expr的子类型,直接修改TaggedTpl节点无法将其替换为CallExpr,必须在表达式层级进行节点替换。以下是完整实现:
完整代码
use swc_core::ecma::{ ast::*, visit::{VisitMut, VisitMutWith}, }; struct TemplateToCallTransform; impl VisitMut for TemplateToCallTransform { fn visit_mut_expr(&mut self, expr: &mut Expr) { // 先递归处理子节点,确保内部转换完成 expr.visit_mut_children_with(self); // 检查当前表达式是否是TaggedTemplate类型 if let Expr::TaggedTpl(tagged_tpl) = expr { // 判断tag是否是console.log成员表达式 if let Expr::Member(MemberExpr { obj, prop, .. }) = &tagged_tpl.tag { if let Expr::Ident(obj_ident) = &**obj { if let PropName::Ident(prop_ident) = prop { if obj_ident.sym == *"console" && prop_ident.sym == *"log" { // 构建调用参数列表 let mut args = Vec::new(); let quasi = &tagged_tpl.quasi; for (index, elem) in quasi.quasis.iter().enumerate() { // 添加模板字符串片段的原始值 args.push(Expr::Lit(Lit::Str(Str { value: elem.value.raw.clone(), span: elem.span, has_escape: false, }))); // 如果还有对应的表达式,添加到参数列表 if index < quasi.expressions.len() { args.push(quasi.expressions[index].clone()); } } // 构建console.log成员表达式作为调用目标 let call_callee = Expr::Member(MemberExpr { obj: Box::new(Expr::Ident(Ident { sym: *"console", span: obj_ident.span, ..Default::default() })), prop: PropName::Ident(Ident { sym: *"log", span: prop_ident.span, ..Default::default() }), span: tagged_tpl.tag.span(), ..Default::default() }); // 将原表达式替换为CallExpr *expr = Expr::Call(CallExpr { callee: Box::new(call_callee), args: args.into_iter().map(|arg| ExprOrSpread { expr: Box::new(arg), spread: None, }).collect(), span: tagged_tpl.span, ..Default::default() }); } } } } } } }
关键说明
- 表达式层级处理:通过
visit_mut_expr捕获所有表达式节点,识别到TaggedTpl类型时直接替换整个表达式的类型,这是SWC中跨类型节点替换的标准方式。 - 节点类型判断:SWC中标识符的名称以
Symbol类型存储,需通过sym字段比较;逐层解构tag节点,判断是否为console.log的成员表达式。 - 参数构建:遍历模板的
quasis和expressions,交替生成字符串字面量和原表达式,保留原节点的span信息以保证sourcemap和错误提示的正确性。 - 节点替换:直接修改
*expr的值,将Expr::TaggedTpl替换为Expr::Call,完成AST结构的转换。
内容的提问来源于stack exchange,提问作者jantimon
相关产品推荐
相关产品推荐

