1#![deny(missing_docs)]
38
39pub mod container;
40pub mod counter_style;
41pub mod custom_media;
42pub mod document;
43pub mod font_face;
44pub mod font_palette_values;
45pub mod import;
46pub mod keyframes;
47pub mod layer;
48pub mod media;
49pub mod namespace;
50pub mod nesting;
51pub mod page;
52pub mod property;
53pub mod scope;
54pub mod starting_style;
55pub mod style;
56pub mod supports;
57pub mod unknown;
58pub mod viewport;
59
60use self::font_palette_values::FontPaletteValuesRule;
61use self::layer::{LayerBlockRule, LayerStatementRule};
62use self::property::PropertyRule;
63use crate::context::PropertyHandlerContext;
64use crate::declaration::{DeclarationBlock, DeclarationHandler};
65use crate::dependencies::{Dependency, ImportDependency};
66use crate::error::{MinifyError, ParserError, PrinterError, PrinterErrorKind};
67use crate::parser::{parse_rule_list, parse_style_block, DefaultAtRule, DefaultAtRuleParser, TopLevelRuleParser};
68use crate::prefixes::Feature;
69use crate::printer::Printer;
70use crate::rules::keyframes::KeyframesName;
71use crate::selector::{is_compatible, is_equivalent, Component, Selector, SelectorList};
72use crate::stylesheet::ParserOptions;
73use crate::targets::Targets;
74use crate::traits::{AtRuleParser, ToCss};
75use crate::values::string::CowArcStr;
76use crate::vendor_prefix::VendorPrefix;
77#[cfg(feature = "visitor")]
78use crate::visitor::{Visit, VisitTypes, Visitor};
79use container::ContainerRule;
80use counter_style::CounterStyleRule;
81use cssparser::{parse_one_rule, ParseError, Parser, ParserInput};
82use custom_media::CustomMediaRule;
83use document::MozDocumentRule;
84use font_face::FontFaceRule;
85use import::ImportRule;
86use itertools::Itertools;
87use keyframes::KeyframesRule;
88use media::MediaRule;
89use namespace::NamespaceRule;
90use nesting::NestingRule;
91use page::PageRule;
92use scope::ScopeRule;
93use smallvec::{smallvec, SmallVec};
94use starting_style::StartingStyleRule;
95use std::collections::{HashMap, HashSet};
96use std::hash::{BuildHasherDefault, Hasher};
97use style::StyleRule;
98use supports::SupportsRule;
99use unknown::UnknownAtRule;
100use viewport::ViewportRule;
101
102#[derive(Clone)]
103pub(crate) struct StyleContext<'a, 'i> {
104 pub selectors: &'a SelectorList<'i>,
105 pub parent: Option<&'a StyleContext<'a, 'i>>,
106}
107
108#[derive(PartialEq, Eq, Debug, Clone, Copy)]
110#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(serde::Serialize))]
111#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
112#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
113#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
114pub struct Location {
115 pub source_index: u32,
117 pub line: u32,
119 pub column: u32,
122}
123
124#[derive(Debug, PartialEq, Clone)]
126#[cfg_attr(feature = "visitor", derive(Visit))]
127#[cfg_attr(feature = "visitor", visit(visit_rule, RULES))]
128#[cfg_attr(
129 feature = "serde",
130 derive(serde::Serialize),
131 serde(tag = "type", content = "value", rename_all = "kebab-case")
132)]
133#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema), schemars(rename = "Rule"))]
134#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
135pub enum CssRule<'i, R = DefaultAtRule> {
136 #[cfg_attr(feature = "serde", serde(borrow))]
138 Media(MediaRule<'i, R>),
139 Import(ImportRule<'i>),
141 Style(StyleRule<'i, R>),
143 Keyframes(KeyframesRule<'i>),
145 FontFace(FontFaceRule<'i>),
147 FontPaletteValues(FontPaletteValuesRule<'i>),
149 Page(PageRule<'i>),
151 Supports(SupportsRule<'i, R>),
153 CounterStyle(CounterStyleRule<'i>),
155 Namespace(NamespaceRule<'i>),
157 MozDocument(MozDocumentRule<'i, R>),
159 Nesting(NestingRule<'i, R>),
161 Viewport(ViewportRule<'i>),
163 CustomMedia(CustomMediaRule<'i>),
165 LayerStatement(LayerStatementRule<'i>),
167 LayerBlock(LayerBlockRule<'i, R>),
169 Property(PropertyRule<'i>),
171 Container(ContainerRule<'i, R>),
173 Scope(ScopeRule<'i, R>),
175 StartingStyle(StartingStyleRule<'i, R>),
177 Ignored,
179 Unknown(UnknownAtRule<'i>),
181 Custom(R),
183}
184
185#[cfg(feature = "serde")]
187#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
188impl<'i, 'de: 'i, R: serde::Deserialize<'de>> serde::Deserialize<'de> for CssRule<'i, R> {
189 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
190 where
191 D: serde::Deserializer<'de>,
192 {
193 #[derive(serde::Deserialize)]
194 #[serde(field_identifier, rename_all = "snake_case")]
195 enum Field {
196 Type,
197 Value,
198 }
199
200 struct PartialRule<'de> {
201 rule_type: CowArcStr<'de>,
202 content: serde::__private::de::Content<'de>,
203 }
204
205 struct CssRuleVisitor;
206
207 impl<'de> serde::de::Visitor<'de> for CssRuleVisitor {
208 type Value = PartialRule<'de>;
209
210 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
211 formatter.write_str("a CssRule")
212 }
213
214 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
215 where
216 A: serde::de::MapAccess<'de>,
217 {
218 let mut rule_type: Option<CowArcStr<'de>> = None;
219 let mut value: Option<serde::__private::de::Content> = None;
220 while let Some(key) = map.next_key()? {
221 match key {
222 Field::Type => {
223 rule_type = Some(map.next_value()?);
224 }
225 Field::Value => {
226 value = Some(map.next_value()?);
227 }
228 }
229 }
230
231 let rule_type = rule_type.ok_or_else(|| serde::de::Error::missing_field("type"))?;
232 let content = value.ok_or_else(|| serde::de::Error::missing_field("value"))?;
233 Ok(PartialRule { rule_type, content })
234 }
235 }
236
237 let partial = deserializer.deserialize_map(CssRuleVisitor)?;
238 let deserializer = serde::__private::de::ContentDeserializer::new(partial.content);
239
240 match partial.rule_type.as_ref() {
241 "media" => {
242 let rule = MediaRule::deserialize(deserializer)?;
243 Ok(CssRule::Media(rule))
244 }
245 "import" => {
246 let rule = ImportRule::deserialize(deserializer)?;
247 Ok(CssRule::Import(rule))
248 }
249 "style" => {
250 let rule = StyleRule::deserialize(deserializer)?;
251 Ok(CssRule::Style(rule))
252 }
253 "keyframes" => {
254 let rule = KeyframesRule::deserialize(deserializer)?;
255 Ok(CssRule::Keyframes(rule))
256 }
257 "font-face" => {
258 let rule = FontFaceRule::deserialize(deserializer)?;
259 Ok(CssRule::FontFace(rule))
260 }
261 "font-palette-values" => {
262 let rule = FontPaletteValuesRule::deserialize(deserializer)?;
263 Ok(CssRule::FontPaletteValues(rule))
264 }
265 "page" => {
266 let rule = PageRule::deserialize(deserializer)?;
267 Ok(CssRule::Page(rule))
268 }
269 "supports" => {
270 let rule = SupportsRule::deserialize(deserializer)?;
271 Ok(CssRule::Supports(rule))
272 }
273 "counter-style" => {
274 let rule = CounterStyleRule::deserialize(deserializer)?;
275 Ok(CssRule::CounterStyle(rule))
276 }
277 "namespace" => {
278 let rule = NamespaceRule::deserialize(deserializer)?;
279 Ok(CssRule::Namespace(rule))
280 }
281 "moz-document" => {
282 let rule = MozDocumentRule::deserialize(deserializer)?;
283 Ok(CssRule::MozDocument(rule))
284 }
285 "nesting" => {
286 let rule = NestingRule::deserialize(deserializer)?;
287 Ok(CssRule::Nesting(rule))
288 }
289 "viewport" => {
290 let rule = ViewportRule::deserialize(deserializer)?;
291 Ok(CssRule::Viewport(rule))
292 }
293 "custom-media" => {
294 let rule = CustomMediaRule::deserialize(deserializer)?;
295 Ok(CssRule::CustomMedia(rule))
296 }
297 "layer-statement" => {
298 let rule = LayerStatementRule::deserialize(deserializer)?;
299 Ok(CssRule::LayerStatement(rule))
300 }
301 "layer-block" => {
302 let rule = LayerBlockRule::deserialize(deserializer)?;
303 Ok(CssRule::LayerBlock(rule))
304 }
305 "property" => {
306 let rule = PropertyRule::deserialize(deserializer)?;
307 Ok(CssRule::Property(rule))
308 }
309 "container" => {
310 let rule = ContainerRule::deserialize(deserializer)?;
311 Ok(CssRule::Container(rule))
312 }
313 "scope" => {
314 let rule = ScopeRule::deserialize(deserializer)?;
315 Ok(CssRule::Scope(rule))
316 }
317 "starting-style" => {
318 let rule = StartingStyleRule::deserialize(deserializer)?;
319 Ok(CssRule::StartingStyle(rule))
320 }
321 "ignored" => Ok(CssRule::Ignored),
322 "unknown" => {
323 let rule = UnknownAtRule::deserialize(deserializer)?;
324 Ok(CssRule::Unknown(rule))
325 }
326 "custom" => {
327 let rule = R::deserialize(deserializer)?;
328 Ok(CssRule::Custom(rule))
329 }
330 t => Err(serde::de::Error::unknown_variant(t, &[])),
331 }
332 }
333}
334
335impl<'a, 'i, T: ToCss> ToCss for CssRule<'i, T> {
336 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
337 where
338 W: std::fmt::Write,
339 {
340 match self {
341 CssRule::Media(media) => media.to_css(dest),
342 CssRule::Import(import) => import.to_css(dest),
343 CssRule::Style(style) => style.to_css(dest),
344 CssRule::Keyframes(keyframes) => keyframes.to_css(dest),
345 CssRule::FontFace(font_face) => font_face.to_css(dest),
346 CssRule::FontPaletteValues(f) => f.to_css(dest),
347 CssRule::Page(font_face) => font_face.to_css(dest),
348 CssRule::Supports(supports) => supports.to_css(dest),
349 CssRule::CounterStyle(counter_style) => counter_style.to_css(dest),
350 CssRule::Namespace(namespace) => namespace.to_css(dest),
351 CssRule::MozDocument(document) => document.to_css(dest),
352 CssRule::Nesting(nesting) => nesting.to_css(dest),
353 CssRule::Viewport(viewport) => viewport.to_css(dest),
354 CssRule::CustomMedia(custom_media) => custom_media.to_css(dest),
355 CssRule::LayerStatement(layer) => layer.to_css(dest),
356 CssRule::LayerBlock(layer) => layer.to_css(dest),
357 CssRule::Property(property) => property.to_css(dest),
358 CssRule::StartingStyle(rule) => rule.to_css(dest),
359 CssRule::Container(container) => container.to_css(dest),
360 CssRule::Scope(scope) => scope.to_css(dest),
361 CssRule::Unknown(unknown) => unknown.to_css(dest),
362 CssRule::Custom(rule) => rule.to_css(dest).map_err(|_| PrinterError {
363 kind: PrinterErrorKind::FmtError,
364 loc: None,
365 }),
366 CssRule::Ignored => Ok(()),
367 }
368 }
369}
370
371impl<'i> CssRule<'i, DefaultAtRule> {
372 pub fn parse<'t>(
374 input: &mut Parser<'i, 't>,
375 options: &ParserOptions<'_, 'i>,
376 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
377 Self::parse_with(input, options, &mut DefaultAtRuleParser)
378 }
379
380 pub fn parse_string(
382 input: &'i str,
383 options: ParserOptions<'_, 'i>,
384 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
385 Self::parse_string_with(input, options, &mut DefaultAtRuleParser)
386 }
387}
388
389impl<'i, T> CssRule<'i, T> {
390 pub fn parse_with<'t, P: AtRuleParser<'i, AtRule = T>>(
392 input: &mut Parser<'i, 't>,
393 options: &ParserOptions<'_, 'i>,
394 at_rule_parser: &mut P,
395 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
396 let mut rules = CssRuleList(Vec::new());
397 parse_one_rule(input, &mut TopLevelRuleParser::new(options, at_rule_parser, &mut rules))?;
398 Ok(rules.0.pop().unwrap())
399 }
400
401 pub fn parse_string_with<P: AtRuleParser<'i, AtRule = T>>(
403 input: &'i str,
404 options: ParserOptions<'_, 'i>,
405 at_rule_parser: &mut P,
406 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
407 let mut input = ParserInput::new(input);
408 let mut parser = Parser::new(&mut input);
409 Self::parse_with(&mut parser, &options, at_rule_parser)
410 }
411}
412
413#[derive(Debug, PartialEq, Clone, Default)]
415#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
416#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
417#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
418pub struct CssRuleList<'i, R = DefaultAtRule>(
419 #[cfg_attr(feature = "serde", serde(borrow))] pub Vec<CssRule<'i, R>>,
420);
421
422impl<'i> CssRuleList<'i, DefaultAtRule> {
423 pub fn parse<'t>(
425 input: &mut Parser<'i, 't>,
426 options: &ParserOptions<'_, 'i>,
427 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
428 Self::parse_with(input, options, &mut DefaultAtRuleParser)
429 }
430
431 pub fn parse_style_block<'t>(
434 input: &mut Parser<'i, 't>,
435 options: &ParserOptions<'_, 'i>,
436 is_nested: bool,
437 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
438 Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser, is_nested)
439 }
440}
441
442impl<'i, T> CssRuleList<'i, T> {
443 pub fn parse_with<'t, P: AtRuleParser<'i, AtRule = T>>(
445 input: &mut Parser<'i, 't>,
446 options: &ParserOptions<'_, 'i>,
447 at_rule_parser: &mut P,
448 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
449 parse_rule_list(input, options, at_rule_parser)
450 }
451
452 pub fn parse_style_block_with<'t, P: AtRuleParser<'i, AtRule = T>>(
455 input: &mut Parser<'i, 't>,
456 options: &ParserOptions<'_, 'i>,
457 at_rule_parser: &mut P,
458 is_nested: bool,
459 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
460 parse_style_block(input, options, at_rule_parser, is_nested)
461 }
462}
463
464#[cfg(feature = "visitor")]
466#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
467impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for CssRuleList<'i, T> {
468 const CHILD_TYPES: VisitTypes = VisitTypes::all();
469
470 fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
471 if visitor.visit_types().contains(VisitTypes::RULES) {
472 visitor.visit_rule_list(self)
473 } else {
474 self.0.visit(visitor)
475 }
476 }
477
478 fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {
479 self.0.visit(visitor)
480 }
481}
482
483pub(crate) struct MinifyContext<'a, 'i> {
484 pub targets: &'a Targets,
485 pub handler: &'a mut DeclarationHandler<'i>,
486 pub important_handler: &'a mut DeclarationHandler<'i>,
487 pub handler_context: PropertyHandlerContext<'i, 'a>,
488 pub unused_symbols: &'a HashSet<String>,
489 pub custom_media: Option<HashMap<CowArcStr<'i>, CustomMediaRule<'i>>>,
490 pub css_modules: bool,
491 pub pure_css_modules: bool,
492}
493
494impl<'i, T: Clone> CssRuleList<'i, T> {
495 pub(crate) fn minify(
496 &mut self,
497 context: &mut MinifyContext<'_, 'i>,
498 parent_is_unused: bool,
499 ) -> Result<(), MinifyError> {
500 let mut keyframe_rules = HashMap::new();
501 let mut layer_rules = HashMap::new();
502 let mut property_rules = HashMap::new();
503 let mut style_rules =
504 HashMap::with_capacity_and_hasher(self.0.len(), BuildHasherDefault::<PrecomputedHasher>::default());
505 let mut rules = Vec::new();
506 for mut rule in self.0.drain(..) {
507 match &mut rule {
508 CssRule::Keyframes(keyframes) => {
509 if context.unused_symbols.contains(match &keyframes.name {
510 KeyframesName::Ident(ident) => ident.0.as_ref(),
511 KeyframesName::Custom(string) => string.as_ref(),
512 }) {
513 continue;
514 }
515 keyframes.minify(context);
516
517 macro_rules! set_prefix {
518 ($keyframes: ident) => {
519 $keyframes.vendor_prefix = context.targets.prefixes($keyframes.vendor_prefix, Feature::AtKeyframes);
520 };
521 }
522
523 if let Some(existing_idx) = keyframe_rules.get(&keyframes.name) {
525 if let Some(CssRule::Keyframes(existing)) = &mut rules.get_mut(*existing_idx) {
526 if existing.vendor_prefix == keyframes.vendor_prefix {
528 *existing = keyframes.clone();
529 continue;
530 }
531 if existing.keyframes == keyframes.keyframes {
533 existing.vendor_prefix |= keyframes.vendor_prefix;
534 set_prefix!(existing);
535 continue;
536 }
537 }
538 }
539
540 set_prefix!(keyframes);
541 keyframe_rules.insert(keyframes.name.clone(), rules.len());
542
543 let fallbacks = keyframes.get_fallbacks(context.targets);
544 rules.push(rule);
545 rules.extend(fallbacks);
546 continue;
547 }
548 CssRule::CustomMedia(_) => {
549 if context.custom_media.is_some() {
550 continue;
551 }
552 }
553 CssRule::Media(media) => {
554 if let Some(CssRule::Media(last_rule)) = rules.last_mut() {
555 if last_rule.query == media.query {
556 last_rule.rules.0.extend(media.rules.0.drain(..));
557 last_rule.minify(context, parent_is_unused)?;
558 continue;
559 }
560 }
561
562 if media.minify(context, parent_is_unused)? {
563 continue;
564 }
565 }
566 CssRule::Supports(supports) => {
567 if let Some(CssRule::Supports(last_rule)) = rules.last_mut() {
568 if last_rule.condition == supports.condition {
569 last_rule.rules.0.extend(supports.rules.0.drain(..));
570 last_rule.minify(context, parent_is_unused)?;
571 continue;
572 }
573 }
574
575 supports.minify(context, parent_is_unused)?;
576 if supports.rules.0.is_empty() {
577 continue;
578 }
579 }
580 CssRule::Container(container) => {
581 if let Some(CssRule::Container(last_rule)) = rules.last_mut() {
582 if last_rule.name == container.name && last_rule.condition == container.condition {
583 last_rule.rules.0.extend(container.rules.0.drain(..));
584 last_rule.minify(context, parent_is_unused)?;
585 continue;
586 }
587 }
588
589 if container.minify(context, parent_is_unused)? {
590 continue;
591 }
592 }
593 CssRule::LayerBlock(layer) => {
594 if let Some(name) = &layer.name {
597 if let Some(idx) = layer_rules.get(name) {
598 if let Some(CssRule::LayerBlock(last_rule)) = rules.get_mut(*idx) {
599 last_rule.rules.0.extend(layer.rules.0.drain(..));
600 continue;
601 }
602 }
603
604 layer_rules.insert(name.clone(), rules.len());
605 }
606 }
607 CssRule::LayerStatement(layer) => {
608 for name in &layer.names {
611 if !layer_rules.contains_key(name) {
612 layer_rules.insert(name.clone(), rules.len());
613 rules.push(CssRule::LayerBlock(LayerBlockRule {
614 name: Some(name.clone()),
615 rules: CssRuleList(vec![]),
616 loc: layer.loc.clone(),
617 }));
618 }
619 }
620 continue;
621 }
622 CssRule::MozDocument(document) => document.minify(context)?,
623 CssRule::Style(style) => {
624 if parent_is_unused || style.minify(context, parent_is_unused)? {
625 continue;
626 }
627
628 let incompatible = if style.selectors.0.len() > 1
631 && context.targets.should_compile_selectors()
632 && !style.is_compatible(*context.targets)
633 {
634 if context.targets.is_compatible(crate::compat::Feature::IsSelector)
639 && !style.selectors.0.iter().any(|selector| selector.has_pseudo_element())
640 && style.selectors.0.iter().map(|selector| selector.specificity()).all_equal()
641 {
642 style.selectors =
643 SelectorList::new(smallvec![
644 Component::Is(style.selectors.0.clone().into_boxed_slice()).into()
645 ]);
646 smallvec![]
647 } else {
648 let (compatible, incompatible) = style
651 .selectors
652 .0
653 .iter()
654 .cloned()
655 .partition::<SmallVec<[Selector; 1]>, _>(|selector| {
656 let list = SelectorList::new(smallvec![selector.clone()]);
657 is_compatible(&list.0, *context.targets)
658 });
659 style.selectors = SelectorList::new(compatible);
660 incompatible
661 }
662 } else {
663 smallvec![]
664 };
665
666 style.update_prefix(context);
667
668 let mut merged = false;
670 if let Some(CssRule::Style(last_style_rule)) = rules.last_mut() {
671 if merge_style_rules(style, last_style_rule, context) {
672 while rules.len() >= 2 {
676 let len = rules.len();
677 let (a, b) = rules.split_at_mut(len - 1);
678 if let (CssRule::Style(last), CssRule::Style(prev)) = (&mut b[0], &mut a[len - 2]) {
679 if merge_style_rules(last, prev, context) {
680 rules.pop();
682 continue;
683 }
684 }
685 break;
687 }
688 merged = true;
689 }
690 }
691
692 let supports = context.handler_context.get_supports_rules(&style);
694 let logical = context.handler_context.get_additional_rules(&style);
695
696 let incompatible_rules = incompatible
697 .into_iter()
698 .map(|selector| {
699 let list = SelectorList::new(smallvec![selector]);
701 let mut clone = style.clone();
702 clone.selectors = list;
703 clone.update_prefix(context);
704
705 let supports = context.handler_context.get_supports_rules(&clone);
707 let logical = context.handler_context.get_additional_rules(&clone);
708 (clone, logical, supports)
709 })
710 .collect::<Vec<_>>();
711
712 context.handler_context.reset();
713
714 let nested_rule = if !style.rules.0.is_empty()
718 && !style.selectors.0.is_empty()
720 && (!logical.is_empty() || !supports.is_empty() || !incompatible_rules.is_empty())
721 {
722 let mut rules = CssRuleList(vec![]);
723 std::mem::swap(&mut style.rules, &mut rules);
724 Some(StyleRule {
725 selectors: style.selectors.clone(),
726 declarations: DeclarationBlock::default(),
727 rules,
728 vendor_prefix: style.vendor_prefix,
729 loc: style.loc,
730 })
731 } else {
732 None
733 };
734
735 if !merged && !style.is_empty() {
736 let source_index = style.loc.source_index;
737 let has_no_rules = style.rules.0.is_empty();
738 let idx = rules.len();
739 rules.push(rule);
740
741 if has_no_rules {
745 let key = StyleRuleKey::new(unsafe { &*(&rules as *const _) }, idx);
747 if idx > 0 {
748 if let Some(i) = style_rules.remove(&key) {
749 if let CssRule::Style(other) = &rules[i] {
750 if !context.css_modules || source_index == other.loc.source_index {
752 rules[i] = CssRule::Ignored;
754 }
755 }
756 }
757 }
758
759 style_rules.insert(key, idx);
760 }
761 }
762
763 if !logical.is_empty() {
764 let mut logical = CssRuleList(logical);
765 logical.minify(context, parent_is_unused)?;
766 rules.extend(logical.0)
767 }
768
769 rules.extend(supports);
770 for (rule, logical, supports) in incompatible_rules {
771 if !rule.is_empty() {
772 rules.push(CssRule::Style(rule));
773 }
774 if !logical.is_empty() {
775 let mut logical = CssRuleList(logical);
776 logical.minify(context, parent_is_unused)?;
777 rules.extend(logical.0)
778 }
779 rules.extend(supports);
780 }
781
782 if let Some(nested_rule) = nested_rule {
783 rules.push(CssRule::Style(nested_rule));
784 }
785
786 continue;
787 }
788 CssRule::CounterStyle(counter_style) => {
789 if context.unused_symbols.contains(counter_style.name.0.as_ref()) {
790 continue;
791 }
792 }
793 CssRule::Scope(scope) => scope.minify(context)?,
794 CssRule::Nesting(nesting) => {
795 if nesting.minify(context, parent_is_unused)? {
796 continue;
797 }
798 }
799 CssRule::StartingStyle(rule) => {
800 if rule.minify(context, parent_is_unused)? {
801 continue;
802 }
803 }
804 CssRule::FontPaletteValues(f) => {
805 if context.unused_symbols.contains(f.name.0.as_ref()) {
806 continue;
807 }
808
809 f.minify(context, parent_is_unused);
810
811 let fallbacks = f.get_fallbacks(*context.targets);
812 rules.push(rule);
813 rules.extend(fallbacks);
814 continue;
815 }
816 CssRule::Property(property) => {
817 if context.unused_symbols.contains(property.name.0.as_ref()) {
818 continue;
819 }
820
821 if let Some(index) = property_rules.get_mut(&property.name) {
822 rules[*index] = rule;
823 continue;
824 } else {
825 property_rules.insert(property.name.clone(), rules.len());
826 }
827 }
828 _ => {}
829 }
830
831 rules.push(rule)
832 }
833
834 if !layer_rules.is_empty() {
837 let mut declared_layers = HashSet::new();
838 let mut layer_statement = None;
839 for index in 0..rules.len() {
840 match &mut rules[index] {
841 CssRule::LayerBlock(layer) => {
842 if layer.minify(context, parent_is_unused)? {
843 if let Some(name) = &layer.name {
844 if declared_layers.contains(name) {
845 rules[index] = CssRule::Ignored;
847 continue;
848 }
849
850 let name = name.clone();
851 declared_layers.insert(name.clone());
852
853 if let Some(layer_index) = layer_statement {
854 if let CssRule::LayerStatement(layer) = &mut rules[layer_index] {
855 layer.names.push(name);
857 rules[index] = CssRule::Ignored;
858 }
859 } else {
860 rules[index] = CssRule::LayerStatement(LayerStatementRule {
862 names: vec![name],
863 loc: layer.loc,
864 });
865 layer_statement = Some(index);
866 }
867 } else {
868 rules[index] = CssRule::Ignored;
870 }
871 } else {
872 layer_statement = None;
874 }
875 }
876 CssRule::Import(import) => {
877 if let Some(layer) = &import.layer {
878 layer_statement = None;
880 if let Some(name) = layer {
881 declared_layers.insert(name.clone());
882 }
883 }
884 }
885 _ => {}
886 }
887 }
888 }
889
890 self.0 = rules;
891 Ok(())
892 }
893}
894
895fn merge_style_rules<'i, T>(
896 style: &mut StyleRule<'i, T>,
897 last_style_rule: &mut StyleRule<'i, T>,
898 context: &mut MinifyContext<'_, 'i>,
899) -> bool {
900 if style.selectors == last_style_rule.selectors
902 && style.is_compatible(*context.targets)
903 && last_style_rule.is_compatible(*context.targets)
904 && style.rules.0.is_empty()
905 && last_style_rule.rules.0.is_empty()
906 && (!context.css_modules || style.loc.source_index == last_style_rule.loc.source_index)
907 {
908 last_style_rule
909 .declarations
910 .declarations
911 .extend(style.declarations.declarations.drain(..));
912 last_style_rule
913 .declarations
914 .important_declarations
915 .extend(style.declarations.important_declarations.drain(..));
916 last_style_rule
917 .declarations
918 .minify(context.handler, context.important_handler, &mut context.handler_context);
919 return true;
920 } else if style.declarations == last_style_rule.declarations
921 && style.rules.0.is_empty()
922 && last_style_rule.rules.0.is_empty()
923 {
924 if !style.vendor_prefix.is_empty()
927 && !last_style_rule.vendor_prefix.is_empty()
928 && is_equivalent(&style.selectors.0, &last_style_rule.selectors.0)
929 {
930 if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.should_compile_selectors() {
933 last_style_rule.vendor_prefix = style.vendor_prefix;
934 } else {
935 last_style_rule.vendor_prefix |= style.vendor_prefix;
936 }
937 return true;
938 }
939
940 if style.is_compatible(*context.targets) && last_style_rule.is_compatible(*context.targets) {
942 last_style_rule.selectors.0.extend(style.selectors.0.drain(..));
943 if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.should_compile_selectors() {
944 last_style_rule.vendor_prefix = style.vendor_prefix;
945 } else {
946 last_style_rule.vendor_prefix |= style.vendor_prefix;
947 }
948 return true;
949 }
950 }
951 false
952}
953
954impl<'a, 'i, T: ToCss> ToCss for CssRuleList<'i, T> {
955 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
956 where
957 W: std::fmt::Write,
958 {
959 let mut first = true;
960 let mut last_without_block = false;
961
962 for rule in &self.0 {
963 if let CssRule::Ignored = &rule {
964 continue;
965 }
966
967 if let CssRule::Import(rule) = &rule {
969 if dest.remove_imports {
970 let dep = if dest.dependencies.is_some() {
971 Some(Dependency::Import(ImportDependency::new(&rule, dest.filename())))
972 } else {
973 None
974 };
975
976 if let Some(dependencies) = &mut dest.dependencies {
977 dependencies.push(dep.unwrap());
978 continue;
979 }
980 }
981 }
982
983 if first {
984 first = false;
985 } else {
986 if !dest.minify
987 && !(last_without_block
988 && matches!(
989 rule,
990 CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)
991 ))
992 {
993 dest.write_char('\n')?;
994 }
995 dest.newline()?;
996 }
997 rule.to_css(dest)?;
998 last_without_block = matches!(
999 rule,
1000 CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)
1001 );
1002 }
1003
1004 Ok(())
1005 }
1006}
1007
1008impl<'i, T> std::ops::Index<usize> for CssRuleList<'i, T> {
1009 type Output = CssRule<'i, T>;
1010
1011 fn index(&self, index: usize) -> &Self::Output {
1012 &self.0[index]
1013 }
1014}
1015
1016impl<'i, T> std::ops::IndexMut<usize> for CssRuleList<'i, T> {
1017 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
1018 &mut self.0[index]
1019 }
1020}
1021
1022#[derive(Default)]
1024struct PrecomputedHasher {
1025 hash: Option<u64>,
1026}
1027
1028impl Hasher for PrecomputedHasher {
1029 #[inline]
1030 fn write(&mut self, _: &[u8]) {
1031 unreachable!()
1032 }
1033
1034 #[inline]
1035 fn write_u64(&mut self, i: u64) {
1036 debug_assert!(self.hash.is_none());
1037 self.hash = Some(i);
1038 }
1039
1040 #[inline]
1041 fn finish(&self) -> u64 {
1042 self.hash.unwrap()
1043 }
1044}
1045
1046#[derive(Clone)]
1050pub(crate) struct StyleRuleKey<'a, 'i, R> {
1051 list: &'a Vec<CssRule<'i, R>>,
1052 index: usize,
1053 hash: u64,
1054}
1055
1056impl<'a, 'i, R> StyleRuleKey<'a, 'i, R> {
1057 fn new(list: &'a Vec<CssRule<'i, R>>, index: usize) -> Self {
1058 let rule = match &list[index] {
1059 CssRule::Style(style) => style,
1060 _ => unreachable!(),
1061 };
1062
1063 Self {
1064 list,
1065 index,
1066 hash: rule.hash_key(),
1067 }
1068 }
1069}
1070
1071impl<'a, 'i, R> PartialEq for StyleRuleKey<'a, 'i, R> {
1072 fn eq(&self, other: &Self) -> bool {
1073 let rule = match self.list.get(self.index) {
1074 Some(CssRule::Style(style)) => style,
1075 _ => return false,
1076 };
1077
1078 let other_rule = match other.list.get(other.index) {
1079 Some(CssRule::Style(style)) => style,
1080 _ => return false,
1081 };
1082
1083 rule.is_duplicate(other_rule)
1084 }
1085}
1086
1087impl<'a, 'i, R> Eq for StyleRuleKey<'a, 'i, R> {}
1088
1089impl<'a, 'i, R> std::hash::Hash for StyleRuleKey<'a, 'i, R> {
1090 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1091 state.write_u64(self.hash);
1092 }
1093}