1use crate::context::{DeclarationContext, PropertyHandlerContext};
7use crate::css_modules::{hash, CssModule, CssModuleExports, CssModuleReferences};
8use crate::declaration::{DeclarationBlock, DeclarationHandler};
9use crate::dependencies::Dependency;
10use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterError, PrinterErrorKind};
11use crate::parser::{DefaultAtRule, DefaultAtRuleParser, TopLevelRuleParser};
12use crate::printer::Printer;
13use crate::rules::{CssRule, CssRuleList, MinifyContext};
14use crate::targets::{should_compile, Targets, TargetsWithSupportsScope};
15use crate::traits::{AtRuleParser, ToCss};
16use crate::values::string::CowArcStr;
17#[cfg(feature = "visitor")]
18use crate::visitor::{Visit, VisitTypes, Visitor};
19use cssparser::{Parser, ParserInput, StyleSheetParser};
20#[cfg(feature = "sourcemap")]
21use parcel_sourcemap::SourceMap;
22use std::collections::{HashMap, HashSet};
23
24pub use crate::parser::{ParserFlags, ParserOptions};
25pub use crate::printer::PrinterOptions;
26pub use crate::printer::PseudoClasses;
27
28#[derive(Debug)]
63#[cfg_attr(
64 feature = "serde",
65 derive(serde::Serialize, serde::Deserialize),
66 serde(rename_all = "camelCase")
67)]
68#[cfg_attr(
69 feature = "jsonschema",
70 derive(schemars::JsonSchema),
71 schemars(rename = "StyleSheet", bound = "T: schemars::JsonSchema")
72)]
73pub struct StyleSheet<'i, 'o, T = DefaultAtRule> {
74 #[cfg_attr(feature = "serde", serde(borrow))]
76 pub rules: CssRuleList<'i, T>,
77 pub sources: Vec<String>,
80 pub(crate) source_map_urls: Vec<Option<String>>,
82 pub license_comments: Vec<CowArcStr<'i>>,
84 #[cfg_attr(feature = "serde", serde(skip))]
87 pub(crate) content_hashes: Option<Vec<String>>,
88 #[cfg_attr(feature = "serde", serde(skip))]
89 options: ParserOptions<'o, 'i>,
91}
92
93#[derive(Default)]
96pub struct MinifyOptions {
97 pub targets: Targets,
99 pub unused_symbols: HashSet<String>,
102}
103
104#[derive(Debug)]
107pub struct ToCssResult {
108 pub code: String,
110 pub exports: Option<CssModuleExports>,
113 pub references: Option<CssModuleReferences>,
116 pub dependencies: Option<Vec<Dependency>>,
119}
120
121impl<'i, 'o> StyleSheet<'i, 'o, DefaultAtRule> {
122 pub fn parse(code: &'i str, options: ParserOptions<'o, 'i>) -> Result<Self, Error<ParserError<'i>>> {
124 Self::parse_with(code, options, &mut DefaultAtRuleParser)
125 }
126}
127
128impl<'i, 'o, T> StyleSheet<'i, 'o, T>
129where
130 T: ToCss + Clone,
131{
132 pub fn new(
134 sources: Vec<String>,
135 rules: CssRuleList<'i, T>,
136 options: ParserOptions<'o, 'i>,
137 ) -> StyleSheet<'i, 'o, T> {
138 StyleSheet {
139 sources,
140 source_map_urls: Vec::new(),
141 license_comments: Vec::new(),
142 content_hashes: None,
143 rules,
144 options,
145 }
146 }
147
148 pub fn parse_with<P: AtRuleParser<'i, AtRule = T>>(
150 code: &'i str,
151 mut options: ParserOptions<'o, 'i>,
152 at_rule_parser: &mut P,
153 ) -> Result<Self, Error<ParserError<'i>>> {
154 let mut input = ParserInput::new(&code);
155 let mut parser = Parser::new(&mut input);
156 let mut license_comments = Vec::new();
157
158 let mut content_hashes = None;
159 if let Some(config) = &options.css_modules {
160 if config.pattern.has_content_hash() {
161 content_hashes = Some(vec![hash(
162 &code,
163 matches!(config.pattern.segments[0], crate::css_modules::Segment::ContentHash),
164 )]);
165 }
166 }
167
168 let mut state = parser.state();
169 while let Ok(token) = parser.next_including_whitespace_and_comments() {
170 match token {
171 cssparser::Token::WhiteSpace(..) => {}
172 cssparser::Token::Comment(comment) if comment.starts_with('!') => {
173 license_comments.push((*comment).into());
174 }
175 cssparser::Token::Comment(comment) if comment.contains("cssmodules-pure-no-check") => {
176 if let Some(css_modules) = &mut options.css_modules {
177 css_modules.pure = false;
178 }
179 }
180 _ => break,
181 }
182 state = parser.state();
183 }
184 parser.reset(&state);
185
186 let mut rules = CssRuleList(vec![]);
187 let mut rule_parser = TopLevelRuleParser::new(&mut options, at_rule_parser, &mut rules);
188 let mut rule_list_parser = StyleSheetParser::new(&mut parser, &mut rule_parser);
189
190 while let Some(rule) = rule_list_parser.next() {
191 match rule {
192 Ok(()) => {}
193 Err((e, _)) => {
194 let options = &mut rule_list_parser.parser.options;
195 if options.error_recovery {
196 options.warn(e);
197 continue;
198 }
199
200 return Err(Error::from(e, options.filename.clone()));
201 }
202 }
203 }
204
205 Ok(StyleSheet {
206 sources: vec![options.filename.clone()],
207 source_map_urls: vec![parser.current_source_map_url().map(|s| s.to_owned())],
208 content_hashes,
209 rules,
210 license_comments,
211 options,
212 })
213 }
214
215 pub fn source_map_url(&self, source_index: usize) -> Option<&String> {
217 self.source_map_urls.get(source_index)?.as_ref()
218 }
219
220 #[cfg(feature = "sourcemap")]
222 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
223 pub fn source_map(&self, source_index: usize) -> Option<SourceMap> {
224 SourceMap::from_data_url("/", self.source_map_url(source_index)?).ok()
225 }
226
227 pub fn minify(&mut self, options: MinifyOptions) -> Result<(), Error<MinifyErrorKind>> {
229 let context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);
230 let mut handler = DeclarationHandler::default();
231 let mut important_handler = DeclarationHandler::default();
232
233 let custom_media = if self.options.flags.contains(ParserFlags::CUSTOM_MEDIA)
236 && should_compile!(options.targets, CustomMediaQueries)
237 {
238 let mut custom_media = HashMap::new();
239 for rule in &self.rules.0 {
240 if let CssRule::CustomMedia(rule) = rule {
241 custom_media.insert(rule.name.0.clone(), rule.clone());
242 }
243 }
244 Some(custom_media)
245 } else {
246 None
247 };
248
249 let mut ctx = MinifyContext {
250 targets: TargetsWithSupportsScope::new(options.targets),
251 handler: &mut handler,
252 important_handler: &mut important_handler,
253 handler_context: context,
254 unused_symbols: &options.unused_symbols,
255 custom_media,
256 css_modules: self.options.css_modules.is_some(),
257 pure_css_modules: self.options.css_modules.as_ref().map(|c| c.pure).unwrap_or_default(),
258 };
259
260 self.rules.minify(&mut ctx, false).map_err(|e| Error {
261 kind: e.kind,
262 loc: Some(ErrorLocation::new(
263 e.loc,
264 self.sources[e.loc.source_index as usize].clone(),
265 )),
266 })?;
267
268 Ok(())
269 }
270
271 pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, Error<PrinterErrorKind>> {
273 let mut dest = String::with_capacity(1);
275 let project_root = options.project_root.clone();
276 let mut printer = Printer::new(&mut dest, options);
277
278 #[cfg(feature = "sourcemap")]
279 {
280 printer.sources = Some(&self.sources);
281 }
282
283 #[cfg(feature = "sourcemap")]
284 if printer.source_map.is_some() {
285 printer.source_maps = self.sources.iter().enumerate().map(|(i, _)| self.source_map(i)).collect();
286 }
287
288 for comment in &self.license_comments {
289 printer.write_str("/*")?;
290 printer.write_str_with_newlines(comment)?;
291 printer.write_str_with_newlines("*/\n")?;
292 }
293
294 if let Some(config) = &self.options.css_modules {
295 let mut references = HashMap::new();
296 printer.css_module = Some(CssModule::new(
297 config,
298 &self.sources,
299 project_root,
300 &mut references,
301 &self.content_hashes,
302 ));
303
304 self.rules.to_css(&mut printer)?;
305 printer.newline()?;
306
307 Ok(ToCssResult {
308 dependencies: printer.dependencies,
309 exports: Some(std::mem::take(
310 &mut printer.css_module.unwrap().exports_by_source_index[0],
311 )),
312 code: dest,
313 references: Some(references),
314 })
315 } else {
316 self.rules.to_css(&mut printer)?;
317 printer.newline()?;
318
319 Ok(ToCssResult {
320 dependencies: printer.dependencies,
321 code: dest,
322 exports: None,
323 references: None,
324 })
325 }
326 }
327}
328
329#[cfg(feature = "visitor")]
330#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
331impl<'i, 'o, T, V> Visit<'i, T, V> for StyleSheet<'i, 'o, T>
332where
333 T: Visit<'i, T, V>,
334 V: ?Sized + Visitor<'i, T>,
335{
336 const CHILD_TYPES: VisitTypes = VisitTypes::all();
337
338 fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
339 visitor.visit_stylesheet(self)
340 }
341
342 fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {
343 self.rules.visit(visitor)
344 }
345}
346
347#[cfg_attr(feature = "visitor", derive(Visit))]
373#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
374pub struct StyleAttribute<'i> {
375 pub declarations: DeclarationBlock<'i>,
377 #[cfg_attr(feature = "visitor", skip_visit)]
378 sources: Vec<String>,
379}
380
381impl<'i> StyleAttribute<'i> {
382 pub fn parse(
384 code: &'i str,
385 options: ParserOptions<'_, 'i>,
386 ) -> Result<StyleAttribute<'i>, Error<ParserError<'i>>> {
387 let mut input = ParserInput::new(&code);
388 let mut parser = Parser::new(&mut input);
389 Ok(StyleAttribute {
390 declarations: DeclarationBlock::parse(&mut parser, &options).map_err(|e| Error::from(e, "".into()))?,
391 sources: vec![options.filename],
392 })
393 }
394
395 pub fn minify(&mut self, options: MinifyOptions) {
397 let mut context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);
398 let mut handler = DeclarationHandler::default();
399 let mut important_handler = DeclarationHandler::default();
400 context.context = DeclarationContext::StyleAttribute;
401 self.declarations.minify(&mut handler, &mut important_handler, &mut context);
402 }
403
404 pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, PrinterError> {
406 #[cfg(feature = "sourcemap")]
407 assert!(
408 options.source_map.is_none(),
409 "Source maps are not supported for style attributes"
410 );
411
412 let mut dest = String::with_capacity(1);
414 let mut printer = Printer::new(&mut dest, options);
415 printer.sources = Some(&self.sources);
416
417 self.declarations.to_css(&mut printer)?;
418
419 Ok(ToCssResult {
420 dependencies: printer.dependencies,
421 code: dest,
422 exports: None,
423 references: None,
424 })
425 }
426}