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 #[inline]
94 fn mapping_columns(&self) -> bool {
95 #[cfg(feature = "sourcemap")]
96 if self.source_map.is_some() {
97 return true;
98 }
99 false
100 }
101
102 #[inline]
103 fn string_columns(&self, text: &str) -> u32 {
104 if self.mapping_columns() {
105 text.encode_utf16().count() as u32
106 } else {
107 text.len() as u32
108 }
109 }
110
111 pub fn new(dest: &'a mut W, options: PrinterOptions<'a>) -> Self {
113 Printer {
114 sources: None,
115 dest,
116 #[cfg(feature = "sourcemap")]
117 source_map: options.source_map,
118 #[cfg(feature = "sourcemap")]
119 source_maps: Vec::new(),
120 loc: Location {
121 source_index: 0,
122 line: 0,
123 column: 1,
124 },
125 indent: 0,
126 line: 0,
127 col: 0,
128 minify: options.minify,
129 targets: TargetsWithSupportsScope::new(options.targets),
130 vendor_prefix: VendorPrefix::empty(),
131 in_calc: false,
132 css_module: None,
133 dependencies: if options.analyze_dependencies.is_some() {
134 Some(Vec::new())
135 } else {
136 None
137 },
138 remove_imports: matches!(&options.analyze_dependencies, Some(d) if d.remove_imports),
139 pseudo_classes: options.pseudo_classes,
140 context: None,
141 }
142 }
143
144 pub fn filename(&self) -> &'c str {
146 if let Some(sources) = self.sources {
147 if let Some(f) = sources.get(self.loc.source_index as usize) {
148 f
149 } else {
150 "unknown.css"
151 }
152 } else {
153 "unknown.css"
154 }
155 }
156
157 pub fn write_str(&mut self, s: &str) -> Result<(), PrinterError> {
162 self.col += self.string_columns(s);
163 self.dest.write_str(s)?;
164 Ok(())
165 }
166
167 pub fn write_str_with_newlines(&mut self, s: &str) -> Result<(), PrinterError> {
169 let mut last_line_start: usize = 0;
170
171 for (idx, n) in s.char_indices() {
172 if n == '\n' {
173 self.line += 1;
174 self.col = 0;
175
176 last_line_start = idx + 1;
178 }
179 }
180
181 self.col += self.string_columns(&s[last_line_start..]);
182 self.dest.write_str(s)?;
183 Ok(())
184 }
185
186 pub fn write_char(&mut self, c: char) -> Result<(), PrinterError> {
188 if c == '\n' {
189 self.line += 1;
190 self.col = 0;
191 } else {
192 self.col += if self.mapping_columns() {
193 c.len_utf16() as u32
194 } else {
195 1
196 };
197 }
198 self.dest.write_char(c)?;
199 Ok(())
200 }
201
202 pub fn whitespace(&mut self) -> Result<(), PrinterError> {
207 if self.minify {
208 return Ok(());
209 }
210
211 self.write_char(' ')
212 }
213
214 pub fn delim(&mut self, delim: char, ws_before: bool) -> Result<(), PrinterError> {
217 if ws_before {
218 self.whitespace()?;
219 }
220 self.write_char(delim)?;
221 self.whitespace()
222 }
223
224 pub fn newline(&mut self) -> Result<(), PrinterError> {
227 if self.minify {
228 return Ok(());
229 }
230
231 self.write_char('\n')?;
232 if self.indent > 0 {
233 self.write_str(&" ".repeat(self.indent as usize))?;
234 }
235
236 Ok(())
237 }
238
239 pub fn indent(&mut self) {
241 self.indent += 2;
242 }
243
244 pub fn dedent(&mut self) {
246 self.indent -= 2;
247 }
248
249 pub fn indent_by(&mut self, amt: u8) {
251 self.indent += amt;
252 }
253
254 pub fn dedent_by(&mut self, amt: u8) {
256 self.indent -= amt;
257 }
258
259 pub fn is_nested(&self) -> bool {
261 self.indent > 2
262 }
263
264 #[cfg(feature = "sourcemap")]
266 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
267 pub fn add_mapping(&mut self, loc: Location) {
268 self.loc = loc;
269
270 if let Some(map) = &mut self.source_map {
271 let mut original = OriginalLocation {
272 original_line: loc.line,
273 original_column: loc.column - 1,
274 source: loc.source_index,
275 name: None,
276 };
277
278 if let Some(Some(sm)) = self.source_maps.get_mut(loc.source_index as usize) {
280 let mut found_mapping = false;
281 if let Some(mapping) = sm.find_closest_mapping(loc.line, loc.column - 1) {
282 if let Some(orig) = mapping.original {
283 let sources_len = map.get_sources().len();
284 let source_index = map.add_source(sm.get_source(orig.source).unwrap());
285 let name = orig.name.map(|name| map.add_name(sm.get_name(name).unwrap()));
286 original.original_line = orig.original_line;
287 original.original_column = orig.original_column;
288 original.source = source_index;
289 original.name = name;
290
291 if map.get_sources().len() > sources_len {
292 let content = sm.get_source_content(orig.source).unwrap().to_owned();
293 let _ = map.set_source_content(source_index as usize, &content);
294 }
295
296 found_mapping = true;
297 }
298 }
299
300 if !found_mapping {
301 return;
302 }
303 }
304
305 map.add_mapping(self.line, self.col, Some(original))
306 }
307 }
308
309 pub fn write_ident(&mut self, ident: &str, handle_css_module: bool) -> Result<(), PrinterError> {
313 let mapping_columns = self.mapping_columns();
314 if handle_css_module {
315 if let Some(css_module) = &mut self.css_module {
316 let dest = &mut self.dest;
317 let mut first = true;
318 css_module.config.pattern.write(
319 &css_module.hashes[self.loc.source_index as usize],
320 &css_module.sources[self.loc.source_index as usize],
321 ident,
322 if let Some(content_hashes) = &css_module.content_hashes {
323 &content_hashes[self.loc.source_index as usize]
324 } else {
325 ""
326 },
327 |s| {
328 if mapping_columns {
329 let mut writer = MappingWriter {
330 dest: &mut **dest,
331 column: &mut self.col,
332 };
333 if first {
334 first = false;
335 return serialize_identifier(s, &mut writer);
336 }
337 return serialize_name(s, &mut writer);
338 }
339 self.col += s.len() as u32;
340 if first {
341 first = false;
342 serialize_identifier(s, dest)
343 } else {
344 serialize_name(s, dest)
345 }
346 },
347 )?;
348
349 css_module.add_local(&ident, &ident, self.loc.source_index);
350 return Ok(());
351 }
352 }
353
354 serialize_identifier(ident, self)?;
355 Ok(())
356 }
357
358 pub(crate) fn write_dashed_ident(&mut self, ident: &str, is_declaration: bool) -> Result<(), PrinterError> {
359 self.write_str("--")?;
360 let mapping_columns = self.mapping_columns();
361
362 match &mut self.css_module {
363 Some(css_module) if css_module.config.dashed_idents => {
364 let dest = &mut self.dest;
365 css_module.config.pattern.write(
366 &css_module.hashes[self.loc.source_index as usize],
367 &css_module.sources[self.loc.source_index as usize],
368 &ident[2..],
369 if let Some(content_hashes) = &css_module.content_hashes {
370 &content_hashes[self.loc.source_index as usize]
371 } else {
372 ""
373 },
374 |s| {
375 if mapping_columns {
376 return serialize_name(
377 s,
378 &mut MappingWriter {
379 dest: &mut **dest,
380 column: &mut self.col,
381 },
382 );
383 }
384 self.col += s.len() as u32;
385 serialize_name(s, dest)
386 },
387 )?;
388
389 if is_declaration {
390 css_module.add_dashed(ident, self.loc.source_index);
391 }
392 }
393 _ => {
394 serialize_name(&ident[2..], self)?;
395 }
396 }
397
398 Ok(())
399 }
400
401 pub fn error(&self, kind: PrinterErrorKind, loc: crate::dependencies::Location) -> Error<PrinterErrorKind> {
403 Error {
404 kind,
405 loc: Some(ErrorLocation {
406 filename: self.filename().into(),
407 line: loc.line - 1,
408 column: loc.column,
409 }),
410 }
411 }
412
413 pub(crate) fn with_context<T, U, F: FnOnce(&mut Printer<'a, 'c, W>) -> Result<T, U>>(
414 &mut self,
415 selectors: &SelectorList,
416 f: F,
417 ) -> Result<T, U> {
418 let parent = std::mem::take(&mut self.context);
419 let ctx = StyleContext {
420 selectors: unsafe { std::mem::transmute(selectors) },
421 parent,
422 };
423
424 self.context = Some(unsafe { std::mem::transmute(&ctx) });
427 let res = f(self);
428 self.context = parent;
429 res
430 }
431
432 pub(crate) fn with_cleared_context<T, U, F: FnOnce(&mut Printer<'a, 'c, W>) -> Result<T, U>>(
433 &mut self,
434 f: F,
435 ) -> Result<T, U> {
436 let parent = std::mem::take(&mut self.context);
437 let res = f(self);
438 self.context = parent;
439 res
440 }
441
442 pub(crate) fn with_parent_context<T, U, F: FnOnce(&mut Printer<'a, 'c, W>) -> Result<T, U>>(
443 &mut self,
444 f: F,
445 ) -> Result<T, U> {
446 let parent = std::mem::take(&mut self.context);
447 if let Some(parent) = parent {
448 self.context = parent.parent;
449 }
450 let res = f(self);
451 self.context = parent;
452 res
453 }
454
455 pub(crate) fn context(&self) -> Option<&'a StyleContext<'a, 'c>> {
456 self.context.clone()
457 }
458}
459
460impl<'a, 'b, 'c, W: std::fmt::Write + Sized> std::fmt::Write for Printer<'a, 'c, W> {
461 fn write_str(&mut self, s: &str) -> std::fmt::Result {
462 self.col += self.string_columns(s);
463 self.dest.write_str(s)
464 }
465}
466
467struct MappingWriter<'a, W> {
470 dest: &'a mut W,
471 column: &'a mut u32,
472}
473
474impl<W: std::fmt::Write> std::fmt::Write for MappingWriter<'_, W> {
475 fn write_str(&mut self, text: &str) -> std::fmt::Result {
476 *self.column += text.encode_utf16().count() as u32;
477 self.dest.write_str(text)
478 }
479}