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