From 65bf05f2a88ecad66477d48afba7bd9a3d0df412 Mon Sep 17 00:00:00 2001 From: Chris Wong Date: Sun, 12 Jan 2025 18:26:04 +1100 Subject: [PATCH] Explicitly disallow dynamic attribute names (#464) They were already disallowed before, but this also removes them from the AST. --- .../tests/warnings/dynamic-attribute-names.rs | 8 ++++ .../warnings/dynamic-attribute-names.stderr | 5 +++ maud_macros/src/ast.rs | 44 +++++++++---------- maud_macros/src/generate.rs | 32 +++++++------- 4 files changed, 50 insertions(+), 39 deletions(-) create mode 100644 maud/tests/warnings/dynamic-attribute-names.rs create mode 100644 maud/tests/warnings/dynamic-attribute-names.stderr diff --git a/maud/tests/warnings/dynamic-attribute-names.rs b/maud/tests/warnings/dynamic-attribute-names.rs new file mode 100644 index 00000000..59cd8c43 --- /dev/null +++ b/maud/tests/warnings/dynamic-attribute-names.rs @@ -0,0 +1,8 @@ +use maud::html; + +fn main() { + let name = "href"; + html! { + a (name)="about:blank" {} + }; +} diff --git a/maud/tests/warnings/dynamic-attribute-names.stderr b/maud/tests/warnings/dynamic-attribute-names.stderr new file mode 100644 index 00000000..7701a9fb --- /dev/null +++ b/maud/tests/warnings/dynamic-attribute-names.stderr @@ -0,0 +1,5 @@ +error: expected one of: identifier, literal, `.`, `#`, curly braces, `;` + --> tests/warnings/dynamic-attribute-names.rs:6:11 + | +6 | a (name)="about:blank" {} + | ^ diff --git a/maud_macros/src/ast.rs b/maud_macros/src/ast.rs index 2040ec3b..bc79a4f8 100644 --- a/maud_macros/src/ast.rs +++ b/maud_macros/src/ast.rs @@ -335,15 +335,15 @@ impl ToTokens for Block { pub enum Attribute { Class { dot_token: Dot, - name: AttributeName, + name: HtmlNameOrMarkup, toggler: Option, }, Id { pound_token: Pound, - name: AttributeName, + name: HtmlNameOrMarkup, }, Named { - name: AttributeName, + name: HtmlName, attr_type: AttributeType, }, } @@ -375,8 +375,12 @@ impl DiagnosticParse for Attribute { name: input.diagnostic_parse(diagnostics)?, }) } else { - let name = input.diagnostic_parse::(diagnostics)?; - let name_display = name.to_string(); + let name = input.diagnostic_parse::(diagnostics)?; + + if input.peek(Question) { + input.parse::()?; + } + let fork = input.fork(); let attr = Self::Named { @@ -388,8 +392,8 @@ impl DiagnosticParse for Attribute { diagnostics.push( attr.span() .error("attribute value must be a string") - .help(format!("to declare an empty attribute, omit the equals sign: `{name_display}`")) - .help(format!("to toggle the attribute, use square brackets: `{name_display}[some_boolean_flag]`")) + .help(format!("to declare an empty attribute, omit the equals sign: `{name}`")) + .help(format!("to toggle the attribute, use square brackets: `{name}[some_boolean_flag]`")) ); } @@ -425,49 +429,43 @@ impl ToTokens for Attribute { } #[derive(Debug, Clone)] -pub enum AttributeName { - Normal(HtmlName), +pub enum HtmlNameOrMarkup { + HtmlName(HtmlName), Markup(Markup), } -impl DiagnosticParse for AttributeName { +impl DiagnosticParse for HtmlNameOrMarkup { fn diagnostic_parse( input: ParseStream, diagnostics: &mut Vec, ) -> syn::Result { - let name = if input.peek(Ident::peek_any) || input.peek(Lit) { - input.diagnostic_parse(diagnostics).map(Self::Normal) + if input.peek(Ident::peek_any) || input.peek(Lit) { + input.diagnostic_parse(diagnostics).map(Self::HtmlName) } else { input.diagnostic_parse(diagnostics).map(Self::Markup) - }; - - if input.peek(Question) { - input.parse::()?; } - - name } } -impl Parse for AttributeName { +impl Parse for HtmlNameOrMarkup { fn parse(input: ParseStream) -> syn::Result { Self::diagnostic_parse(input, &mut Vec::new()) } } -impl ToTokens for AttributeName { +impl ToTokens for HtmlNameOrMarkup { fn to_tokens(&self, tokens: &mut TokenStream) { match self { - Self::Normal(name) => name.to_tokens(tokens), + Self::HtmlName(name) => name.to_tokens(tokens), Self::Markup(markup) => markup.to_tokens(tokens), } } } -impl Display for AttributeName { +impl Display for HtmlNameOrMarkup { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Normal(name) => name.fmt(f), + Self::HtmlName(name) => name.fmt(f), Self::Markup(markup) => markup.to_token_stream().fmt(f), } } diff --git a/maud_macros/src/generate.rs b/maud_macros/src/generate.rs index 303bda69..b24ffa17 100644 --- a/maud_macros/src/generate.rs +++ b/maud_macros/src/generate.rs @@ -87,18 +87,18 @@ impl Generator { build.push_escaped(&name.to_string()); } - fn attr_name(&self, name: AttributeName, build: &mut Builder) { + fn name_or_markup(&self, name: HtmlNameOrMarkup, build: &mut Builder) { match name { - AttributeName::Normal(name) => self.name(name, build), - AttributeName::Markup(markup) => self.markup(markup, build), + HtmlNameOrMarkup::HtmlName(name) => self.name(name, build), + HtmlNameOrMarkup::Markup(markup) => self.markup(markup, build), } } - fn attr(&self, name: AttributeName, value: AttributeType, build: &mut Builder) { + fn attr(&self, name: HtmlName, value: AttributeType, build: &mut Builder) { match value { AttributeType::Normal { value, .. } => { build.push_str(" "); - self.attr_name(name, build); + self.name(name, build); build.push_str("=\""); self.markup(value, build); build.push_str("\""); @@ -112,7 +112,7 @@ impl Generator { let body = { let mut build = self.builder(); build.push_str(" "); - self.attr_name(name, &mut build); + self.name(name, &mut build); build.push_str("=\""); self.splice(inner_value.clone(), &mut build); build.push_str("\""); @@ -122,13 +122,13 @@ impl Generator { } AttributeType::Empty(None) => { build.push_str(" "); - self.attr_name(name, build); + self.name(name, build); } AttributeType::Empty(Some(Toggler { cond, .. })) => { let body = { let mut build = self.builder(); build.push_str(" "); - self.attr_name(name, &mut build); + self.name(name, &mut build); build.finish() }; build.push_tokens(quote!(if (#cond) { #body })); @@ -143,7 +143,7 @@ impl Generator { let mut toggle_class_exprs = vec![]; build.push_str(" "); - self.attr_name(parse_quote!(class), build); + self.name(parse_quote!(class), build); build.push_str("=\""); for (i, (name, toggler)) in classes.into_iter().enumerate() { if let Some(toggler) = toggler { @@ -152,7 +152,7 @@ impl Generator { if i > 0 { build.push_str(" "); } - self.attr_name(name, build); + self.name_or_markup(name, build); } } @@ -162,7 +162,7 @@ impl Generator { if not_first { build.push_str(" "); } - self.attr_name(name, &mut build); + self.name_or_markup(name, &mut build); build.finish() }; build.push_tokens(quote!(if (#toggler) { #body })); @@ -173,9 +173,9 @@ impl Generator { if let Some(id) = id { build.push_str(" "); - self.attr_name(parse_quote!(id), build); + self.name(parse_quote!(id), build); build.push_str("=\""); - self.attr_name(id, build); + self.name_or_markup(id, build); build.push_str("\""); } @@ -311,9 +311,9 @@ impl Generator { fn split_attrs( attrs: Vec, ) -> ( - Vec<(AttributeName, Option)>, - Option, - Vec<(AttributeName, AttributeType)>, + Vec<(HtmlNameOrMarkup, Option)>, + Option, + Vec<(HtmlName, AttributeType)>, ) { let mut classes = vec![]; let mut id = None;