lightningcss/rules/
scope.rs

1//! The `@scope` rule.
2
3use super::Location;
4use super::{CssRuleList, MinifyContext};
5use crate::error::{MinifyError, PrinterError};
6use crate::parser::DefaultAtRule;
7use crate::printer::Printer;
8use crate::selector::{is_pure_css_modules_selector, SelectorList};
9use crate::traits::ToCss;
10#[cfg(feature = "visitor")]
11use crate::visitor::Visit;
12
13/// A [@scope](https://drafts.csswg.org/css-cascade-6/#scope-atrule) rule.
14///
15/// @scope (<scope-start>) [to (<scope-end>)]? {
16///  <stylesheet>
17/// }
18#[derive(Debug, PartialEq, Clone)]
19#[cfg_attr(feature = "visitor", derive(Visit))]
20#[cfg_attr(
21  feature = "serde",
22  derive(serde::Serialize, serde::Deserialize),
23  serde(rename_all = "camelCase")
24)]
25#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
26#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
27pub struct ScopeRule<'i, R = DefaultAtRule> {
28  /// A selector list used to identify the scoping root(s).
29  pub scope_start: Option<SelectorList<'i>>,
30  /// A selector list used to identify any scoping limits.
31  pub scope_end: Option<SelectorList<'i>>,
32  /// Nested rules within the `@scope` rule.
33  #[cfg_attr(feature = "serde", serde(borrow))]
34  pub rules: CssRuleList<'i, R>,
35  /// The location of the rule in the source file.
36  #[cfg_attr(feature = "visitor", skip_visit)]
37  pub loc: Location,
38}
39
40impl<'i, T: Clone> ScopeRule<'i, T> {
41  pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>) -> Result<(), MinifyError> {
42    if context.pure_css_modules {
43      if let Some(scope_start) = &self.scope_start {
44        if !scope_start.0.iter().all(is_pure_css_modules_selector) {
45          return Err(MinifyError {
46            kind: crate::error::MinifyErrorKind::ImpureCSSModuleSelector,
47            loc: self.loc,
48          });
49        }
50      }
51
52      if let Some(scope_end) = &self.scope_end {
53        if !scope_end.0.iter().all(is_pure_css_modules_selector) {
54          return Err(MinifyError {
55            kind: crate::error::MinifyErrorKind::ImpureCSSModuleSelector,
56            loc: self.loc,
57          });
58        }
59      }
60    }
61
62    self.rules.minify(context, false)
63  }
64}
65
66impl<'i, T: ToCss> ToCss for ScopeRule<'i, T> {
67  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
68  where
69    W: std::fmt::Write,
70  {
71    #[cfg(feature = "sourcemap")]
72    dest.add_mapping(self.loc);
73    dest.write_str("@scope")?;
74    dest.whitespace()?;
75    if let Some(scope_start) = &self.scope_start {
76      dest.write_char('(')?;
77      scope_start.to_css(dest)?;
78      dest.write_char(')')?;
79      dest.whitespace()?;
80    }
81    if let Some(scope_end) = &self.scope_end {
82      if dest.minify {
83        dest.write_char(' ')?;
84      }
85      dest.write_str("to (")?;
86      // <scope-start> is treated as an ancestor of scope end.
87      // https://drafts.csswg.org/css-nesting/#nesting-at-scope
88      if let Some(scope_start) = &self.scope_start {
89        dest.with_context(scope_start, |dest| scope_end.to_css(dest))?;
90      } else {
91        scope_end.to_css(dest)?;
92      }
93      dest.write_char(')')?;
94      dest.whitespace()?;
95    }
96    dest.write_char('{')?;
97    dest.indent();
98    dest.newline()?;
99    // Nested style rules within @scope are implicitly relative to the <scope-start>
100    // so clear our style context while printing them to avoid replacing & ourselves.
101    // https://drafts.csswg.org/css-cascade-6/#scoped-rules
102    dest.with_cleared_context(|dest| self.rules.to_css(dest))?;
103    dest.dedent();
104    dest.newline()?;
105    dest.write_char('}')
106  }
107}