//! Properties related to CSS modules. use crate::dependencies::Location; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; use crate::traits::{Parse, ToCss}; use crate::values::ident::{CustomIdent, CustomIdentList}; use crate::values::string::CowArcStr; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; use smallvec::SmallVec; /// A value for the [composes](https://github.com/css-modules/css-modules/#dependencies) property from CSS modules. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(lightningcss_derive::IntoOwned))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct Composes<'i> { /// A list of class names to compose. #[cfg_attr(feature = "serde", serde(borrow))] pub names: CustomIdentList<'i>, /// Where the class names are composed from. pub from: Option>, /// The source location of the `composes` property. pub loc: Location, } /// Defines where the class names referenced in the `composes` property are located. /// /// See [Composes](Composes). #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(lightningcss_derive::IntoOwned))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", content = "value", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum Specifier<'i> { /// The referenced name is global. Global, /// The referenced name comes from the specified file. #[cfg_attr(feature = "serde", serde(borrow))] File(CowArcStr<'i>), /// The referenced name comes from a source index (used during bundling). SourceIndex(u32), } impl<'i> Parse<'i> for Composes<'i> { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let loc = input.current_source_location(); let mut names = SmallVec::new(); while let Ok(name) = input.try_parse(parse_one_ident) { names.push(name); } if names.is_empty() { return Err(input.new_custom_error(ParserError::InvalidDeclaration)); } let from = if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() { Some(Specifier::parse(input)?) } else { None }; Ok(Composes { names, from, loc: loc.into(), }) } } fn parse_one_ident<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result, ParseError<'i, ParserError<'i>>> { let name = CustomIdent::parse(input)?; if name.0.eq_ignore_ascii_case("from") { return Err(input.new_error_for_next_token()); } Ok(name) } impl ToCss for Composes<'_> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { let mut first = true; for name in &self.names { if first { first = false; } else { dest.write_char(' ')?; } name.to_css(dest)?; } if let Some(from) = &self.from { dest.write_str(" from ")?; from.to_css(dest)?; } Ok(()) } } impl<'i> Parse<'i> for Specifier<'i> { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { if let Ok(file) = input.try_parse(|input| input.expect_string_cloned()) { Ok(Specifier::File(file.into())) } else { input.expect_ident_matching("global")?; Ok(Specifier::Global) } } } impl<'i> ToCss for Specifier<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match self { Specifier::Global => dest.write_str("global")?, Specifier::File(file) => serialize_string(&file, dest)?, Specifier::SourceIndex(..) => {} } Ok(()) } }