Rust代码生成与AST操作库swc_ecma_quote_macros的使用,高效实现JavaScript语法树宏转换

Rust代码生成与AST操作库swc_ecma_quote_macros的使用,高效实现JavaScript语法树宏转换

安装

在项目目录中运行以下Cargo命令:

cargo add swc_ecma_quote_macros

或者将以下行添加到Cargo.toml中:

swc_ecma_quote_macros = "22.0.0"

基本使用示例

swc_ecma_quote_macros提供了quote!宏,可以方便地生成JavaScript语法树节点。以下是一个基本示例:

use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;

fn create_function() -> Function {
    quote!("function add(a, b) { return a + b; }" as Function)
}

完整示例

下面是一个更完整的示例,展示了如何使用swc_ecma_quote_macros库来转换JavaScript代码:

use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;
use swc_common::sync::Lrc;
use swc_common::SourceMap;
use swc_ecma_codegen::{Emitter, Config};

fn main() {
    // 创建AST节点
    let function = quote!("function add(a, b) { return a + b; }" as Function);
    
    // 创建模块,包含我们的函数
    let module = Module {
        span: Default::default(),
        body: vec![
            ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
                ident: Ident::new("add".into(), Default::default()),
                declare: false,
                function: Box::new(function),
            }))),
        ],
        shebang: None,
    };
    
    // 设置代码生成器
    let cm: Lrc<SourceMap> = Default::default();
    let mut buf = vec![];
    {
        let mut emitter = Emitter {
            cfg: Config {
                ..Default::default()
            },
            cm: cm.clone(),
            wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new(
                cm.clone(),
                "\n",
                &mut buf,
                None,
            )),
            comments: None,
        };
        
        emitter.emit_module(&module).unwrap();
    }
    
    // 打印生成的代码
    println!("{}", String::from_utf8(buf).unwrap());
}

动态生成AST节点

swc_ecma_quote_macros还支持动态插入变量到生成的AST中:

use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;

fn create_function_with_params(params: Vec<Pat>) -> Function {
    let body = BlockStmt {
        span: Default::default(),
        stmts: vec![
            Stmt::Return(ReturnStmt {
                span: Default::default(),
                arg: Some(Box::new(Expr::Bin(BinExpr {
                    span: Default::default(),
                    op: BinaryOp::Add,
                    left: Box::new(Expr::Ident(Ident::new("a".into(), Default::default()))),
                    right: Box::new(Expr::Ident(Ident::new("b".into(), Default::default()))),
                }))),
            }),
        ],
    };
    
    quote!("function add($params) $body" as Function, params = params, body = body)
}

宏转换示例

以下示例展示了如何将简单的函数调用转换为更复杂的表达式:

use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;
use swc_common::DUMMY_SP;

fn transform_call_expr(call: CallExpr) -> Expr {
    match &call.callee {
        Callee::Expr(expr) => {
            if let Expr::Ident(ident) = &**expr {
                if ident.sym == "log" {
                    // 将简单的log调用转换为console.log
                    return Expr::Call(CallExpr {
                        callee: quote!("console.log" as Expr).into(),
                        args: call.args,
                        type_args: call.type_args,
                        span: DUMMY_SP,
                    });
                }
            }
        }
        _ => {}
    }
    
    Expr::Call(call)
}

完整示例Demo

以下是一个完整的代码转换示例,展示了如何使用swc_ecma_quote_macros来创建和转换JavaScript AST:

use swc_ecma_quote_macros::quote;
use swc_ecma_ast::*;
use swc_common::sync::Lrc;
use swc_common::SourceMap;
use swc_ecma_codegen::{Emitter, Config};
use swc_ecma_visit::{VisitMut, VisitMutWith};

// 自定义访问器,用于转换AST
struct LogToConsoleLog;

impl VisitMut for LogToConsoleLog {
    fn visit_mut_call_expr(&mut self, call: &mut CallExpr) {
        // 先访问子节点
        call.visit_mut_children_with(self);
        
        // 转换log调用为console.log
        if let Callee::Expr(expr) = &call.callee {
            if let Expr::Ident(ident) = &**expr {
                if ident.sym == "log" {
                    *call = CallExpr {
                        callee: quote!("console.log" as Expr).into(),
                        args: call.args.clone(),
                        type_args: call.type_args.clone(),
                        span: call.span,
                    };
                }
            }
        }
    }
}

fn main() {
    // 创建带有log调用的函数AST
    let mut module = Module {
        span: Default::default(),
        body: vec![
            ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
                ident: Ident::new("test".into(), Default::default()),
                declare: false,
                function: Box::new(quote!(
                    "function test() {
                        log('Hello');
                        log('World');
                    }" as Function
                )),
            }))),
        ],
        shebang: None,
    };
    
    // 应用转换
    module.visit_mut_with(&mut LogToConsoleLog);
    
    // 设置代码生成器
    let cm: Lrc<SourceMap> = Default::default();
    let mut buf = vec![];
    {
        let mut emitter = Emitter {
            cfg: Config {
                ..Default::default()
            },
            cm: cm.clone(),
            wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new(
                cm.clone(),
                "\n",
                &mut buf,
                None,
            )),
            comments: None,
        };
        
        emitter.emit_module(&module).unwrap();
    }
    
    // 打印转换后的代码
    println!("转换后的代码:\n{}", String::from_utf8(buf).unwrap());
}

这个示例演示了:

  1. 使用quote!宏创建JavaScript AST
  2. 自定义访问器实现AST转换
  3. 将log调用转换为console.log调用
  4. 将修改后的AST重新生成为JavaScript代码

输出结果应该是:

转换后的代码:
function test() {
    console.log('Hello');
    console.log('World');
}

1 回复

Rust代码生成与AST操作库swc_ecma_quote_macros使用指南

swc_ecma_quote_macros 是 SWC (Speedy Web Compiler) 生态系统中的一个强大工具,用于在 Rust 中高效地生成和操作 JavaScript 抽象语法树(AST)。它提供了一种类似 JSX 的宏语法来创建 AST 节点,极大简化了代码生成和转换工作。

核心功能

  • 通过类似 JavaScript 的语法直接生成 AST 节点
  • 支持 ECMAScript 所有语法结构
  • 类型安全的 AST 节点构建
  • 与 SWC 生态无缝集成

安装方法

Cargo.toml 中添加依赖:

[dependencies]
swc_ecma_quote = "0.105"
swc_ecma_ast = "0.105"

基本使用方法

1. 创建简单表达式

use swc_ecma_quote::quote;
use swc_ecma_ast::Expr;

let expr: Expr = quote!("1 + 1" as Expr);

2. 创建函数声明

use swc_ecma_quote::quote;
use swc_ecma_ast::FnDecl;

let func: FnDecl = quote!(
    "function add(a, b) { return a + b; }" as FnDecl
);

3. 创建带变量的模板

use swc_ecma_quote::quote;
use swc_ecma_ast::{Expr, Ident};

let x = Ident::new("x".into());
let y = Ident::new("y".into());

let expr: Expr = quote!(
    "$x + $y" as Expr,
    x: Expr = x.into(),
    y: Expr = y.into()
);

高级用法

1. 条件语句生成

use swc_ecma_quote::quote;
use swc_ecma_ast::Stmt;

let condition = quote!("a > b" as Expr);
let then_branch = quote!("console.log('a is greater')" as Stmt);
let else_branch = quote!("console.log('b is greater or equal')" as Stmt);

let if_stmt = quote!(
    "if ($cond) { $then } else { $else }" as Stmt,
    cond: Expr = condition,
    then: Stmt = then_branch,
    else: Stmt = else_branch
);

2. 循环语句生成

use swc_ecma_quote::quote;
use swc_ecma_ast::Stmt;

let init = quote!("let i = 极客" as VarDecl);
let test = quote!("i < 10" as Expr);
let update = quote!("i++" as Expr);
let body = quote!("console.log(i)" as Stmt);

let for_loop = quote!(
    "for ($init; $test; $update) { $body }" as Stmt,
    init: VarDecl = init,
    test: Expr = test,
    update: Expr = update,
    body: Stmt = body
);

3. 类定义生成

use swc_ecma_quote::quote;
use swc_ecma_ast::ClassDecl;

let class_decl = quote!(
    "class Person {
        constructor(name) {
            this.name = name;
        }
        
        greet() {
            return `Hello, ${this.name}!`;
        }
    }" as ClassDecl
);

与 SWC 转换器集成

use swc_ecma_quote::quote;
use swc_ecma_visit::{VisitMut, VisitMutWith};

struct MyVisitor;

impl VisitMut for MyVisitor {
    fn visit_mut_expr(&mut self, expr: &mut Expr) {
        // 将所有的数字字面量乘以2
        if let Expr::Lit(Lit::Num(num)) = expr {
            *expr = quote!(
                "${value} * 2" as Expr,
                value: Expr = Expr::Lit(Lit::Num(*num))
            );
        }
        
        expr.visit_mut_children_with(self);
    }
}

完整示例代码

下面是一个完整的示例,展示了如何使用 swc_ecma_quote_macros 来生成一个简单的 JavaScript 程序:

use swc_ecma_ast::{Expr, FnDecl, Stmt, Ident, VarDecl, ClassDecl};
use swc_ecma_quote::quote;

fn main() {
    // 1. 创建简单表达式
    let expr: Expr = quote!("1 + 1" as Expr);
    println!("生成的表达式: {:?}", expr);

    // 2. 创建函数声明
    let func: FnDecl = quote!(
        "function add(a, b) { return a + b; }" as FnDecl
    );
    println!("生成的函数: {:?}", func);

    // 3. 创建带变量的模板
    let x = Ident::new("x".into());
    let y = Ident::new("y".into());
    let expr_with_vars: Expr = quote!(
        "$x + $y" as Expr,
        x: Expr = x.into(),
        y: Expr = y.into()
    );
    println!("带变量的表达式: {:?}", expr_with_vars);

    // 4. 创建条件语句
    let condition = quote!("a > b" as Expr);
    let then_branch = quote!("console.log('a is greater')" as Stmt);
    let else_branch = quote!("console.log('b is greater or equal')" as Stmt);
    let if_stmt = quote!(
        "if ($cond) { $then } else { $else }" as Stmt,
        cond: Expr = condition,
        then: Stmt = then_branch,
        else: Stmt = else_branch
    );
    println!("生成的条件语句: {:?}", if_stmt);

    // 5. 创建类定义
    let class_decl = quote!(
        "class Person {
            constructor(name) {
                this.name = name;
            }
            
            greet() {
                return `Hello, ${this.name}!`;
            }
        }" as ClassDecl
    );
    println!("生成的类定义: {:?}", class_decl);
}

最佳实践

  1. 复用 AST 片段:将常用模式抽象为函数返回 AST 节点
  2. 类型安全:充分利用 Rust 的类型系统确保生成的 AST 节点正确
  3. 性能考虑:对于大量 AST 生成,考虑使用 swc_ecma_codegen 直接生成代码
  4. 调试:使用 swc_ecma_utils::dump_ast 调试生成的 AST

注意事项

  • 宏中的字符串模板必须使用有效的 JavaScript 语法
  • 变量替换使用 $var 语法
  • 需要显式指定目标 AST 节点类型 (as Type)
  • 对于复杂转换,建议结合 swc_ecma_visit 使用

swc_ecma_quote_macros 提供了一种声明式、类型安全的方式来操作 JavaScript AST,特别适合构建代码转换工具、语法扩展和 DSL 实现。

回到顶部