lightningcss/rules/
property.rs

1//! The `@property` rule.
2
3use super::Location;
4#[cfg(feature = "visitor")]
5use crate::visitor::Visit;
6use crate::{
7  error::{ParserError, PrinterError},
8  printer::Printer,
9  properties::custom::TokenList,
10  traits::{Parse, ToCss},
11  values::{
12    ident::DashedIdent,
13    syntax::{ParsedComponent, SyntaxString},
14  },
15};
16use cssparser::*;
17
18/// A [@property](https://drafts.css-houdini.org/css-properties-values-api/#at-property-rule) rule.
19#[derive(Debug, PartialEq, Clone)]
20#[cfg_attr(feature = "visitor", derive(Visit))]
21#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
22#[cfg_attr(
23  feature = "serde",
24  derive(serde::Serialize, serde::Deserialize),
25  serde(rename_all = "camelCase")
26)]
27#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
28pub struct PropertyRule<'i> {
29  /// The name of the custom property to declare.
30  #[cfg_attr(feature = "serde", serde(borrow))]
31  pub name: DashedIdent<'i>,
32  /// A syntax string to specify the grammar for the custom property.
33  #[cfg_attr(feature = "visitor", skip_visit)]
34  pub syntax: SyntaxString,
35  /// Whether the custom property is inherited.
36  #[cfg_attr(feature = "visitor", skip_visit)]
37  pub inherits: bool,
38  /// An optional initial value for the custom property.
39  #[cfg_attr(feature = "visitor", skip_visit)]
40  pub initial_value: Option<ParsedComponent<'i>>,
41  /// The location of the rule in the source file.
42  #[cfg_attr(feature = "visitor", skip_visit)]
43  pub loc: Location,
44}
45
46impl<'i> PropertyRule<'i> {
47  pub(crate) fn parse<'t>(
48    name: DashedIdent<'i>,
49    input: &mut Parser<'i, 't>,
50    loc: Location,
51  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
52    let mut parser = PropertyRuleDeclarationParser {
53      syntax: None,
54      inherits: None,
55      initial_value: None,
56    };
57
58    let mut decl_parser = RuleBodyParser::new(input, &mut parser);
59    while let Some(decl) = decl_parser.next() {
60      match decl {
61        Ok(()) => {}
62        Err((e, _)) => return Err(e),
63      }
64    }
65
66    // `syntax` and `inherits` are always required.
67    let parser = decl_parser.parser;
68    let syntax = parser
69      .syntax
70      .clone()
71      .ok_or(decl_parser.input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
72    let inherits = parser
73      .inherits
74      .clone()
75      .ok_or(decl_parser.input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
76
77    // `initial-value` is required unless the syntax is a universal definition.
78    let initial_value = match syntax {
79      SyntaxString::Universal => match parser.initial_value {
80        None => None,
81        Some(val) => {
82          let mut input = ParserInput::new(val);
83          let mut parser = Parser::new(&mut input);
84
85          if parser.is_exhausted() {
86            Some(ParsedComponent::TokenList(TokenList(vec![])))
87          } else {
88            Some(syntax.parse_value(&mut parser)?)
89          }
90        }
91      },
92      _ => {
93        let val = parser
94          .initial_value
95          .ok_or(input.new_custom_error(ParserError::AtRuleBodyInvalid))?;
96        let mut input = ParserInput::new(val);
97        let mut parser = Parser::new(&mut input);
98        Some(syntax.parse_value(&mut parser)?)
99      }
100    };
101
102    return Ok(PropertyRule {
103      name,
104      syntax,
105      inherits,
106      initial_value,
107      loc,
108    });
109  }
110}
111
112impl<'i> ToCss for PropertyRule<'i> {
113  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
114  where
115    W: std::fmt::Write,
116  {
117    #[cfg(feature = "sourcemap")]
118    dest.add_mapping(self.loc);
119    dest.write_str("@property ")?;
120    self.name.to_css(dest)?;
121    dest.whitespace()?;
122    dest.write_char('{')?;
123    dest.indent();
124    dest.newline()?;
125
126    dest.write_str("syntax:")?;
127    dest.whitespace()?;
128    self.syntax.to_css(dest)?;
129    dest.write_char(';')?;
130    dest.newline()?;
131
132    dest.write_str("inherits:")?;
133    dest.whitespace()?;
134    match self.inherits {
135      true => dest.write_str("true")?,
136      false => dest.write_str("false")?,
137    }
138
139    if let Some(initial_value) = &self.initial_value {
140      dest.write_char(';')?;
141      dest.newline()?;
142
143      dest.write_str("initial-value:")?;
144      dest.whitespace()?;
145      initial_value.to_css(dest)?;
146
147      if !dest.minify {
148        dest.write_char(';')?;
149      }
150    }
151
152    dest.dedent();
153    dest.newline()?;
154    dest.write_char('}')
155  }
156}
157
158pub(crate) struct PropertyRuleDeclarationParser<'i> {
159  syntax: Option<SyntaxString>,
160  inherits: Option<bool>,
161  initial_value: Option<&'i str>,
162}
163
164impl<'i> cssparser::DeclarationParser<'i> for PropertyRuleDeclarationParser<'i> {
165  type Declaration = ();
166  type Error = ParserError<'i>;
167
168  fn parse_value<'t>(
169    &mut self,
170    name: CowRcStr<'i>,
171    input: &mut cssparser::Parser<'i, 't>,
172  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
173    match_ignore_ascii_case! { &name,
174      "syntax" => {
175        let syntax = SyntaxString::parse(input)?;
176        self.syntax = Some(syntax);
177      },
178      "inherits" => {
179        let location = input.current_source_location();
180        let ident = input.expect_ident()?;
181        let inherits = match_ignore_ascii_case! {&*ident,
182          "true" => true,
183          "false" => false,
184          _ => return Err(location.new_unexpected_token_error(
185            cssparser::Token::Ident(ident.clone())
186          ))
187        };
188        self.inherits = Some(inherits);
189      },
190      "initial-value" => {
191        // Buffer the value into a string. We will parse it later.
192        let start = input.position();
193        while input.next().is_ok() {}
194        let initial_value = input.slice_from(start);
195        self.initial_value = Some(initial_value);
196      },
197      _ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))
198    }
199
200    return Ok(());
201  }
202}
203
204/// Default methods reject all at rules.
205impl<'i> AtRuleParser<'i> for PropertyRuleDeclarationParser<'i> {
206  type Prelude = ();
207  type AtRule = ();
208  type Error = ParserError<'i>;
209}
210
211impl<'i> QualifiedRuleParser<'i> for PropertyRuleDeclarationParser<'i> {
212  type Prelude = ();
213  type QualifiedRule = ();
214  type Error = ParserError<'i>;
215}
216
217impl<'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PropertyRuleDeclarationParser<'i> {
218  fn parse_qualified(&self) -> bool {
219    false
220  }
221
222  fn parse_declarations(&self) -> bool {
223    true
224  }
225}