swc_ecma_codegen/text_writer/
basic_impl.rs1use std::io::Write;
2
3use rustc_hash::FxBuildHasher;
4use swc_allocator::api::global::HashSet;
5use swc_common::{sync::Lrc, BytePos, LineCol, SourceMap, Span};
6
7use super::{Result, WriteJs};
8
9pub struct JsWriter<'a, W: Write> {
16 indent: usize,
17 indent_str: &'static str,
18 line_start: bool,
19 line_count: usize,
20 line_pos: usize,
21 new_line: &'a str,
22 srcmap: Option<&'a mut Vec<(BytePos, LineCol)>>,
23 srcmap_done: HashSet<(BytePos, u32, u32), FxBuildHasher>,
24 pending_srcmap: Option<BytePos>,
26 wr: W,
27}
28
29impl<'a, W: Write> JsWriter<'a, W> {
30 pub fn new(
31 _: Lrc<SourceMap>,
32 new_line: &'a str,
33 wr: W,
34 srcmap: Option<&'a mut Vec<(BytePos, LineCol)>>,
35 ) -> Self {
36 JsWriter {
37 indent: Default::default(),
38 indent_str: " ",
39 line_start: true,
40 line_count: 0,
41 line_pos: Default::default(),
42 new_line,
43 srcmap,
44 wr,
45 pending_srcmap: Default::default(),
46 srcmap_done: Default::default(),
47 }
48 }
49
50 pub fn preamble(&mut self, s: &str) -> Result {
51 self.raw_write(s)?;
52 self.update_pos(s);
53
54 Ok(())
55 }
56
57 pub fn set_indent_str(&mut self, indent_str: &'static str) {
59 self.indent_str = indent_str;
60 }
61
62 #[inline]
63 fn write_indent_string(&mut self) -> Result {
64 for _ in 0..self.indent {
65 self.raw_write(self.indent_str)?;
66 }
67 if self.srcmap.is_some() {
68 self.line_pos += self.indent_str.len() * self.indent;
69 }
70
71 Ok(())
72 }
73
74 #[inline]
75 fn raw_write(&mut self, data: &str) -> Result {
76 self.wr.write_all(data.as_bytes())?;
77
78 Ok(())
79 }
80
81 #[inline(always)]
82 fn write(&mut self, span: Option<Span>, data: &str) -> Result {
83 if !data.is_empty() {
84 if self.line_start {
85 self.write_indent_string()?;
86 self.line_start = false;
87
88 if let Some(pending) = self.pending_srcmap.take() {
89 self.srcmap(pending);
90 }
91 }
92
93 if let Some(span) = span {
94 self.srcmap(span.lo());
95 }
96
97 self.raw_write(data)?;
98 self.update_pos(data);
99
100 if let Some(span) = span {
101 self.srcmap(span.hi());
102 }
103 }
104
105 Ok(())
106 }
107
108 #[inline]
109 fn update_pos(&mut self, s: &str) {
110 if self.srcmap.is_some() {
111 let line_start_of_s = compute_line_starts(s);
112 self.line_count += line_start_of_s.line_count;
113
114 let chars = s[line_start_of_s.byte_pos..].encode_utf16().count();
115 if line_start_of_s.line_count > 0 {
116 self.line_pos = chars;
117 } else {
118 self.line_pos += chars;
119 }
120 }
121 }
122
123 #[inline]
124 fn srcmap(&mut self, byte_pos: BytePos) {
125 if byte_pos.is_dummy() && byte_pos != BytePos(u32::MAX) {
126 return;
127 }
128
129 if let Some(ref mut srcmap) = self.srcmap {
130 if self
131 .srcmap_done
132 .insert((byte_pos, self.line_count as _, self.line_pos as _))
133 {
134 let loc = LineCol {
135 line: self.line_count as _,
136 col: self.line_pos as _,
137 };
138
139 srcmap.push((byte_pos, loc));
140 }
141 }
142 }
143}
144
145impl<W: Write> WriteJs for JsWriter<'_, W> {
146 #[inline]
147 fn increase_indent(&mut self) -> Result {
148 self.indent += 1;
149 Ok(())
150 }
151
152 #[inline]
153 fn decrease_indent(&mut self) -> Result {
154 self.indent -= 1;
155 Ok(())
156 }
157
158 #[inline]
159 fn write_semi(&mut self, span: Option<Span>) -> Result {
160 self.write(span, ";")?;
161 Ok(())
162 }
163
164 #[inline]
165 fn write_space(&mut self) -> Result {
166 self.write(None, " ")?;
167 Ok(())
168 }
169
170 #[inline]
171 fn write_keyword(&mut self, span: Option<Span>, s: &'static str) -> Result {
172 self.write(span, s)?;
173 Ok(())
174 }
175
176 #[inline]
177 fn write_operator(&mut self, span: Option<Span>, s: &str) -> Result {
178 self.write(span, s)?;
179 Ok(())
180 }
181
182 #[inline]
183 fn write_param(&mut self, s: &str) -> Result {
184 self.write(None, s)?;
185 Ok(())
186 }
187
188 #[inline]
189 fn write_property(&mut self, s: &str) -> Result {
190 self.write(None, s)?;
191 Ok(())
192 }
193
194 #[inline]
195 fn write_line(&mut self) -> Result {
196 let pending = self.pending_srcmap.take();
197 if !self.line_start {
198 self.raw_write(self.new_line)?;
199 if self.srcmap.is_some() {
200 self.line_count += 1;
201 self.line_pos = 0;
202 }
203 self.line_start = true;
204
205 if let Some(pending) = pending {
206 self.srcmap(pending)
207 }
208 }
209
210 Ok(())
211 }
212
213 #[inline]
214 fn write_lit(&mut self, span: Span, s: &str) -> Result {
215 if !s.is_empty() {
216 self.srcmap(span.lo());
217 self.write(None, s)?;
218 self.srcmap(span.hi());
219 }
220
221 Ok(())
222 }
223
224 #[inline]
225 fn write_comment(&mut self, s: &str) -> Result {
226 self.write(None, s)?;
227 Ok(())
228 }
229
230 #[inline]
231 fn write_str_lit(&mut self, span: Span, s: &str) -> Result {
232 if !s.is_empty() {
233 self.srcmap(span.lo());
234 self.write(None, s)?;
235 self.srcmap(span.hi());
236 }
237
238 Ok(())
239 }
240
241 #[inline]
242 fn write_str(&mut self, s: &str) -> Result {
243 self.write(None, s)?;
244 Ok(())
245 }
246
247 #[inline]
248 fn write_symbol(&mut self, span: Span, s: &str) -> Result {
249 self.write(Some(span), s)?;
250 Ok(())
251 }
252
253 #[inline]
254 fn write_punct(&mut self, span: Option<Span>, s: &'static str) -> Result {
255 self.write(span, s)?;
256 Ok(())
257 }
258
259 #[inline]
260 fn care_about_srcmap(&self) -> bool {
261 self.srcmap.is_some()
262 }
263
264 #[inline]
265 fn add_srcmap(&mut self, pos: BytePos) -> Result {
266 if self.srcmap.is_some() {
267 if self.line_start {
268 self.pending_srcmap = Some(pos);
269 } else {
270 self.srcmap(pos);
271 }
272 }
273 Ok(())
274 }
275
276 #[inline]
277 fn commit_pending_semi(&mut self) -> Result {
278 Ok(())
279 }
280
281 #[inline(always)]
282 fn can_ignore_invalid_unicodes(&mut self) -> bool {
283 false
284 }
285}
286
287#[derive(Debug)]
288struct LineStart {
289 line_count: usize,
290 byte_pos: usize,
291}
292fn compute_line_starts(s: &str) -> LineStart {
293 let mut count = 0;
294 let mut line_start = 0;
295
296 let mut chars = s.as_bytes().iter().enumerate().peekable();
297
298 while let Some((pos, c)) = chars.next() {
299 match c {
300 b'\r' => {
301 count += 1;
302 if let Some(&(_, b'\n')) = chars.peek() {
303 let _ = chars.next();
304 line_start = pos + 2
305 } else {
306 line_start = pos + 1
307 }
308 }
309
310 b'\n' => {
311 count += 1;
312 line_start = pos + 1;
313 }
314
315 _ => {}
316 }
317 }
318
319 LineStart {
320 line_count: count,
321 byte_pos: line_start,
322 }
323}
324
325#[cfg(test)]
326mod test {
327 use std::sync::Arc;
328
329 use swc_common::SourceMap;
330
331 use super::JsWriter;
332 use crate::text_writer::WriteJs;
333
334 #[test]
335 fn changes_indent_str() {
336 let source_map = Arc::new(SourceMap::default());
337 let mut output = Vec::new();
338 let mut writer = JsWriter::new(source_map, "\n", &mut output, None);
339 writer.set_indent_str("\t");
340 writer.increase_indent().unwrap();
341 writer.write_indent_string().unwrap();
342 writer.increase_indent().unwrap();
343 writer.write_indent_string().unwrap();
344 assert_eq!(output, "\t\t\t".as_bytes());
345 }
346}