Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicitly disallow dynamic attribute names #464

Merged
merged 3 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading