swc_css_codegen/writer/
basic.rs

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