1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! The `@property` rule.

use super::Location;
use crate::{
  error::{ParserError, PrinterError},
  printer::Printer,
  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 = "serde", derive(serde::Serialize, serde::Deserialize))]
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.
  pub syntax: SyntaxString,
  /// Whether the custom property is inherited.
  pub inherits: bool,
  /// An optional initial value for the custom property.
  pub initial_value: Option<ParsedComponent<'i>>,
  /// The location of the rule in the source file.
  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 parser = PropertyRuleDeclarationParser {
      syntax: None,
      inherits: None,
      initial_value: None,
    };

    let mut decl_parser = DeclarationListParser::new(input, 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.ok_or(input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
    let inherits = parser.inherits.ok_or(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);
          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,
  {
    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>;
}