lightningcss/properties/
css_modules.rs

1//! Properties related to CSS modules.
2
3use crate::dependencies::Location;
4use crate::error::{ParserError, PrinterError};
5use crate::printer::Printer;
6use crate::traits::{Parse, ToCss};
7use crate::values::ident::{CustomIdent, CustomIdentList};
8use crate::values::string::CowArcStr;
9#[cfg(feature = "visitor")]
10use crate::visitor::Visit;
11use cssparser::*;
12use smallvec::SmallVec;
13
14/// A value for the [composes](https://github.com/css-modules/css-modules/#dependencies) property from CSS modules.
15#[derive(Debug, Clone, PartialEq)]
16#[cfg_attr(feature = "visitor", derive(Visit))]
17#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
20pub struct Composes<'i> {
21  /// A list of class names to compose.
22  #[cfg_attr(feature = "serde", serde(borrow))]
23  pub names: CustomIdentList<'i>,
24  /// Where the class names are composed from.
25  pub from: Option<Specifier<'i>>,
26  /// The source location of the `composes` property.
27  pub loc: Location,
28}
29
30/// Defines where the class names referenced in the `composes` property are located.
31///
32/// See [Composes](Composes).
33#[derive(Debug, Clone, PartialEq)]
34#[cfg_attr(feature = "visitor", derive(Visit))]
35#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
36#[cfg_attr(
37  feature = "serde",
38  derive(serde::Serialize, serde::Deserialize),
39  serde(tag = "type", content = "value", rename_all = "kebab-case")
40)]
41#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
42pub enum Specifier<'i> {
43  /// The referenced name is global.
44  Global,
45  /// The referenced name comes from the specified file.
46  #[cfg_attr(feature = "serde", serde(borrow))]
47  File(CowArcStr<'i>),
48  /// The referenced name comes from a source index (used during bundling).
49  SourceIndex(u32),
50}
51
52impl<'i> Parse<'i> for Composes<'i> {
53  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
54    let loc = input.current_source_location();
55    let mut names = SmallVec::new();
56    while let Ok(name) = input.try_parse(parse_one_ident) {
57      names.push(name);
58    }
59
60    if names.is_empty() {
61      return Err(input.new_custom_error(ParserError::InvalidDeclaration));
62    }
63
64    let from = if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() {
65      Some(Specifier::parse(input)?)
66    } else {
67      None
68    };
69
70    Ok(Composes {
71      names,
72      from,
73      loc: loc.into(),
74    })
75  }
76}
77
78fn parse_one_ident<'i, 't>(
79  input: &mut Parser<'i, 't>,
80) -> Result<CustomIdent<'i>, ParseError<'i, ParserError<'i>>> {
81  let name = CustomIdent::parse(input)?;
82  if name.0.eq_ignore_ascii_case("from") {
83    return Err(input.new_error_for_next_token());
84  }
85
86  Ok(name)
87}
88
89impl ToCss for Composes<'_> {
90  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
91  where
92    W: std::fmt::Write,
93  {
94    let mut first = true;
95    for name in &self.names {
96      if first {
97        first = false;
98      } else {
99        dest.write_char(' ')?;
100      }
101      name.to_css(dest)?;
102    }
103
104    if let Some(from) = &self.from {
105      dest.write_str(" from ")?;
106      from.to_css(dest)?;
107    }
108
109    Ok(())
110  }
111}
112
113impl<'i> Parse<'i> for Specifier<'i> {
114  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
115    if let Ok(file) = input.try_parse(|input| input.expect_string_cloned()) {
116      Ok(Specifier::File(file.into()))
117    } else {
118      input.expect_ident_matching("global")?;
119      Ok(Specifier::Global)
120    }
121  }
122}
123
124impl<'i> ToCss for Specifier<'i> {
125  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
126  where
127    W: std::fmt::Write,
128  {
129    match self {
130      Specifier::Global => dest.write_str("global")?,
131      Specifier::File(file) => serialize_string(&file, dest)?,
132      Specifier::SourceIndex(..) => {}
133    }
134    Ok(())
135  }
136}