blob: f775a69af43c64084e0c77f1279cefa4ec2f34b0 [file] [log] [blame] [edit]
//! The `@property` rule.
use super::Location;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use crate::{
error::{ParserError, PrinterError},
printer::Printer,
properties::custom::TokenList,
traits::{Parse, ToCss},
values::{
ident::DashedIdent,
syntax::{ParsedComponent, SyntaxString},
},
};
use cssparser::*;
/// A [@property](https://drafts.css-houdini.org/css-properties-values-api/#at-property-rule) rule.
#[derive(Debug, PartialEq, Clone)]
#[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(rename_all = "camelCase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct PropertyRule<'i> {
/// The name of the custom property to declare.
#[cfg_attr(feature = "serde", serde(borrow))]
pub name: DashedIdent<'i>,
/// A syntax string to specify the grammar for the custom property.
#[cfg_attr(feature = "visitor", skip_visit)]
pub syntax: SyntaxString,
/// Whether the custom property is inherited.
#[cfg_attr(feature = "visitor", skip_visit)]
pub inherits: bool,
/// An optional initial value for the custom property.
#[cfg_attr(feature = "visitor", skip_visit)]
pub initial_value: Option<ParsedComponent<'i>>,
/// The location of the rule in the source file.
#[cfg_attr(feature = "visitor", skip_visit)]
pub loc: Location,
}
impl<'i> PropertyRule<'i> {
pub(crate) fn parse<'t>(
name: DashedIdent<'i>,
input: &mut Parser<'i, 't>,
loc: Location,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut parser = PropertyRuleDeclarationParser {
syntax: None,
inherits: None,
initial_value: None,
};
let mut decl_parser = RuleBodyParser::new(input, &mut parser);
while let Some(decl) = decl_parser.next() {
match decl {
Ok(()) => {}
Err((e, _)) => return Err(e),
}
}
// `syntax` and `inherits` are always required.
let parser = decl_parser.parser;
let syntax = parser
.syntax
.clone()
.ok_or(decl_parser.input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
let inherits = parser
.inherits
.clone()
.ok_or(decl_parser.input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
// `initial-value` is required unless the syntax is a universal definition.
let initial_value = match syntax {
SyntaxString::Universal => match parser.initial_value {
None => None,
Some(val) => {
let mut input = ParserInput::new(val);
let mut parser = Parser::new(&mut input);
if parser.is_exhausted() {
Some(ParsedComponent::TokenList(TokenList(vec![])))
} else {
Some(syntax.parse_value(&mut parser)?)
}
}
},
_ => {
let val = parser
.initial_value
.ok_or(input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
let mut input = ParserInput::new(val);
let mut parser = Parser::new(&mut input);
Some(syntax.parse_value(&mut parser)?)
}
};
return Ok(PropertyRule {
name,
syntax,
inherits,
initial_value,
loc,
});
}
}
impl<'i> ToCss for PropertyRule<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
#[cfg(feature = "sourcemap")]
dest.add_mapping(self.loc);
dest.write_str("@property ")?;
self.name.to_css(dest)?;
dest.whitespace()?;
dest.write_char('{')?;
dest.indent();
dest.newline()?;
dest.write_str("syntax:")?;
dest.whitespace()?;
self.syntax.to_css(dest)?;
dest.write_char(';')?;
dest.newline()?;
dest.write_str("inherits:")?;
dest.whitespace()?;
match self.inherits {
true => dest.write_str("true")?,
false => dest.write_str("false")?,
}
if let Some(initial_value) = &self.initial_value {
dest.write_char(';')?;
dest.newline()?;
dest.write_str("initial-value:")?;
dest.whitespace()?;
initial_value.to_css(dest)?;
if !dest.minify {
dest.write_char(';')?;
}
}
dest.dedent();
dest.newline()?;
dest.write_char('}')
}
}
pub(crate) struct PropertyRuleDeclarationParser<'i> {
syntax: Option<SyntaxString>,
inherits: Option<bool>,
initial_value: Option<&'i str>,
}
impl<'i> cssparser::DeclarationParser<'i> for PropertyRuleDeclarationParser<'i> {
type Declaration = ();
type Error = ParserError<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut cssparser::Parser<'i, 't>,
) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
match_ignore_ascii_case! { &name,
"syntax" => {
let syntax = SyntaxString::parse(input)?;
self.syntax = Some(syntax);
},
"inherits" => {
let location = input.current_source_location();
let ident = input.expect_ident()?;
let inherits = match_ignore_ascii_case! {&*ident,
"true" => true,
"false" => false,
_ => return Err(location.new_unexpected_token_error(
cssparser::Token::Ident(ident.clone())
))
};
self.inherits = Some(inherits);
},
"initial-value" => {
// Buffer the value into a string. We will parse it later.
let start = input.position();
while input.next().is_ok() {}
let initial_value = input.slice_from(start);
self.initial_value = Some(initial_value);
},
_ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))
}
return Ok(());
}
}
/// Default methods reject all at rules.
impl<'i> AtRuleParser<'i> for PropertyRuleDeclarationParser<'i> {
type Prelude = ();
type AtRule = ();
type Error = ParserError<'i>;
}
impl<'i> QualifiedRuleParser<'i> for PropertyRuleDeclarationParser<'i> {
type Prelude = ();
type QualifiedRule = ();
type Error = ParserError<'i>;
}
impl<'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PropertyRuleDeclarationParser<'i> {
fn parse_qualified(&self) -> bool {
false
}
fn parse_declarations(&self) -> bool {
true
}
}