mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
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:
parent
4bc61b8da9
commit
8978baa45d
@ -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)
|
||||||
|
|||||||
@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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");
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user