1use crate::css_modules::CssModule;
4use crate::dependencies::{Dependency, DependencyOptions};
5use crate::error::{Error, ErrorLocation, PrinterError, PrinterErrorKind};
6use crate::rules::{Location, StyleContext};
7use crate::selector::SelectorList;
8use crate::targets::{Targets, TargetsWithSupportsScope};
9use crate::vendor_prefix::VendorPrefix;
10use cssparser::{serialize_identifier, serialize_name};
11#[cfg(feature = "sourcemap")]
12use parcel_sourcemap::{OriginalLocation, SourceMap};
13
14#[derive(Default)]
16pub struct PrinterOptions<'a> {
17 pub minify: bool,
19 #[cfg(feature = "sourcemap")]
21 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
22 pub source_map: Option<&'a mut SourceMap>,
23 pub project_root: Option<&'a str>,
25 pub targets: Targets,
27 pub analyze_dependencies: Option<DependencyOptions>,
35 pub pseudo_classes: Option<PseudoClasses<'a>>,
38}
39
40#[derive(Default, Debug)]
44pub struct PseudoClasses<'a> {
45 pub hover: Option<&'a str>,
47 pub active: Option<&'a str>,
49 pub focus: Option<&'a str>,
51 pub focus_visible: Option<&'a str>,
53 pub focus_within: Option<&'a str>,
55}
56
57pub struct Printer<'a, 'c, W> {
67 pub(crate) sources: Option<&'c Vec<String>>,
68 dest: &'a mut W,
69 #[cfg(feature = "sourcemap")]
70 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
71 pub(crate) source_map: Option<&'a mut SourceMap>,
72 #[cfg(feature = "sourcemap")]
73 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
74 pub(crate) source_maps: Vec<Option<SourceMap>>,
75 pub(crate) loc: Location,
76 indent: u8,
77 line: u32,
78 col: u32,
79 pub(crate) minify: bool,
80 pub(crate) targets: TargetsWithSupportsScope,
81 pub(crate) vendor_prefix: VendorPrefix,
84 pub(crate) in_calc: bool,
85 pub(crate) css_module: Option<CssModule<'a, 'c>>,
86 pub(crate) dependencies: Option<Vec<Dependency>>,
87 pub(crate) remove_imports: bool,
88 pub(crate) pseudo_classes: Option<PseudoClasses<'a>>,
89 context: Option<&'a StyleContext<'a, 'c>>,
90}
91
92impl<'a, 'c, W: std::fmt::Write + Sized> Printer<'a, 'c, W> {
93 pub fn new(dest: &'a mut W, options: PrinterOptions<'a>) -> Self {
95 Printer {
96 sources: None,
97 dest,
98 #[cfg(feature = "sourcemap")]
99 source_map: options.source_map,
100 #[cfg(feature = "sourcemap")]
101 source_maps: Vec::new(),
102 loc: Location {
103 source_index: 0,
104 line: 0,
105 column: 1,
106 },
107 indent: 0,
108 line: 0,
109 col: 0,
110 minify: options.minify,
111 targets: TargetsWithSupportsScope::new(options.targets),
112 vendor_prefix: VendorPrefix::empty(),
113 in_calc: false,
114 css_module: None,
115 dependencies: if options.analyze_dependencies.is_some() {
116 Some(Vec::new())
117 } else {
118 None
119 },
120 remove_imports: matches!(&options.analyze_dependencies, Some(d) if d.remove_imports),
121 pseudo_classes: options.pseudo_classes,
122 context: None,
123 }
124 }
125
126 pub fn filename(&self) -> &'c str {
128 if let Some(sources) = self.sources {
129 if let Some(f) = sources.get(self.loc.source_index as usize) {
130 f
131 } else {
132 "unknown.css"
133 }
134 } else {
135 "unknown.css"
136 }
137 }
138
139 pub fn write_str(&mut self, s: &str) -> Result<(), PrinterError> {
144 self.col += s.len() as u32;
145 self.dest.write_str(s)?;
146 Ok(())
147 }
148
149 pub fn write_str_with_newlines(&mut self, s: &str) -> Result<(), PrinterError> {
151 let mut last_line_start: usize = 0;
152
153 for (idx, n) in s.char_indices() {
154 if n == '\n' {
155 self.line += 1;
156 self.col = 0;
157
158 last_line_start = idx + 1;
160 }
161 }
162
163 self.col += (s.len() - last_line_start) as u32;
164 self.dest.write_str(s)?;
165 Ok(())
166 }
167
168 pub fn write_char(&mut self, c: char) -> Result<(), PrinterError> {
170 if c == '\n' {
171 self.line += 1;
172 self.col = 0;
173 } else {
174 self.col += 1;
175 }
176 self.dest.write_char(c)?;
177 Ok(())
178 }
179
180 pub fn whitespace(&mut self) -> Result<(), PrinterError> {
185 if self.minify {
186 return Ok(());
187 }
188
189 self.write_char(' ')
190 }
191
192 pub fn delim(&mut self, delim: char, ws_before: bool) -> Result<(), PrinterError> {
195 if ws_before {
196 self.whitespace()?;
197 }
198 self.write_char(delim)?;
199 self.whitespace()
200 }
201
202 pub fn newline(&mut self) -> Result<(), PrinterError> {
205 if self.minify {
206 return Ok(());
207 }
208
209 self.write_char('\n')?;
210 if self.indent > 0 {
211 self.write_str(&" ".repeat(self.indent as usize))?;
212 }
213
214 Ok(())
215 }
216
217 pub fn indent(&mut self) {
219 self.indent += 2;
220 }
221
222 pub fn dedent(&mut self) {
224 self.indent -= 2;
225 }
226
227 pub fn indent_by(&mut self, amt: u8) {
229 self.indent += amt;
230 }
231
232 pub fn dedent_by(&mut self, amt: u8) {
234 self.indent -= amt;
235 }
236
237 pub fn is_nested(&self) -> bool {
239 self.indent > 2
240 }
241
242 #[cfg(feature = "sourcemap")]
244 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
245 pub fn add_mapping(&mut self, loc: Location) {
246 self.loc = loc;
247
248 if let Some(map) = &mut self.source_map {
249 let mut original = OriginalLocation {
250 original_line: loc.line,
251 original_column: loc.column - 1,
252 source: loc.source_index,
253 name: None,
254 };
255
256 if let Some(Some(sm)) = self.source_maps.get_mut(loc.source_index as usize) {
258 let mut found_mapping = false;
259 if let Some(mapping) = sm.find_closest_mapping(loc.line, loc.column - 1) {
260 if let Some(orig) = mapping.original {
261 let sources_len = map.get_sources().len();
262 let source_index = map.add_source(sm.get_source(orig.source).unwrap());
263 let name = orig.name.map(|name| map.add_name(sm.get_name(name).unwrap()));
264 original.original_line = orig.original_line;
265 original.original_column = orig.original_column;
266 original.source = source_index;
267 original.name = name;
268
269 if map.get_sources().len() > sources_len {
270 let content = sm.get_source_content(orig.source).unwrap().to_owned();
271 let _ = map.set_source_content(source_index as usize, &content);
272 }
273
274 found_mapping = true;
275 }
276 }
277
278 if !found_mapping {
279 return;
280 }
281 }
282
283 map.add_mapping(self.line, self.col, Some(original))
284 }
285 }
286
287 pub fn write_ident(&mut self, ident: &str, handle_css_module: bool) -> Result<(), PrinterError> {
291 if handle_css_module {
292 if let Some(css_module) = &mut self.css_module {
293 let dest = &mut self.dest;
294 let mut first = true;
295 css_module.config.pattern.write(
296 &css_module.hashes[self.loc.source_index as usize],
297 &css_module.sources[self.loc.source_index as usize],
298 ident,
299 if let Some(content_hashes) = &css_module.content_hashes {
300 &content_hashes[self.loc.source_index as usize]
301 } else {
302 ""
303 },
304 |s| {
305 self.col += s.len() as u32;
306 if first {
307 first = false;
308 serialize_identifier(s, dest)
309 } else {
310 serialize_name(s, dest)
311 }
312 },
313 )?;
314
315 css_module.add_local(&ident, &ident, self.loc.source_index);
316 return Ok(());
317 }
318 }
319
320 serialize_identifier(ident, self)?;
321 Ok(())
322 }
323
324 pub(crate) fn write_dashed_ident(&mut self, ident: &str, is_declaration: bool) -> Result<(), PrinterError> {
325 self.write_str("--")?;
326
327 match &mut self.css_module {
328 Some(css_module) if css_module.config.dashed_idents => {
329 let dest = &mut self.dest;
330 css_module.config.pattern.write(
331 &css_module.hashes[self.loc.source_index as usize],
332 &css_module.sources[self.loc.source_index as usize],
333 &ident[2..],
334 if let Some(content_hashes) = &css_module.content_hashes {
335 &content_hashes[self.loc.source_index as usize]
336 } else {
337 ""
338 },
339 |s| {
340 self.col += s.len() as u32;
341 serialize_name(s, dest)
342 },
343 )?;
344
345 if is_declaration {
346 css_module.add_dashed(ident, self.loc.source_index);
347 }
348 }
349 _ => {
350 serialize_name(&ident[2..], self)?;
351 }
352 }
353
354 Ok(())
355 }
356
357 pub fn error(&self, kind: PrinterErrorKind, loc: crate::dependencies::Location) -> Error<PrinterErrorKind> {
359 Error {
360 kind,
361 loc: Some(ErrorLocation {
362 filename: self.filename().into(),
363 line: loc.line - 1,
364 column: loc.column,
365 }),
366 }
367 }
368
369 pub(crate) fn with_context<T, U, F: FnOnce(&mut Printer<'a, 'c, W>) -> Result<T, U>>(
370 &mut self,
371 selectors: &SelectorList,
372 f: F,
373 ) -> Result<T, U> {
374 let parent = std::mem::take(&mut self.context);
375 let ctx = StyleContext {
376 selectors: unsafe { std::mem::transmute(selectors) },
377 parent,
378 };
379
380 self.context = Some(unsafe { std::mem::transmute(&ctx) });
383 let res = f(self);
384 self.context = parent;
385 res
386 }
387
388 pub(crate) fn with_cleared_context<T, U, F: FnOnce(&mut Printer<'a, 'c, W>) -> Result<T, U>>(
389 &mut self,
390 f: F,
391 ) -> Result<T, U> {
392 let parent = std::mem::take(&mut self.context);
393 let res = f(self);
394 self.context = parent;
395 res
396 }
397
398 pub(crate) fn with_parent_context<T, U, F: FnOnce(&mut Printer<'a, 'c, W>) -> Result<T, U>>(
399 &mut self,
400 f: F,
401 ) -> Result<T, U> {
402 let parent = std::mem::take(&mut self.context);
403 if let Some(parent) = parent {
404 self.context = parent.parent;
405 }
406 let res = f(self);
407 self.context = parent;
408 res
409 }
410
411 pub(crate) fn context(&self) -> Option<&'a StyleContext<'a, 'c>> {
412 self.context.clone()
413 }
414}
415
416impl<'a, 'b, 'c, W: std::fmt::Write + Sized> std::fmt::Write for Printer<'a, 'c, W> {
417 fn write_str(&mut self, s: &str) -> std::fmt::Result {
418 self.col += s.len() as u32;
419 self.dest.write_str(s)
420 }
421}