如何从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

