rspack_javascript_compiler/compiler/
stringify.rs1use std::sync::Arc;
2
3use rspack_error::Result;
4use rspack_sources::{Mapping, OriginalLocation, encode_mappings};
5use rustc_hash::FxHashMap;
6use swc_core::{
7 base::sourcemap,
8 common::{
9 BytePos, FileName, SourceMap as SwcSourceMap, comments::Comments,
10 source_map::SourceMapGenConfig,
11 },
12 ecma::{
13 ast::{EsVersion, Ident, Program as SwcProgram},
14 atoms::Atom,
15 codegen::{
16 self, Emitter, Node,
17 text_writer::{self, WriteJs},
18 },
19 visit::{Visit, VisitWith, noop_visit_type},
20 },
21};
22
23use super::{JavaScriptCompiler, TransformOutput};
24
25#[derive(Default, Clone, Debug)]
26pub struct SourceMapConfig {
27 pub enable: bool,
28 pub inline_sources_content: bool,
29 pub emit_columns: bool,
30 pub names: FxHashMap<BytePos, Atom>,
31}
32
33impl SourceMapGenConfig for SourceMapConfig {
34 fn file_name_to_source(&self, f: &FileName) -> String {
35 let f = f.to_string();
36 if f.starts_with('<') && f.ends_with('>') {
37 f[1..f.len() - 1].to_string()
38 } else {
39 f
40 }
41 }
42
43 fn inline_sources_content(&self, _: &FileName) -> bool {
44 self.inline_sources_content
45 }
46
47 fn emit_columns(&self, _f: &FileName) -> bool {
48 self.emit_columns
49 }
50
51 fn name_for_bytepos(&self, pos: BytePos) -> Option<&str> {
52 self.names.get(&pos).map(|v| &**v)
53 }
54}
55
56pub struct PrintOptions<'a> {
57 pub source_len: u32,
58 pub source_map: Arc<SwcSourceMap>,
59 pub target: EsVersion,
60 pub source_map_config: SourceMapConfig,
61 pub input_source_map: Option<&'a sourcemap::SourceMap>,
62 pub minify: bool,
63 pub comments: Option<&'a dyn Comments>,
64 pub preamble: &'a str,
65 pub ascii_only: bool,
66 pub inline_script: bool,
67}
68
69impl JavaScriptCompiler {
70 pub fn print(&self, node: &SwcProgram, options: PrintOptions<'_>) -> Result<TransformOutput> {
71 let PrintOptions {
72 source_len,
73 source_map,
74 target,
75 mut source_map_config,
76 input_source_map,
77 minify,
78 comments,
79 preamble,
80 ascii_only,
81 inline_script,
82 } = options;
83 let mut src_map_buf = vec![];
84
85 if source_map_config.enable {
86 let mut v = IdentCollector {
87 names: Default::default(),
88 };
89
90 node.visit_with(&mut v);
91
92 source_map_config.names = v.names;
93 }
94
95 let src = {
96 let mut buf = Vec::with_capacity(source_len as usize);
97 {
98 let mut w = text_writer::JsWriter::new(
99 source_map.clone(),
100 "\n",
101 &mut buf,
102 source_map_config.enable.then_some(&mut src_map_buf),
103 );
104
105 w.preamble(preamble)?;
106 let mut wr = Box::new(w) as Box<dyn WriteJs>;
107
108 if minify {
109 wr = Box::new(text_writer::omit_trailing_semi(wr));
110 }
111
112 let mut emitter = Emitter {
113 cfg: codegen::Config::default()
114 .with_minify(minify)
115 .with_target(target)
116 .with_ascii_only(ascii_only)
117 .with_inline_script(inline_script),
118 comments,
119 cm: source_map.clone(),
120 wr,
121 };
122 node.emit_with(&mut emitter)?;
123 }
124 unsafe { String::from_utf8_unchecked(buf) }
126 };
127
128 let map = if source_map_config.enable {
129 let combined_source_map =
130 source_map.build_source_map(&src_map_buf, input_source_map.cloned(), source_map_config);
131
132 let mappings = encode_mappings(combined_source_map.tokens().map(|token| Mapping {
133 generated_line: token.get_dst_line() + 1,
134 generated_column: token.get_dst_col(),
135 original: if token.has_source() {
136 Some(OriginalLocation {
137 source_index: token.get_src_id(),
138 original_line: token.get_src_line() + 1,
139 original_column: token.get_src_col(),
140 name_index: if token.has_name() {
141 Some(token.get_name_id())
142 } else {
143 None
144 },
145 })
146 } else {
147 None
148 },
149 }));
150
151 let mut rspack_source_map = rspack_sources::SourceMap::new(
152 mappings,
153 combined_source_map
154 .sources()
155 .map(ToString::to_string)
156 .collect::<Vec<_>>(),
157 combined_source_map
158 .source_contents()
159 .flatten()
160 .map(ToString::to_string)
161 .collect::<Vec<_>>(),
162 combined_source_map
163 .names()
164 .map(ToString::to_string)
165 .collect::<Vec<_>>(),
166 );
167 rspack_source_map.set_file(combined_source_map.get_file().map(ToString::to_string));
168
169 Some(rspack_source_map)
170 } else {
171 None
172 };
173
174 Ok(TransformOutput {
175 code: src,
176 map,
177 diagnostics: Default::default(),
178 })
179 }
180}
181
182struct IdentCollector {
183 pub names: FxHashMap<BytePos, Atom>,
184}
185
186impl Visit for IdentCollector {
187 noop_visit_type!();
188
189 fn visit_ident(&mut self, ident: &Ident) {
190 self.names.insert(ident.span.lo, ident.sym.clone());
191 }
192}