swc_xml_codegen/writer/
basic.rs

1use std::fmt::{Result, Write};
2
3use rustc_hash::FxHashSet;
4use swc_common::{BytePos, LineCol, Span};
5
6use super::XmlWriter;
7
8#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
9pub enum IndentType {
10    Tab,
11    #[default]
12    Space,
13}
14
15#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
16pub enum LineFeed {
17    #[default]
18    LF,
19    CRLF,
20}
21
22pub struct BasicXmlWriterConfig {
23    pub indent_type: IndentType,
24    pub indent_width: i32,
25    pub linefeed: LineFeed,
26}
27
28impl Default for BasicXmlWriterConfig {
29    fn default() -> Self {
30        BasicXmlWriterConfig {
31            indent_type: IndentType::default(),
32            indent_width: 2,
33            linefeed: LineFeed::default(),
34        }
35    }
36}
37
38pub struct BasicXmlWriter<'a, W>
39where
40    W: Write,
41{
42    line_start: bool,
43    line: usize,
44    col: usize,
45
46    indent_type: &'a str,
47    indent_level: usize,
48    linefeed: &'a str,
49
50    srcmap: Option<&'a mut Vec<(BytePos, LineCol)>>,
51    srcmap_done: FxHashSet<(BytePos, u32, u32)>,
52    /// Used to avoid including whitespaces created by indention.
53    pending_srcmap: Option<BytePos>,
54
55    config: BasicXmlWriterConfig,
56
57    w: W,
58}
59
60impl<'a, W> BasicXmlWriter<'a, W>
61where
62    W: Write,
63{
64    pub fn new(
65        writer: W,
66        srcmap: Option<&'a mut Vec<(BytePos, LineCol)>>,
67        config: BasicXmlWriterConfig,
68    ) -> Self {
69        let indent_type = match config.indent_type {
70            IndentType::Tab => "\t",
71            IndentType::Space => " ",
72        };
73        let linefeed = match config.linefeed {
74            LineFeed::LF => "\n",
75            LineFeed::CRLF => "\r\n",
76        };
77
78        BasicXmlWriter {
79            line_start: true,
80            line: 0,
81            col: 0,
82
83            indent_type,
84            indent_level: 0,
85            linefeed,
86
87            config,
88            srcmap,
89
90            w: writer,
91            pending_srcmap: Default::default(),
92            srcmap_done: Default::default(),
93        }
94    }
95
96    fn write_indent_string(&mut self) -> Result {
97        for _ in 0..(self.config.indent_width * self.indent_level as i32) {
98            self.raw_write(self.indent_type)?;
99        }
100
101        Ok(())
102    }
103
104    fn raw_write(&mut self, data: &str) -> Result {
105        self.w.write_str(data)?;
106        self.col += data.chars().count();
107
108        Ok(())
109    }
110
111    fn write(&mut self, span: Option<Span>, data: &str) -> Result {
112        if !data.is_empty() {
113            if self.line_start {
114                self.write_indent_string()?;
115                self.line_start = false;
116
117                if let Some(pending) = self.pending_srcmap.take() {
118                    self.srcmap(pending);
119                }
120            }
121
122            if let Some(span) = span {
123                if !span.is_dummy() {
124                    self.srcmap(span.lo())
125                }
126            }
127
128            self.raw_write(data)?;
129
130            if let Some(span) = span {
131                if !span.is_dummy() {
132                    self.srcmap(span.hi())
133                }
134            }
135        }
136
137        Ok(())
138    }
139
140    fn srcmap(&mut self, byte_pos: BytePos) {
141        if byte_pos.is_dummy() {
142            return;
143        }
144
145        if let Some(ref mut srcmap) = self.srcmap {
146            if self
147                .srcmap_done
148                .insert((byte_pos, self.line as _, self.col as _))
149            {
150                let loc = LineCol {
151                    line: self.line as _,
152                    col: self.col as _,
153                };
154
155                srcmap.push((byte_pos, loc));
156            }
157        }
158    }
159}
160
161impl<W> XmlWriter for BasicXmlWriter<'_, W>
162where
163    W: Write,
164{
165    fn write_space(&mut self) -> Result {
166        self.write_raw(None, " ")
167    }
168
169    fn write_newline(&mut self) -> Result {
170        let pending = self.pending_srcmap.take();
171
172        if !self.line_start {
173            self.raw_write(self.linefeed)?;
174            self.line += 1;
175            self.col = 0;
176            self.line_start = true;
177
178            if let Some(pending) = pending {
179                self.srcmap(pending)
180            }
181        }
182
183        Ok(())
184    }
185
186    fn write_raw(&mut self, span: Option<Span>, text: &str) -> Result {
187        debug_assert!(
188            !text.contains('\n'),
189            "write_raw should not contains new lines, got '{text}'",
190        );
191
192        self.write(span, text)?;
193
194        Ok(())
195    }
196
197    fn write_multiline_raw(&mut self, span: Span, s: &str) -> Result {
198        if !s.is_empty() {
199            if !span.is_dummy() {
200                self.srcmap(span.lo())
201            }
202
203            self.write(None, s)?;
204
205            let line_start_of_s = compute_line_starts(s);
206
207            if line_start_of_s.len() > 1 {
208                self.line = self.line + line_start_of_s.len() - 1;
209
210                let last_line_byte_index = line_start_of_s.last().cloned().unwrap_or(0);
211
212                self.col = s[last_line_byte_index..].chars().count();
213            }
214
215            if !span.is_dummy() {
216                self.srcmap(span.hi())
217            }
218        }
219
220        Ok(())
221    }
222
223    fn increase_indent(&mut self) {
224        self.indent_level += 1;
225    }
226
227    fn decrease_indent(&mut self) {
228        debug_assert!(
229            (self.indent_level as i32) >= 0,
230            "indent should zero or greater than zero",
231        );
232
233        self.indent_level -= 1;
234    }
235}
236
237fn compute_line_starts(s: &str) -> Vec<usize> {
238    let mut res = Vec::new();
239    let mut line_start = 0;
240    let mut chars = s.char_indices().peekable();
241
242    while let Some((pos, c)) = chars.next() {
243        match c {
244            '\r' => {
245                if let Some(&(_, '\n')) = chars.peek() {
246                    let _ = chars.next();
247                }
248            }
249
250            '\n' => {
251                res.push(line_start);
252                line_start = pos + 1;
253            }
254
255            _ => {}
256        }
257    }
258
259    // Last line.
260    res.push(line_start);
261    res
262}