blob: dc8046f3ecd289c7ab5698f9efa2dbeec4891000 [file] [log] [blame]
//! 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(static_self::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<Specifier<'i>>,
/// 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(static_self::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<Self, ParseError<'i, ParserError<'i>>> {
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<CustomIdent<'i>, 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<W>(&self, dest: &mut Printer<W>) -> 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<Self, ParseError<'i, ParserError<'i>>> {
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<W>(&self, dest: &mut Printer<W>) -> 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(())
}
}