repo_description: An extremely fast CSS parser, transformer, bundler, and minifier written in Rust.
repo_topics: build-tool compiler css minifier parser rust
code: //! 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
    }
  }
  