swc_ecma_codegen/text_writer/
basic_impl.rs

1use 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
9///
10/// -----
11///
12/// Ported from `createTextWriter` of the typescript compiler.
13///
14/// https://github.com/Microsoft/TypeScript/blob/45eaf42006/src/compiler/utilities.ts#L2548
15pub 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    /// Used to avoid including whitespaces created by indention.
25    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    /// Sets the indentation string. Defaults to four spaces.
58    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}