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 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 res.push(line_start);
261 res
262}