Skip to main content

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_feature_values;
45pub mod font_palette_values;
46pub mod import;
47pub mod keyframes;
48pub mod layer;
49pub mod media;
50pub mod namespace;
51pub mod nesting;
52pub mod page;
53pub mod position_try;
54pub mod property;
55pub mod scope;
56pub mod starting_style;
57pub mod style;
58pub mod supports;
59pub mod unknown;
60pub mod view_transition;
61pub mod viewport;
62
63use self::font_feature_values::FontFeatureValuesRule;
64use self::font_palette_values::FontPaletteValuesRule;
65use self::layer::{LayerBlockRule, LayerStatementRule};
66use self::property::PropertyRule;
67use crate::context::PropertyHandlerContext;
68use crate::declaration::{DeclarationBlock, DeclarationHandler};
69use crate::dependencies::{Dependency, ImportDependency};
70use crate::error::{MinifyError, ParserError, PrinterError, PrinterErrorKind};
71use crate::parser::{parse_rule_list, parse_style_block, DefaultAtRule, DefaultAtRuleParser, TopLevelRuleParser};
72use crate::prefixes::Feature;
73use crate::printer::Printer;
74use crate::rules::keyframes::KeyframesName;
75use crate::selector::{is_compatible, is_equivalent, Component, Selector, SelectorList};
76use crate::stylesheet::ParserOptions;
77use crate::targets::{should_compile, TargetsWithSupportsScope};
78use crate::traits::{AtRuleParser, ToCss};
79use crate::values::string::CowArcStr;
80use crate::vendor_prefix::VendorPrefix;
81#[cfg(feature = "visitor")]
82use crate::visitor::{Visit, VisitTypes, Visitor};
83use container::ContainerRule;
84use counter_style::CounterStyleRule;
85use cssparser::{parse_one_rule, ParseError, Parser, ParserInput};
86use custom_media::CustomMediaRule;
87use document::MozDocumentRule;
88use font_face::FontFaceRule;
89use import::ImportRule;
90use itertools::Itertools;
91use keyframes::KeyframesRule;
92use media::MediaRule;
93use namespace::NamespaceRule;
94use nesting::{NestedDeclarationsRule, NestingRule};
95use page::PageRule;
96use position_try::PositionTryRule;
97use scope::ScopeRule;
98use smallvec::{smallvec, SmallVec};
99use starting_style::StartingStyleRule;
100use std::collections::{HashMap, HashSet};
101use std::hash::{BuildHasherDefault, Hasher};
102use style::StyleRule;
103use supports::SupportsRule;
104use unknown::UnknownAtRule;
105use view_transition::ViewTransitionRule;
106use viewport::ViewportRule;
107
108#[derive(Clone)]
109pub(crate) struct StyleContext<'a, 'i> {
110  pub selectors: &'a SelectorList<'i>,
111  pub parent: Option<&'a StyleContext<'a, 'i>>,
112}
113
114/// A source location.
115#[derive(PartialEq, Eq, Debug, Clone, Copy)]
116#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(serde::Serialize))]
117#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
118#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
119#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
120pub struct Location {
121  /// The index of the source file within the source map.
122  pub source_index: u32,
123  /// The line number, starting at 0.
124  pub line: u32,
125  /// The column number within a line, starting at 1 for first the character of the line.
126  /// Column numbers are counted in UTF-16 code units.
127  pub column: u32,
128}
129
130/// A CSS rule.
131#[derive(Debug, PartialEq, Clone)]
132#[cfg_attr(feature = "visitor", derive(Visit))]
133#[cfg_attr(feature = "visitor", visit(visit_rule, RULES))]
134#[cfg_attr(
135  feature = "serde",
136  derive(serde::Serialize),
137  serde(tag = "type", content = "value", rename_all = "kebab-case")
138)]
139#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema), schemars(rename = "Rule"))]
140#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
141pub enum CssRule<'i, R = DefaultAtRule> {
142  /// A `@media` rule.
143  #[cfg_attr(feature = "serde", serde(borrow))]
144  Media(MediaRule<'i, R>),
145  /// An `@import` rule.
146  Import(ImportRule<'i>),
147  /// A style rule.
148  Style(StyleRule<'i, R>),
149  /// A `@keyframes` rule.
150  Keyframes(KeyframesRule<'i>),
151  /// A `@font-face` rule.
152  FontFace(FontFaceRule<'i>),
153  /// A `@font-palette-values` rule.
154  FontPaletteValues(FontPaletteValuesRule<'i>),
155  /// A `@font-feature-values` rule.
156  FontFeatureValues(FontFeatureValuesRule<'i>),
157  /// A `@page` rule.
158  Page(PageRule<'i>),
159  /// A `@supports` rule.
160  Supports(SupportsRule<'i, R>),
161  /// A `@counter-style` rule.
162  CounterStyle(CounterStyleRule<'i>),
163  /// A `@namespace` rule.
164  Namespace(NamespaceRule<'i>),
165  /// A `@-moz-document` rule.
166  MozDocument(MozDocumentRule<'i, R>),
167  /// A `@nest` rule.
168  Nesting(NestingRule<'i, R>),
169  /// A nested declarations rule.
170  NestedDeclarations(NestedDeclarationsRule<'i>),
171  /// A `@viewport` rule.
172  Viewport(ViewportRule<'i>),
173  /// A `@custom-media` rule.
174  CustomMedia(CustomMediaRule<'i>),
175  /// A `@layer` statement rule.
176  LayerStatement(LayerStatementRule<'i>),
177  /// A `@layer` block rule.
178  LayerBlock(LayerBlockRule<'i, R>),
179  /// A `@property` rule.
180  Property(PropertyRule<'i>),
181  /// A `@container` rule.
182  Container(ContainerRule<'i, R>),
183  /// A `@scope` rule.
184  Scope(ScopeRule<'i, R>),
185  /// A `@starting-style` rule.
186  StartingStyle(StartingStyleRule<'i, R>),
187  /// A `@view-transition` rule.
188  ViewTransition(ViewTransitionRule<'i>),
189  /// A `@position-try` rule.
190  PositionTry(PositionTryRule<'i>),
191  /// A placeholder for a rule that was removed.
192  Ignored,
193  /// An unknown at-rule.
194  Unknown(UnknownAtRule<'i>),
195  /// A custom at-rule.
196  Custom(R),
197}
198
199// Manually implemented deserialize to reduce binary size.
200#[cfg(feature = "serde")]
201#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
202impl<'i, 'de: 'i, R: serde::Deserialize<'de>> serde::Deserialize<'de> for CssRule<'i, R> {
203  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
204  where
205    D: serde::Deserializer<'de>,
206  {
207    #[derive(serde::Deserialize)]
208    #[serde(field_identifier, rename_all = "snake_case")]
209    enum Field {
210      Type,
211      Value,
212    }
213
214    struct PartialRule<'de> {
215      rule_type: CowArcStr<'de>,
216      content: serde_content::Value<'de>,
217    }
218
219    struct CssRuleVisitor;
220
221    impl<'de> serde::de::Visitor<'de> for CssRuleVisitor {
222      type Value = PartialRule<'de>;
223
224      fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
225        formatter.write_str("a CssRule")
226      }
227
228      fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
229      where
230        A: serde::de::MapAccess<'de>,
231      {
232        let mut rule_type: Option<CowArcStr<'de>> = None;
233        let mut value: Option<serde_content::Value> = None;
234        while let Some(key) = map.next_key()? {
235          match key {
236            Field::Type => {
237              rule_type = Some(map.next_value()?);
238            }
239            Field::Value => {
240              value = Some(map.next_value()?);
241            }
242          }
243        }
244
245        let rule_type = rule_type.ok_or_else(|| serde::de::Error::missing_field("type"))?;
246        let content = value.ok_or_else(|| serde::de::Error::missing_field("value"))?;
247        Ok(PartialRule { rule_type, content })
248      }
249    }
250
251    let partial = deserializer.deserialize_map(CssRuleVisitor)?;
252    let deserializer = serde_content::Deserializer::new(partial.content).coerce_numbers();
253
254    match partial.rule_type.as_ref() {
255      "media" => {
256        let rule = MediaRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
257        Ok(CssRule::Media(rule))
258      }
259      "import" => {
260        let rule = ImportRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
261        Ok(CssRule::Import(rule))
262      }
263      "style" => {
264        let rule = StyleRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
265        Ok(CssRule::Style(rule))
266      }
267      "keyframes" => {
268        let rule =
269          KeyframesRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
270        Ok(CssRule::Keyframes(rule))
271      }
272      "font-face" => {
273        let rule = FontFaceRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
274        Ok(CssRule::FontFace(rule))
275      }
276      "font-palette-values" => {
277        let rule =
278          FontPaletteValuesRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
279        Ok(CssRule::FontPaletteValues(rule))
280      }
281      "font-feature-values" => {
282        let rule =
283          FontFeatureValuesRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
284        Ok(CssRule::FontFeatureValues(rule))
285      }
286      "page" => {
287        let rule = PageRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
288        Ok(CssRule::Page(rule))
289      }
290      "supports" => {
291        let rule = SupportsRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
292        Ok(CssRule::Supports(rule))
293      }
294      "counter-style" => {
295        let rule =
296          CounterStyleRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
297        Ok(CssRule::CounterStyle(rule))
298      }
299      "namespace" => {
300        let rule =
301          NamespaceRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
302        Ok(CssRule::Namespace(rule))
303      }
304      "moz-document" => {
305        let rule =
306          MozDocumentRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
307        Ok(CssRule::MozDocument(rule))
308      }
309      "nesting" => {
310        let rule = NestingRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
311        Ok(CssRule::Nesting(rule))
312      }
313      "nested-declarations" => {
314        let rule = NestedDeclarationsRule::deserialize(deserializer)
315          .map_err(|e| serde::de::Error::custom(e.to_string()))?;
316        Ok(CssRule::NestedDeclarations(rule))
317      }
318      "viewport" => {
319        let rule = ViewportRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
320        Ok(CssRule::Viewport(rule))
321      }
322      "custom-media" => {
323        let rule =
324          CustomMediaRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
325        Ok(CssRule::CustomMedia(rule))
326      }
327      "layer-statement" => {
328        let rule =
329          LayerStatementRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
330        Ok(CssRule::LayerStatement(rule))
331      }
332      "layer-block" => {
333        let rule =
334          LayerBlockRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
335        Ok(CssRule::LayerBlock(rule))
336      }
337      "property" => {
338        let rule = PropertyRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
339        Ok(CssRule::Property(rule))
340      }
341      "container" => {
342        let rule =
343          ContainerRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
344        Ok(CssRule::Container(rule))
345      }
346      "scope" => {
347        let rule = ScopeRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
348        Ok(CssRule::Scope(rule))
349      }
350      "starting-style" => {
351        let rule =
352          StartingStyleRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
353        Ok(CssRule::StartingStyle(rule))
354      }
355      "view-transition" => {
356        let rule =
357          ViewTransitionRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
358        Ok(CssRule::ViewTransition(rule))
359      }
360      "position-try" => {
361        let rule =
362          PositionTryRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
363        Ok(CssRule::PositionTry(rule))
364      }
365      "ignored" => Ok(CssRule::Ignored),
366      "unknown" => {
367        let rule =
368          UnknownAtRule::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
369        Ok(CssRule::Unknown(rule))
370      }
371      "custom" => {
372        let rule = R::deserialize(deserializer).map_err(|e| serde::de::Error::custom(e.to_string()))?;
373        Ok(CssRule::Custom(rule))
374      }
375      t => Err(serde::de::Error::unknown_variant(t, &[])),
376    }
377  }
378}
379
380impl<'a, 'i, T: ToCss> ToCss for CssRule<'i, T> {
381  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
382  where
383    W: std::fmt::Write,
384  {
385    match self {
386      CssRule::Media(media) => media.to_css(dest),
387      CssRule::Import(import) => import.to_css(dest),
388      CssRule::Style(style) => style.to_css(dest),
389      CssRule::Keyframes(keyframes) => keyframes.to_css(dest),
390      CssRule::FontFace(font_face) => font_face.to_css(dest),
391      CssRule::FontPaletteValues(f) => f.to_css(dest),
392      CssRule::FontFeatureValues(font_feature_values) => font_feature_values.to_css(dest),
393      CssRule::Page(font_face) => font_face.to_css(dest),
394      CssRule::Supports(supports) => supports.to_css(dest),
395      CssRule::CounterStyle(counter_style) => counter_style.to_css(dest),
396      CssRule::Namespace(namespace) => namespace.to_css(dest),
397      CssRule::MozDocument(document) => document.to_css(dest),
398      CssRule::Nesting(nesting) => nesting.to_css(dest),
399      CssRule::NestedDeclarations(nested) => nested.to_css(dest),
400      CssRule::Viewport(viewport) => viewport.to_css(dest),
401      CssRule::CustomMedia(custom_media) => custom_media.to_css(dest),
402      CssRule::LayerStatement(layer) => layer.to_css(dest),
403      CssRule::LayerBlock(layer) => layer.to_css(dest),
404      CssRule::Property(property) => property.to_css(dest),
405      CssRule::StartingStyle(rule) => rule.to_css(dest),
406      CssRule::Container(container) => container.to_css(dest),
407      CssRule::Scope(scope) => scope.to_css(dest),
408      CssRule::ViewTransition(rule) => rule.to_css(dest),
409      CssRule::PositionTry(rule) => rule.to_css(dest),
410      CssRule::Unknown(unknown) => unknown.to_css(dest),
411      CssRule::Custom(rule) => rule.to_css(dest).map_err(|_| PrinterError {
412        kind: PrinterErrorKind::FmtError,
413        loc: None,
414      }),
415      CssRule::Ignored => Ok(()),
416    }
417  }
418}
419
420impl<'i> CssRule<'i, DefaultAtRule> {
421  /// Parse a single rule.
422  pub fn parse<'t>(
423    input: &mut Parser<'i, 't>,
424    options: &ParserOptions<'i>,
425  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
426    Self::parse_with(input, options, &mut DefaultAtRuleParser)
427  }
428
429  /// Parse a single rule from a string.
430  pub fn parse_string(
431    input: &'i str,
432    options: ParserOptions<'i>,
433  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
434    Self::parse_string_with(input, options, &mut DefaultAtRuleParser)
435  }
436}
437
438impl<'i, T> CssRule<'i, T> {
439  /// Parse a single rule.
440  pub fn parse_with<'t, P: AtRuleParser<'i, AtRule = T>>(
441    input: &mut Parser<'i, 't>,
442    options: &ParserOptions<'i>,
443    at_rule_parser: &mut P,
444  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
445    let mut rules = CssRuleList(Vec::new());
446    parse_one_rule(input, &mut TopLevelRuleParser::new(options, at_rule_parser, &mut rules))?;
447    Ok(rules.0.pop().unwrap())
448  }
449
450  /// Parse a single rule from a string.
451  pub fn parse_string_with<P: AtRuleParser<'i, AtRule = T>>(
452    input: &'i str,
453    options: ParserOptions<'i>,
454    at_rule_parser: &mut P,
455  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
456    let mut input = ParserInput::new(input);
457    let mut parser = Parser::new(&mut input);
458    Self::parse_with(&mut parser, &options, at_rule_parser)
459  }
460}
461
462/// A list of CSS rules.
463#[derive(Debug, PartialEq, Clone, Default)]
464#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
465#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
466#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
467pub struct CssRuleList<'i, R = DefaultAtRule>(
468  #[cfg_attr(feature = "serde", serde(borrow))] pub Vec<CssRule<'i, R>>,
469);
470
471impl<'i> CssRuleList<'i, DefaultAtRule> {
472  /// Parse a rule list.
473  pub fn parse<'t>(
474    input: &mut Parser<'i, 't>,
475    options: &ParserOptions<'i>,
476  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
477    Self::parse_with(input, options, &mut DefaultAtRuleParser)
478  }
479
480  /// Parse a style block, with both declarations and rules.
481  /// Resulting declarations are returned in a nested style rule.
482  pub fn parse_style_block<'t>(
483    input: &mut Parser<'i, 't>,
484    options: &ParserOptions<'i>,
485    is_nested: bool,
486  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
487    Self::parse_style_block_with(input, options, &mut DefaultAtRuleParser, is_nested)
488  }
489}
490
491impl<'i, T> CssRuleList<'i, T> {
492  /// Parse a rule list with a custom at rule parser.
493  pub fn parse_with<'t, P: AtRuleParser<'i, AtRule = T>>(
494    input: &mut Parser<'i, 't>,
495    options: &ParserOptions<'i>,
496    at_rule_parser: &mut P,
497  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
498    parse_rule_list(input, options, at_rule_parser)
499  }
500
501  /// Parse a style block, with both declarations and rules.
502  /// Resulting declarations are returned in a nested style rule.
503  pub fn parse_style_block_with<'t, P: AtRuleParser<'i, AtRule = T>>(
504    input: &mut Parser<'i, 't>,
505    options: &ParserOptions<'i>,
506    at_rule_parser: &mut P,
507    is_nested: bool,
508  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
509    parse_style_block(input, options, at_rule_parser, is_nested)
510  }
511}
512
513// Manually implemented to avoid circular child types.
514#[cfg(feature = "visitor")]
515#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
516impl<'i, T: Visit<'i, T, V>, V: ?Sized + Visitor<'i, T>> Visit<'i, T, V> for CssRuleList<'i, T> {
517  const CHILD_TYPES: VisitTypes = VisitTypes::all();
518
519  fn visit(&mut self, visitor: &mut V) -> Result<(), V::Error> {
520    if visitor.visit_types().contains(VisitTypes::RULES) {
521      visitor.visit_rule_list(self)
522    } else {
523      self.0.visit(visitor)
524    }
525  }
526
527  fn visit_children(&mut self, visitor: &mut V) -> Result<(), V::Error> {
528    self.0.visit(visitor)
529  }
530}
531
532pub(crate) struct MinifyContext<'a, 'i> {
533  pub targets: TargetsWithSupportsScope,
534  pub handler: &'a mut DeclarationHandler<'i>,
535  pub important_handler: &'a mut DeclarationHandler<'i>,
536  pub handler_context: PropertyHandlerContext<'i, 'a>,
537  pub unused_symbols: &'a HashSet<String>,
538  pub custom_media: Option<HashMap<CowArcStr<'i>, CustomMediaRule<'i>>>,
539  pub css_modules: bool,
540  pub pure_css_modules: bool,
541}
542
543impl<'i, T: Clone> CssRuleList<'i, T> {
544  pub(crate) fn minify(
545    &mut self,
546    context: &mut MinifyContext<'_, 'i>,
547    parent_is_unused: bool,
548  ) -> Result<(), MinifyError> {
549    let mut keyframe_rules = HashMap::new();
550    let mut layer_rules = HashMap::new();
551    let mut has_layers = false;
552    let mut property_rules = HashMap::new();
553    let mut font_feature_values_rules = Vec::new();
554    let mut style_rules =
555      HashMap::with_capacity_and_hasher(self.0.len(), BuildHasherDefault::<PrecomputedHasher>::default());
556    let mut rules = Vec::new();
557    for mut rule in self.0.drain(..) {
558      match &mut rule {
559        CssRule::Keyframes(keyframes) => {
560          if context.unused_symbols.contains(match &keyframes.name {
561            KeyframesName::Ident(ident) => ident.0.as_ref(),
562            KeyframesName::Custom(string) => string.as_ref(),
563          }) {
564            continue;
565          }
566          keyframes.minify(context);
567
568          macro_rules! set_prefix {
569            ($keyframes: ident) => {
570              $keyframes.vendor_prefix =
571                context.targets.current.prefixes($keyframes.vendor_prefix, Feature::AtKeyframes);
572            };
573          }
574
575          // Merge @keyframes rules with the same name.
576          if let Some(existing_idx) = keyframe_rules.get(&keyframes.name) {
577            if let Some(CssRule::Keyframes(existing)) = &mut rules.get_mut(*existing_idx) {
578              // If the existing rule has the same vendor prefixes, replace it with this rule.
579              if existing.vendor_prefix == keyframes.vendor_prefix {
580                *existing = keyframes.clone();
581                continue;
582              }
583              // Otherwise, if the keyframes are identical, merge the prefixes.
584              if existing.keyframes == keyframes.keyframes {
585                existing.vendor_prefix |= keyframes.vendor_prefix;
586                set_prefix!(existing);
587                continue;
588              }
589            }
590          }
591
592          set_prefix!(keyframes);
593          keyframe_rules.insert(keyframes.name.clone(), rules.len());
594
595          let fallbacks = keyframes.get_fallbacks(&context.targets.current);
596          rules.push(rule);
597          rules.extend(fallbacks);
598          continue;
599        }
600        CssRule::CustomMedia(_) => {
601          if context.custom_media.is_some() {
602            continue;
603          }
604        }
605        CssRule::Media(media) => {
606          if let Some(CssRule::Media(last_rule)) = rules.last_mut() {
607            if last_rule.query == media.query {
608              last_rule.rules.0.extend(media.rules.0.drain(..));
609              last_rule.minify(context, parent_is_unused)?;
610              continue;
611            }
612          }
613
614          if media.minify(context, parent_is_unused)? {
615            continue;
616          }
617        }
618        CssRule::Supports(supports) => {
619          if let Some(CssRule::Supports(last_rule)) = rules.last_mut() {
620            if last_rule.condition == supports.condition {
621              last_rule.rules.0.extend(supports.rules.0.drain(..));
622              last_rule.minify(context, parent_is_unused)?;
623              continue;
624            }
625          }
626
627          supports.minify(context, parent_is_unused)?;
628          if supports.rules.0.is_empty() {
629            continue;
630          }
631        }
632        CssRule::Container(container) => {
633          if let Some(CssRule::Container(last_rule)) = rules.last_mut() {
634            if last_rule.name == container.name && last_rule.condition == container.condition {
635              last_rule.rules.0.extend(container.rules.0.drain(..));
636              last_rule.minify(context, parent_is_unused)?;
637              continue;
638            }
639          }
640
641          if container.minify(context, parent_is_unused)? {
642            continue;
643          }
644        }
645        CssRule::LayerBlock(layer) => {
646          // Merging non-adjacent layer rules is safe because they are applied
647          // in the order they are first defined.
648          if let Some(name) = &layer.name {
649            if let Some(idx) = layer_rules.get(name) {
650              if let Some(CssRule::LayerBlock(last_rule)) = rules.get_mut(*idx) {
651                last_rule.rules.0.extend(layer.rules.0.drain(..));
652                continue;
653              }
654            }
655
656            layer_rules.insert(name.clone(), rules.len());
657            has_layers = true;
658          }
659        }
660        CssRule::LayerStatement(layer) => {
661          // Create @layer block rules for each declared layer name,
662          // so we can merge other blocks into it later on.
663          for name in &layer.names {
664            if !layer_rules.contains_key(name) {
665              layer_rules.insert(name.clone(), rules.len());
666              has_layers = true;
667              rules.push(CssRule::LayerBlock(LayerBlockRule {
668                name: Some(name.clone()),
669                rules: CssRuleList(vec![]),
670                loc: layer.loc.clone(),
671              }));
672            }
673          }
674          continue;
675        }
676        CssRule::MozDocument(document) => document.minify(context)?,
677        CssRule::Style(style) => {
678          if parent_is_unused || style.minify(context, parent_is_unused)? {
679            continue;
680          }
681
682          // If some of the selectors in this rule are not compatible with the targets,
683          // we need to either wrap in :is() or split them into multiple rules.
684          let incompatible = if style.selectors.0.len() > 1
685            && context.targets.current.should_compile_selectors()
686            && !style.is_compatible(context.targets.current)
687          {
688            // The :is() selector accepts a forgiving selector list, so use that if possible.
689            // Note that :is() does not allow pseudo elements, so we need to check for that.
690            // In addition, :is() takes the highest specificity of its arguments, so if the selectors
691            // have different weights, we need to split them into separate rules as well.
692            if context.targets.current.is_compatible(crate::compat::Feature::IsSelector)
693              && !style.selectors.0.iter().any(|selector| selector.has_pseudo_element())
694              && style.selectors.0.iter().map(|selector| selector.specificity()).all_equal()
695            {
696              style.selectors =
697                SelectorList::new(smallvec![
698                  Component::Is(style.selectors.0.clone().into_boxed_slice()).into()
699                ]);
700              smallvec![]
701            } else {
702              // Otherwise, partition the selectors and keep the compatible ones in this rule.
703              // We will generate additional rules for incompatible selectors later.
704              let (compatible, incompatible) = style
705                .selectors
706                .0
707                .iter()
708                .cloned()
709                .partition::<SmallVec<[Selector; 1]>, _>(|selector| {
710                  let list = SelectorList::new(smallvec![selector.clone()]);
711                  is_compatible(&list.0, context.targets.current)
712                });
713              style.selectors = SelectorList::new(compatible);
714              incompatible
715            }
716          } else {
717            smallvec![]
718          };
719
720          style.update_prefix(context);
721
722          // Attempt to merge the new rule with the last rule we added.
723          let mut merged = false;
724          if let Some(CssRule::Style(last_style_rule)) = rules.last_mut() {
725            if merge_style_rules(style, last_style_rule, context) {
726              // If that was successful, then the last rule has been updated to include the
727              // selectors/declarations of the new rule. This might mean that we can merge it
728              // with the previous rule, so continue trying while we have style rules available.
729              while rules.len() >= 2 {
730                let len = rules.len();
731                let (a, b) = rules.split_at_mut(len - 1);
732                if let (CssRule::Style(last), CssRule::Style(prev)) = (&mut b[0], &mut a[len - 2]) {
733                  if merge_style_rules(last, prev, context) {
734                    // If we were able to merge the last rule into the previous one, remove the last.
735                    rules.pop();
736                    continue;
737                  }
738                }
739                // If we didn't see a style rule, or were unable to merge, stop.
740                break;
741              }
742              merged = true;
743            }
744          }
745
746          // Create additional rules for logical properties, @supports overrides, and incompatible selectors.
747          let supports = context.handler_context.get_supports_rules(&style);
748          let logical = context.handler_context.get_additional_rules(&style);
749
750          let incompatible_rules = incompatible
751            .into_iter()
752            .map(|selector| {
753              // Create a clone of the rule with only the one incompatible selector.
754              let list = SelectorList::new(smallvec![selector]);
755              let mut clone = style.clone();
756              clone.selectors = list;
757              clone.update_prefix(context);
758
759              // Also add rules for logical properties and @supports overrides.
760              let supports = context.handler_context.get_supports_rules(&clone);
761              let logical = context.handler_context.get_additional_rules(&clone);
762              (clone, logical, supports)
763            })
764            .collect::<Vec<_>>();
765
766          context.handler_context.reset();
767
768          // If the rule has nested rules, and we have extra rules to insert such as for logical properties,
769          // we need to split the rule in two so we can insert the extra rules in between the declarations from
770          // the main rule and the nested rules.
771          let nested_rule = if !style.rules.0.is_empty()
772            // can happen if there are no compatible rules, above.
773            && !style.selectors.0.is_empty()
774            && (!logical.is_empty() || !supports.is_empty() || !incompatible_rules.is_empty())
775          {
776            let mut rules = CssRuleList(vec![]);
777            std::mem::swap(&mut style.rules, &mut rules);
778            Some(StyleRule {
779              selectors: style.selectors.clone(),
780              declarations: DeclarationBlock::default(),
781              rules,
782              vendor_prefix: style.vendor_prefix,
783              loc: style.loc,
784            })
785          } else {
786            None
787          };
788
789          if !merged && !style.is_empty() {
790            let source_index = style.loc.source_index;
791            let has_no_rules = style.rules.0.is_empty();
792            let idx = rules.len();
793            rules.push(rule);
794
795            // Check if this rule is a duplicate of an earlier rule, meaning it has
796            // the same selectors and defines the same properties. If so, remove the
797            // earlier rule because this one completely overrides it.
798            if has_no_rules {
799              // SAFETY: StyleRuleKeys never live beyond this method.
800              let key = StyleRuleKey::new(unsafe { &*(&rules as *const _) }, idx);
801              if idx > 0 {
802                if let Some(i) = style_rules.remove(&key) {
803                  if let CssRule::Style(other) = &rules[i] {
804                    // Don't remove the rule if this is a CSS module and the other rule came from a different file.
805                    if !context.css_modules || source_index == other.loc.source_index {
806                      // Only mark the rule as ignored so we don't need to change all of the indices.
807                      rules[i] = CssRule::Ignored;
808                    }
809                  }
810                }
811              }
812
813              style_rules.insert(key, idx);
814            }
815          }
816
817          if !logical.is_empty() {
818            let mut logical = CssRuleList(logical);
819            logical.minify(context, parent_is_unused)?;
820            rules.extend(logical.0)
821          }
822
823          rules.extend(supports);
824          for (rule, logical, supports) in incompatible_rules {
825            if !rule.is_empty() {
826              rules.push(CssRule::Style(rule));
827            }
828            if !logical.is_empty() {
829              let mut logical = CssRuleList(logical);
830              logical.minify(context, parent_is_unused)?;
831              rules.extend(logical.0)
832            }
833            rules.extend(supports);
834          }
835
836          if let Some(nested_rule) = nested_rule {
837            rules.push(CssRule::Style(nested_rule));
838          }
839
840          continue;
841        }
842        CssRule::CounterStyle(counter_style) => {
843          if context.unused_symbols.contains(counter_style.name.0.as_ref()) {
844            continue;
845          }
846        }
847        CssRule::Scope(scope) => scope.minify(context)?,
848        CssRule::Nesting(nesting) => {
849          if nesting.minify(context, parent_is_unused)? {
850            continue;
851          }
852        }
853        CssRule::NestedDeclarations(nested) => {
854          if nested.minify(context, parent_is_unused) {
855            continue;
856          }
857        }
858        CssRule::StartingStyle(rule) => {
859          if rule.minify(context, parent_is_unused)? {
860            continue;
861          }
862        }
863        CssRule::FontPaletteValues(f) => {
864          if context.unused_symbols.contains(f.name.0.as_ref()) {
865            continue;
866          }
867
868          f.minify(context, parent_is_unused);
869
870          let fallbacks = f.get_fallbacks(context.targets.current);
871          rules.push(rule);
872          rules.extend(fallbacks);
873          continue;
874        }
875        CssRule::FontFeatureValues(rule) => {
876          if let Some(index) = font_feature_values_rules
877            .iter()
878            .find(|index| matches!(&rules[**index], CssRule::FontFeatureValues(r) if r.name == rule.name))
879          {
880            if let CssRule::FontFeatureValues(existing) = &mut rules[*index] {
881              existing.merge(rule);
882            }
883            continue;
884          } else {
885            font_feature_values_rules.push(rules.len());
886          }
887        }
888        CssRule::PositionTry(rule) => {
889          if context.unused_symbols.contains(rule.name.0.as_ref()) {
890            continue;
891          }
892        }
893        CssRule::Property(property) => {
894          if context.unused_symbols.contains(property.name.0.as_ref()) {
895            continue;
896          }
897
898          if let Some(index) = property_rules.get(&property.name) {
899            rules[*index] = rule;
900            continue;
901          } else {
902            property_rules.insert(property.name.clone(), rules.len());
903          }
904        }
905        CssRule::Import(_) => {
906          // @layer blocks can't be inlined into layers declared before imports.
907          layer_rules.clear();
908        }
909        _ => {}
910      }
911
912      rules.push(rule)
913    }
914
915    // Optimize @layer rules. Combine subsequent empty layer blocks into a single @layer statement
916    // so that layers are declared in the correct order.
917    if has_layers {
918      let mut declared_layers = HashSet::new();
919      let mut layer_statement = None;
920      for index in 0..rules.len() {
921        match &mut rules[index] {
922          CssRule::LayerBlock(layer) => {
923            if layer.minify(context, parent_is_unused)? {
924              if let Some(name) = &layer.name {
925                if declared_layers.contains(name) {
926                  // Remove empty layer that has already been declared.
927                  rules[index] = CssRule::Ignored;
928                  continue;
929                }
930
931                let name = name.clone();
932                declared_layers.insert(name.clone());
933
934                if let Some(layer_index) = layer_statement {
935                  if let CssRule::LayerStatement(layer) = &mut rules[layer_index] {
936                    // Add name to previous layer statement rule and remove this one.
937                    layer.names.push(name);
938                    rules[index] = CssRule::Ignored;
939                  }
940                } else {
941                  // Create a new layer statement rule to declare the name.
942                  rules[index] = CssRule::LayerStatement(LayerStatementRule {
943                    names: vec![name],
944                    loc: layer.loc,
945                  });
946                  layer_statement = Some(index);
947                }
948              } else {
949                // Remove empty anonymous layer.
950                rules[index] = CssRule::Ignored;
951              }
952            } else {
953              // Non-empty @layer block. Start a new statement.
954              layer_statement = None;
955            }
956          }
957          CssRule::Import(import) => {
958            if let Some(layer) = &import.layer {
959              // Start a new @layer statement so the import layer is in the right order.
960              layer_statement = None;
961              if let Some(name) = layer {
962                declared_layers.insert(name.clone());
963              }
964            }
965          }
966          _ => {}
967        }
968      }
969    }
970
971    self.0 = rules;
972    Ok(())
973  }
974}
975
976fn merge_style_rules<'i, T>(
977  style: &mut StyleRule<'i, T>,
978  last_style_rule: &mut StyleRule<'i, T>,
979  context: &mut MinifyContext<'_, 'i>,
980) -> bool {
981  // Merge declarations if the selectors are equivalent, and both are compatible with all targets.
982  if style.selectors == last_style_rule.selectors
983    && style.is_compatible(context.targets.current)
984    && last_style_rule.is_compatible(context.targets.current)
985    && style.rules.0.is_empty()
986    && last_style_rule.rules.0.is_empty()
987    && (!context.css_modules || style.loc.source_index == last_style_rule.loc.source_index)
988  {
989    last_style_rule
990      .declarations
991      .declarations
992      .extend(style.declarations.declarations.drain(..));
993    last_style_rule
994      .declarations
995      .important_declarations
996      .extend(style.declarations.important_declarations.drain(..));
997    last_style_rule
998      .declarations
999      .minify(context.handler, context.important_handler, &mut context.handler_context);
1000    return true;
1001  } else if style.declarations == last_style_rule.declarations
1002    && style.rules.0.is_empty()
1003    && last_style_rule.rules.0.is_empty()
1004  {
1005    // If both selectors are potentially vendor prefixable, and they are
1006    // equivalent minus prefixes, add the prefix to the last rule.
1007    if !style.vendor_prefix.is_empty()
1008      && !last_style_rule.vendor_prefix.is_empty()
1009      && is_equivalent(&style.selectors.0, &last_style_rule.selectors.0)
1010    {
1011      // If the new rule is unprefixed, replace the prefixes of the last rule.
1012      // Otherwise, add the new prefix.
1013      if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() {
1014        last_style_rule.vendor_prefix = style.vendor_prefix;
1015      } else {
1016        last_style_rule.vendor_prefix |= style.vendor_prefix;
1017      }
1018      return true;
1019    }
1020
1021    // Append the selectors to the last rule if the declarations are the same, and all selectors are compatible.
1022    if style.is_compatible(context.targets.current) && last_style_rule.is_compatible(context.targets.current) {
1023      last_style_rule.selectors.0.extend(style.selectors.0.drain(..));
1024      if style.vendor_prefix.contains(VendorPrefix::None) && context.targets.current.should_compile_selectors() {
1025        last_style_rule.vendor_prefix = style.vendor_prefix;
1026      } else {
1027        last_style_rule.vendor_prefix |= style.vendor_prefix;
1028      }
1029      return true;
1030    }
1031  }
1032  false
1033}
1034
1035impl<'a, 'i, T: ToCss> ToCss for CssRuleList<'i, T> {
1036  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1037  where
1038    W: std::fmt::Write,
1039  {
1040    let mut first = true;
1041    let mut last_without_block = false;
1042
1043    for (i, rule) in self.0.iter().enumerate() {
1044      if let CssRule::Ignored = &rule {
1045        continue;
1046      }
1047
1048      // Skip @import rules if collecting dependencies.
1049      if let CssRule::Import(rule) = &rule {
1050        if dest.remove_imports {
1051          let dep = if dest.dependencies.is_some() {
1052            Some(Dependency::Import(ImportDependency::new(&rule, dest.filename())))
1053          } else {
1054            None
1055          };
1056
1057          if let Some(dependencies) = &mut dest.dependencies {
1058            dependencies.push(dep.unwrap());
1059            continue;
1060          }
1061        }
1062      }
1063
1064      if first {
1065        first = false;
1066      } else {
1067        if !dest.minify
1068          && !(last_without_block
1069            && matches!(
1070              rule,
1071              CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)
1072            ))
1073        {
1074          dest.write_char('\n')?;
1075        }
1076        dest.newline()?;
1077      }
1078      rule.to_css(dest)?;
1079
1080      // If this is an invisible nested declarations rule, and not the last rule in the block, add a semicolon.
1081      if dest.minify
1082        && !should_compile!(dest.targets.current, Nesting)
1083        && matches!(rule, CssRule::NestedDeclarations(_))
1084        && i != self.0.len() - 1
1085      {
1086        dest.write_char(';')?;
1087      }
1088
1089      last_without_block = matches!(
1090        rule,
1091        CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)
1092      );
1093    }
1094
1095    Ok(())
1096  }
1097}
1098
1099impl<'i, T> std::ops::Index<usize> for CssRuleList<'i, T> {
1100  type Output = CssRule<'i, T>;
1101
1102  fn index(&self, index: usize) -> &Self::Output {
1103    &self.0[index]
1104  }
1105}
1106
1107impl<'i, T> std::ops::IndexMut<usize> for CssRuleList<'i, T> {
1108  fn index_mut(&mut self, index: usize) -> &mut Self::Output {
1109    &mut self.0[index]
1110  }
1111}
1112
1113/// A hasher that expects to be called with a single u64, which is already a hash.
1114#[derive(Default)]
1115struct PrecomputedHasher {
1116  hash: Option<u64>,
1117}
1118
1119impl Hasher for PrecomputedHasher {
1120  #[inline]
1121  fn write(&mut self, _: &[u8]) {
1122    unreachable!()
1123  }
1124
1125  #[inline]
1126  fn write_u64(&mut self, i: u64) {
1127    debug_assert!(self.hash.is_none());
1128    self.hash = Some(i);
1129  }
1130
1131  #[inline]
1132  fn finish(&self) -> u64 {
1133    self.hash.unwrap()
1134  }
1135}
1136
1137/// A key to a StyleRule meant for use in a HashMap for quickly detecting duplicates.
1138/// It stores a reference to a list and an index so it can access items without cloning
1139/// even when the list is reallocated. A hash is also pre-computed for fast lookups.
1140#[derive(Clone)]
1141pub(crate) struct StyleRuleKey<'a, 'i, R> {
1142  list: &'a Vec<CssRule<'i, R>>,
1143  index: usize,
1144  hash: u64,
1145}
1146
1147impl<'a, 'i, R> StyleRuleKey<'a, 'i, R> {
1148  fn new(list: &'a Vec<CssRule<'i, R>>, index: usize) -> Self {
1149    let rule = match &list[index] {
1150      CssRule::Style(style) => style,
1151      _ => unreachable!(),
1152    };
1153
1154    Self {
1155      list,
1156      index,
1157      hash: rule.hash_key(),
1158    }
1159  }
1160}
1161
1162impl<'a, 'i, R> PartialEq for StyleRuleKey<'a, 'i, R> {
1163  fn eq(&self, other: &Self) -> bool {
1164    let rule = match self.list.get(self.index) {
1165      Some(CssRule::Style(style)) => style,
1166      _ => return false,
1167    };
1168
1169    let other_rule = match other.list.get(other.index) {
1170      Some(CssRule::Style(style)) => style,
1171      _ => return false,
1172    };
1173
1174    rule.is_duplicate(other_rule)
1175  }
1176}
1177
1178impl<'a, 'i, R> Eq for StyleRuleKey<'a, 'i, R> {}
1179
1180impl<'a, 'i, R> std::hash::Hash for StyleRuleKey<'a, 'i, R> {
1181  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1182    state.write_u64(self.hash);
1183  }
1184}