Skip to content

Commit

Permalink
Explicitly disallow dynamic attribute names (#464)
Browse files Browse the repository at this point in the history
They were already disallowed before, but this also removes them from the AST.
  • Loading branch information
lambda-fairy authored Jan 12, 2025
1 parent 1f76200 commit 65bf05f
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 39 deletions.
8 changes: 8 additions & 0 deletions maud/tests/warnings/dynamic-attribute-names.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use maud::html;

fn main() {
let name = "href";
html! {
a (name)="about:blank" {}
};
}
5 changes: 5 additions & 0 deletions maud/tests/warnings/dynamic-attribute-names.stderr
Original file line number Diff line number Diff line change
@@ -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" {}
| ^
44 changes: 21 additions & 23 deletions maud_macros/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,15 +335,15 @@ impl<E: ToTokens> ToTokens for Block<E> {
pub enum Attribute {
Class {
dot_token: Dot,
name: AttributeName,
name: HtmlNameOrMarkup,
toggler: Option<Toggler>,
},
Id {
pound_token: Pound,
name: AttributeName,
name: HtmlNameOrMarkup,
},
Named {
name: AttributeName,
name: HtmlName,
attr_type: AttributeType,
},
}
Expand Down Expand Up @@ -375,8 +375,12 @@ impl DiagnosticParse for Attribute {
name: input.diagnostic_parse(diagnostics)?,
})
} else {
let name = input.diagnostic_parse::<AttributeName>(diagnostics)?;
let name_display = name.to_string();
let name = input.diagnostic_parse::<HtmlName>(diagnostics)?;

if input.peek(Question) {
input.parse::<Question>()?;
}

let fork = input.fork();

let attr = Self::Named {
Expand All @@ -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]`"))
);
}

Expand Down Expand Up @@ -425,49 +429,43 @@ impl ToTokens for Attribute {
}

#[derive(Debug, Clone)]
pub enum AttributeName {
Normal(HtmlName),
pub enum HtmlNameOrMarkup {
HtmlName(HtmlName),
Markup(Markup<NoElement>),
}

impl DiagnosticParse for AttributeName {
impl DiagnosticParse for HtmlNameOrMarkup {
fn diagnostic_parse(
input: ParseStream,
diagnostics: &mut Vec<Diagnostic>,
) -> syn::Result<Self> {
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::<Question>()?;
}

name
}
}

impl Parse for AttributeName {
impl Parse for HtmlNameOrMarkup {
fn parse(input: ParseStream) -> syn::Result<Self> {
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),
}
}
Expand Down
32 changes: 16 additions & 16 deletions maud_macros/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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("\"");
Expand All @@ -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("\"");
Expand All @@ -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 }));
Expand All @@ -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 {
Expand All @@ -152,7 +152,7 @@ impl Generator {
if i > 0 {
build.push_str(" ");
}
self.attr_name(name, build);
self.name_or_markup(name, build);
}
}

Expand All @@ -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 }));
Expand All @@ -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("\"");
}

Expand Down Expand Up @@ -311,9 +311,9 @@ impl Generator {
fn split_attrs(
attrs: Vec<Attribute>,
) -> (
Vec<(AttributeName, Option<Expr>)>,
Option<AttributeName>,
Vec<(AttributeName, AttributeType)>,
Vec<(HtmlNameOrMarkup, Option<Expr>)>,
Option<HtmlNameOrMarkup>,
Vec<(HtmlName, AttributeType)>,
) {
let mut classes = vec![];
let mut id = None;
Expand Down

0 comments on commit 65bf05f

Please sign in to comment.