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

如何从swc_ecma_parser获取注释并修改注释内容?

问题

用swc_ecma_parser解析脚本、修改AST再生成新代码时,发现AST里没包含注释节点——就像测试代码里的// test直接被剥离了。怎么拿到这个注释,还把它改成// modified comment?

测试代码:

use swc_common::sync::Lrc;
use swc_common::{FileName, SourceMap};
use swc_ecma_parser::{Parser, StringInput, Syntax};

fn main() {
    let cm: Lrc<SourceMap> = Default::default();

    // Real usage
    // let fm = cm
    //     .load_file(Path::new("test.js"))
    //     .expect("failed to load test.js");
    let fm = cm.new_source_file(
        FileName::Custom("test.js".into()),
        "
        // test
        function foo() {}
        "
        .into(),
    );

    let ts_config = swc_ecma_parser::TsConfig {
        decorators: true,
        // dynamic_import: true,
        tsx: true,
        ..Default::default()
    };

    let mut parser = Parser::new(
        Syntax::Typescript(ts_config),
        StringInput::from(&*fm),
        Default::default(),
    );

    let module = parser
        .parse_typescript_module()
        .expect("failed to parser module");

    module.body.into_iter().for_each(|module_item| {
        println!("{:?}", module_item);
    });
}

运行后打印的输出只有函数声明节点,注释没了:

Stmt(Decl(Fn(FnDecl { ident: Ident { span: Span { lo: BytePos(35), hi: BytePos(38), ctxt: #0 }, sym: "foo", optional: false }, declare: false, function: Function { params: [], decorators: [], span: Span { lo: BytePos(26), hi: BytePos(43), ctxt: #0 }, body: Some(BlockStmt { span: Span { lo: BytePos(41), hi: BytePos(43), ctxt: #0 }, stmts: [] }), is_generator: false, is_async: false, type_params: None, return_type: None } })))

解决方案

SWC默认不会把注释绑定到AST节点上,得手动开注释收集,把注释和对应节点关联上,之后才能修改并保留注释。

1. 开启注释收集

创建Parser的时候,要传自定义的ParserConfig,打开comments选项,解析完再用take_comments拿到收集到的注释:

把原来的Parser初始化代码换成下面这段:

use swc_ecma_parser::ParserConfig;

let mut parser = Parser::new(
    Syntax::Typescript(ts_config),
    StringInput::from(&*fm),
    ParserConfig {
        comments: true, // 开启注释收集
        ..Default::default()
    },
);

// 解析模块后获取所有注释
let module = parser.parse_typescript_module().expect("failed to parser module");
let mut comments = parser.take_comments();

2. 找到对应节点的注释

SWC的注释是单独收集的,得通过节点的span(位置信息)判断注释属于哪个节点——比如找函数声明前面的单行注释:

use swc_ecma_ast::Comment;

// 遍历注释,找到函数声明前面的目标单行注释
let target_comment = comments.iter_mut().find(|comment| {
    comment.span.hi <= module.body[0].span().lo && comment.kind.is_line()
});

3. 修改注释内容

找到目标注释后,直接改它的text字段就行——注意SWC的text不含//,所以要留个空格:

if let Some(comment) = target_comment {
    comment.text = " modified comment".into();
}

4. 生成带注释的代码

用SWC的代码生成器swc_ecma_codegen时,得把注释传进去,不然生成的代码里还是没有注释。

首先在Cargo.toml加依赖:

[dependencies]
swc_ecma_codegen = "0.109"
swc_ecma_codegen_macros = "0.109"

然后加生成代码的逻辑:

use swc_ecma_codegen::{text_writer::JsWriter, Emitter, Config};
use std::io::Cursor;

// 创建输出缓冲区
let mut buf = Cursor::new(Vec::new());
let writer = JsWriter::new(cm.clone(), "\n", &mut buf, None);

// 创建Emitter时传入注释
let mut emitter = Emitter {
    cfg: Config {
        minify: false,
        ..Default::default()
    },
    comments: Some(&comments),
    ..Emitter::new(writer)
};

// 生成代码
emitter.emit_module(&module).expect("failed to emit module");

// 输出结果
let output = String::from_utf8(buf.into_inner()).unwrap();
println!("{}", output);

完整修改后的代码

use swc_common::sync::Lrc;
use swc_common::{FileName, SourceMap};
use swc_ecma_parser::{Parser, ParserConfig, StringInput, Syntax};
use swc_ecma_codegen::{text_writer::JsWriter, Emitter, Config};
use swc_ecma_ast::Comment;
use std::io::Cursor;

fn main() {
    let cm: Lrc<SourceMap> = Default::default();

    let fm = cm.new_source_file(
        FileName::Custom("test.js".into()),
        "
        // test
        function foo() {}
        "
        .into(),
    );

    let ts_config = swc_ecma_parser::TsConfig {
        decorators: true,
        tsx: true,
        ..Default::default()
    };

    let mut parser = Parser::new(
        Syntax::Typescript(ts_config),
        StringInput::from(&*fm),
        ParserConfig {
            comments: true,
            ..Default::default()
        },
    );

    let module = parser.parse_typescript_module().expect("failed to parser module");
    let mut comments = parser.take_comments();

    // 找到并修改目标注释
    let target_comment = comments.iter_mut().find(|comment| {
        comment.span.hi <= module.body[0].span().lo && comment.kind.is_line()
    });

    if let Some(comment) = target_comment {
        comment.text = " modified comment".into();
    }

    // 生成带注释的代码
    let mut buf = Cursor::new(Vec::new());
    let writer = JsWriter::new(cm.clone(), "\n", &mut buf, None);

    let mut emitter = Emitter {
        cfg: Config {
            minify: false,
            ..Default::default()
        },
        comments: Some(&comments),
        ..Emitter::new(writer)
    };

    emitter.emit_module(&module).expect("failed to emit module");

    let output = String::from_utf8(buf.into_inner()).unwrap();
    println!("{}", output);
}

运行结果

跑起来之后会输出:

// modified comment
        function foo() {}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 06:12:02