lightningcss/rules/
mod.rs

1//! CSS rules.
2//!
3//! The [CssRule](CssRule) enum includes all supported rules, and can be used to parse
4//! and serialize rules from CSS. Lists of rules (i.e. within a stylesheet, or inside
5//! another rule such as `@media`) are represented by [CssRuleList](CssRuleList).
6//!
7//! Each rule includes a source location, which indicates the line and column within
8//! the source file where it was parsed. This is used when generating source maps.
9//!
10//! # Example
11//!
12//! This example shows how you could parse a single CSS rule, and serialize it to a string.
13//!
14//! ```
15//! use lightningcss::{
16//!   rules::CssRule,
17//!   traits::ToCss,
18//!   stylesheet::{ParserOptions, PrinterOptions}
19//! };
20//!
21//! let rule = CssRule::parse_string(
22//!   ".foo { color: red; }",
23//!   ParserOptions::default()
24//! ).unwrap();
25//!
26//! assert_eq!(
27//!   rule.to_css_string(PrinterOptions::default()).unwrap(),
28//!   ".foo {\n  color: red;\n}"
29//! );
30//! ```
31//!
32//! If you have a [cssparser::Parser](cssparser::Parser) already, you can also use the `parse` and `to_css`
33//! methods instead, rather than parsing from a string.
34//!
35//! See [StyleSheet](super::stylesheet::StyleSheet) to parse an entire file of multiple rules.
36
37#![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/// A source location.
109#[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  /// The index of the source file within the source map.
116  pub source_index: u32,
117  /// The line number, starting at 0.
118  pub line: u32,
119  /// The column number within a line, starting at 1 for first the character of the line.
120  /// Column numbers are counted in UTF-16 code units.
121  pub column: u32,
122}
123
124/// A CSS rule.
125#[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  /// A `@media` rule.
137  #[cfg_attr(feature = "serde", serde(borrow))]
138  Media(MediaRule<'i, R>),
139  /// An `@import` rule.
140  Import(ImportRule<'i>),
141  /// A style rule.
142  Style(StyleRule<'i, R>),
143  /// A `@keyframes` rule.
144  Keyframes(KeyframesRule<'i>),
145  /// A `@font-face` rule.
146  FontFace(FontFaceRule<'i>),
147  /// A `@font-palette-values` rule.
148  FontPaletteValues(FontPaletteValuesRule<'i>),
149  /// A `@page` rule.
150  Page(PageRule<'i>),
151  /// A `@supports` rule.
152  Supports(SupportsRule<'i, R>),
153  /// A `@counter-style` rule.
154  CounterStyle(CounterStyleRule<'i>),
155  /// A `@namespace` rule.
156  Namespace(NamespaceRule<'i>),
157  /// A `@-moz-document` rule.
158  MozDocument(MozDocumentRule<'i, R>),
159  /// A `@nest` rule.
160  Nesting(NestingRule<'i, R>),
161  /// A `@viewport` rule.
162  Viewport(ViewportRule<'i>),
163  /// A `@custom-media` rule.
164  CustomMedia(CustomMediaRule<'i>),
165  /// A `@layer` statement rule.
166  LayerStatement(LayerStatementRule<'i>),
167  /// A `@layer` block rule.
168  LayerBlock(LayerBlockRule<'i, R>),
169  /// A `@property` rule.
170  Property(PropertyRule<'i>),
171  /// A `@container` rule.
172  Container(ContainerRule<'i, R>),
173  /// A `@scope` rule.
174  Scope(ScopeRule<'i, R>),
175  /// A `@starting-style` rule.
176  StartingStyle(StartingStyleRule<'i, R>),
177  /// A placeholder for a rule that was removed.
178  Ignored,
179  /// An unknown at-rule.
180  Unknown(UnknownAtRule<'i>),
181  /// A custom at-rule.
182  Custom(R),
183}
184
185// Manually implemented deserialize to reduce binary size.
186#[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  /// Parse a single rule.
373  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  /// Parse a single rule from a string.
381  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  /// Parse a single rule.
391  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  /// Parse a single rule from a string.
402  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/// A list of CSS rules.
414#[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  /// Parse a rule list.
424  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  /// Parse a style block, with both declarations and rules.
432  /// Resulting declarations are returned in a nested style rule.
433  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  /// Parse a rule list with a custom at rule parser.
444  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  /// Parse a style block, with both declarations and rules.
453  /// Resulting declarations are returned in a nested style rule.
454  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// Manually implemented to avoid circular child types.
465#[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          // Merge @keyframes rules with the same name.
524          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 the existing rule has the same vendor prefixes, replace it with this rule.
527              if existing.vendor_prefix == keyframes.vendor_prefix {
528                *existing = keyframes.clone();
529                continue;
530              }
531              // Otherwise, if the keyframes are identical, merge the prefixes.
532              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          // Merging non-adjacent layer rules is safe because they are applied
595          // in the order they are first defined.
596          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          // Create @layer block rules for each declared layer name,
609          // so we can merge other blocks into it later on.
610          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          // If some of the selectors in this rule are not compatible with the targets,
629          // we need to either wrap in :is() or split them into multiple rules.
630          let incompatible = if style.selectors.0.len() > 1
631            && context.targets.should_compile_selectors()
632            && !style.is_compatible(*context.targets)
633          {
634            // The :is() selector accepts a forgiving selector list, so use that if possible.
635            // Note that :is() does not allow pseudo elements, so we need to check for that.
636            // In addition, :is() takes the highest specificity of its arguments, so if the selectors
637            // have different weights, we need to split them into separate rules as well.
638            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              // Otherwise, partition the selectors and keep the compatible ones in this rule.
649              // We will generate additional rules for incompatible selectors later.
650              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          // Attempt to merge the new rule with the last rule we added.
669          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              // If that was successful, then the last rule has been updated to include the
673              // selectors/declarations of the new rule. This might mean that we can merge it
674              // with the previous rule, so continue trying while we have style rules available.
675              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                    // If we were able to merge the last rule into the previous one, remove the last.
681                    rules.pop();
682                    continue;
683                  }
684                }
685                // If we didn't see a style rule, or were unable to merge, stop.
686                break;
687              }
688              merged = true;
689            }
690          }
691
692          // Create additional rules for logical properties, @supports overrides, and incompatible selectors.
693          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              // Create a clone of the rule with only the one incompatible selector.
700              let list = SelectorList::new(smallvec![selector]);
701              let mut clone = style.clone();
702              clone.selectors = list;
703              clone.update_prefix(context);
704
705              // Also add rules for logical properties and @supports overrides.
706              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          // If the rule has nested rules, and we have extra rules to insert such as for logical properties,
715          // we need to split the rule in two so we can insert the extra rules in between the declarations from
716          // the main rule and the nested rules.
717          let nested_rule = if !style.rules.0.is_empty()
718            // can happen if there are no compatible rules, above.
719            && !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            // Check if this rule is a duplicate of an earlier rule, meaning it has
742            // the same selectors and defines the same properties. If so, remove the
743            // earlier rule because this one completely overrides it.
744            if has_no_rules {
745              // SAFETY: StyleRuleKeys never live beyond this method.
746              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                    // Don't remove the rule if this is a CSS module and the other rule came from a different file.
751                    if !context.css_modules || source_index == other.loc.source_index {
752                      // Only mark the rule as ignored so we don't need to change all of the indices.
753                      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    // Optimize @layer rules. Combine subsequent empty layer blocks into a single @layer statement
835    // so that layers are declared in the correct order.
836    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                  // Remove empty layer that has already been declared.
846                  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                    // Add name to previous layer statement rule and remove this one.
856                    layer.names.push(name);
857                    rules[index] = CssRule::Ignored;
858                  }
859                } else {
860                  // Create a new layer statement rule to declare the name.
861                  rules[index] = CssRule::LayerStatement(LayerStatementRule {
862                    names: vec![name],
863                    loc: layer.loc,
864                  });
865                  layer_statement = Some(index);
866                }
867              } else {
868                // Remove empty anonymous layer.
869                rules[index] = CssRule::Ignored;
870              }
871            } else {
872              // Non-empty @layer block. Start a new statement.
873              layer_statement = None;
874            }
875          }
876          CssRule::Import(import) => {
877            if let Some(layer) = &import.layer {
878              // Start a new @layer statement so the import layer is in the right order.
879              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  // Merge declarations if the selectors are equivalent, and both are compatible with all targets.
901  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 both selectors are potentially vendor prefixable, and they are
925    // equivalent minus prefixes, add the prefix to the last rule.
926    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 the new rule is unprefixed, replace the prefixes of the last rule.
931      // Otherwise, add the new prefix.
932      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    // Append the selectors to the last rule if the declarations are the same, and all selectors are compatible.
941    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      // Skip @import rules if collecting dependencies.
968      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/// A hasher that expects to be called with a single u64, which is already a hash.
1023#[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/// A key to a StyleRule meant for use in a HashMap for quickly detecting duplicates.
1047/// It stores a reference to a list and an index so it can access items without cloning
1048/// even when the list is reallocated. A hash is also pre-computed for fast lookups.
1049#[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}