Fix casing of dynamic tags (#2578)

* fix casing of dynamic tags
* add test case for unknown tag names
* add lint for non-normalized tags
This commit is contained in:
WorldSEnder 2022-04-05 19:07:31 +02:00 committed by GitHub
parent 4bc61b8da9
commit 8978baa45d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 28 deletions

View File

@ -4,6 +4,7 @@ use crate::stringify::{Stringify, Value};
use crate::{non_capitalized_ascii, Peek, PeekValue}; use crate::{non_capitalized_ascii, Peek, PeekValue};
use boolinator::Boolinator; use boolinator::Boolinator;
use proc_macro2::{Delimiter, TokenStream}; use proc_macro2::{Delimiter, TokenStream};
use proc_macro_error::emit_warning;
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor; use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
@ -295,9 +296,20 @@ impl ToTokens for HtmlElement {
}; };
tokens.extend(match &name { tokens.extend(match &name {
TagName::Lit(name) => { TagName::Lit(dashedname) => {
let name_span = name.span(); let name_span = dashedname.span();
let name = name.to_ascii_lowercase_string(); let name = dashedname.to_ascii_lowercase_string();
if name != dashedname.to_string() {
emit_warning!(
dashedname.span(),
format!(
"The tag '{0}' is not matching its normalized form '{1}'. If you want \
to keep this form, change this to a dynamic tag `@{{\"{0}\"}}`.",
dashedname,
name,
)
)
}
let node = match &*name { let node = match &*name {
"input" => { "input" => {
quote! { quote! {
@ -375,18 +387,15 @@ impl ToTokens for HtmlElement {
let mut #vtag_name = ::std::convert::Into::< let mut #vtag_name = ::std::convert::Into::<
::std::borrow::Cow::<'static, ::std::primitive::str> ::std::borrow::Cow::<'static, ::std::primitive::str>
>::into(#expr); >::into(#expr);
if !#vtag_name.is_ascii() { ::std::debug_assert!(
::std::panic!( #vtag_name.is_ascii(),
"a dynamic tag returned a tag name containing non ASCII characters: `{}`", "a dynamic tag returned a tag name containing non ASCII characters: `{}`",
#vtag_name, #vtag_name,
); );
}
// convert to lowercase because the runtime checks rely on it.
#vtag_name.to_mut().make_ascii_lowercase();
#[allow(clippy::redundant_clone, unused_braces, clippy::let_and_return)] #[allow(clippy::redundant_clone, unused_braces, clippy::let_and_return)]
let mut #vtag = match ::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name) { let mut #vtag = match () {
"input" => { _ if "input".eq_ignore_ascii_case(::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name)) => {
::yew::virtual_dom::VTag::__new_textarea( ::yew::virtual_dom::VTag::__new_textarea(
#value, #value,
#node_ref, #node_ref,
@ -395,7 +404,7 @@ impl ToTokens for HtmlElement {
#listeners, #listeners,
) )
} }
"textarea" => { _ if "textarea".eq_ignore_ascii_case(::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name)) => {
::yew::virtual_dom::VTag::__new_textarea( ::yew::virtual_dom::VTag::__new_textarea(
#value, #value,
#node_ref, #node_ref,
@ -429,17 +438,14 @@ impl ToTokens for HtmlElement {
// //
// check void element // check void element
if !#vtag.children().is_empty() { if !#vtag.children().is_empty() {
match #vtag.tag() { ::std::debug_assert!(
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" !::std::matches!(#vtag.tag().to_ascii_lowercase().as_str(),
| "link" | "meta" | "param" | "source" | "track" | "wbr" "area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input"
=> { | "link" | "meta" | "param" | "source" | "track" | "wbr"
::std::panic!( ),
"a dynamic tag tried to create a `<{0}>` tag with children. `<{0}>` is a void element which can't have any children.", "a dynamic tag tried to create a `<{0}>` tag with children. `<{0}>` is a void element which can't have any children.",
#vtag.tag(), #vtag.tag(),
); );
}
_ => {}
}
} }
::std::convert::Into::<::yew::virtual_dom::VNode>::into(#vtag) ::std::convert::Into::<::yew::virtual_dom::VNode>::into(#vtag)

View File

@ -13,5 +13,8 @@ fn main() {
let bad_img = html! { let bad_img = html! {
<img src="img.jpeg"/> <img src="img.jpeg"/>
}; };
let misformed_tagname = html! {
<tExTAreA />
};
compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of lints"); compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of lints");
} }

View File

@ -22,8 +22,14 @@ warning: All `<img>` tags should have an `alt` attribute which provides a human-
14 | <img src="img.jpeg"/> 14 | <img src="img.jpeg"/>
| ^^^ | ^^^
error: This macro call exists to deliberately fail the compilation of the test so we can verify output of lints warning: The tag 'tExTAreA' is not matching its normalized form 'textarea'. If you want to keep this form, change this to a dynamic tag `@{"tExTAreA"}`.
--> tests/html_lints/fail.rs:16:5 --> tests/html_lints/fail.rs:17:10
| |
16 | compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of lints"); 17 | <tExTAreA />
| ^^^^^^^^
error: This macro call exists to deliberately fail the compilation of the test so we can verify output of lints
--> tests/html_lints/fail.rs:19:5
|
19 | compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of lints");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -845,9 +845,20 @@ mod tests {
<@{"tExTAREa"}/> <@{"tExTAREa"}/>
}; };
let vtag = assert_vtag_ref(&el); let vtag = assert_vtag_ref(&el);
// textarea is a special element, so it gets normalized
assert_eq!(vtag.tag(), "textarea"); assert_eq!(vtag.tag(), "textarea");
} }
#[test]
fn dynamic_tags_allow_custom_capitalization() {
let el = html! {
<@{"clipPath"}/>
};
let vtag = assert_vtag_ref(&el);
// no special treatment for elements not recognized e.g. clipPath
assert_eq!(vtag.tag(), "clipPath");
}
#[test] #[test]
fn reset_node_ref() { fn reset_node_ref() {
let (root, scope, parent) = setup_parent(); let (root, scope, parent) = setup_parent();