You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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()
                            });
                        }
                    }
                }
            }
        }
    }
}

关键说明

  1. 表达式层级处理:通过visit_mut_expr捕获所有表达式节点,识别到TaggedTpl类型时直接替换整个表达式的类型,这是SWC中跨类型节点替换的标准方式。
  2. 节点类型判断:SWC中标识符的名称以Symbol类型存储,需通过sym字段比较;逐层解构tag节点,判断是否为console.log的成员表达式。
  3. 参数构建:遍历模板的quasis和expressions,交替生成字符串字面量和原表达式,保留原节点的span信息以保证sourcemap和错误提示的正确性。
  4. 节点替换:直接修改*expr的值,将Expr::TaggedTpl替换为Expr::Call,完成AST结构的转换。

内容的提问来源于stack exchange,提问作者jantimon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 11:00:05