Skip to content

Commit

Permalink
Explicitly disallow dynamic attribute names
Browse files Browse the repository at this point in the history
  • Loading branch information
lambda-fairy committed Jan 12, 2025
1 parent 1f76200 commit 271c1a4
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 31 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
16 changes: 8 additions & 8 deletions maud_macros/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ impl Generator {
build.push_escaped(&name.to_string());
}

fn attr_name(&self, name: AttributeName, build: &mut Builder) {
fn attr_name(&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: HtmlNameOrMarkup, value: AttributeType, build: &mut Builder) {
match value {
AttributeType::Normal { value, .. } => {
build.push_str(" ");
Expand Down Expand Up @@ -180,7 +180,7 @@ impl Generator {
}

for (name, attr_type) in named_attrs {
self.attr(name, attr_type, build);
self.attr(HtmlNameOrMarkup::HtmlName(name), attr_type, build);
}
}

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 271c1a4

Please sign in to comment.