Skip to content

Commit

Permalink
Fix casing of dynamic tags (#2578)
Browse files Browse the repository at this point in the history
* fix casing of dynamic tags
* add test case for unknown tag names
* add lint for non-normalized tags
  • Loading branch information
WorldSEnder committed Apr 5, 2022
1 parent 4bc61b8 commit 8978baa
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 27 deletions.
56 changes: 31 additions & 25 deletions packages/yew-macro/src/html_tree/html_element.rs
Expand Up @@ -4,6 +4,7 @@ use crate::stringify::{Stringify, Value};
use crate::{non_capitalized_ascii, Peek, PeekValue};
use boolinator::Boolinator;
use proc_macro2::{Delimiter, TokenStream};
use proc_macro_error::emit_warning;
use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream};
Expand Down Expand Up @@ -295,9 +296,20 @@ impl ToTokens for HtmlElement {
};

tokens.extend(match &name {
TagName::Lit(name) => {
let name_span = name.span();
let name = name.to_ascii_lowercase_string();
TagName::Lit(dashedname) => {
let name_span = dashedname.span();
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 {
"input" => {
quote! {
Expand Down Expand Up @@ -375,18 +387,15 @@ impl ToTokens for HtmlElement {
let mut #vtag_name = ::std::convert::Into::<
::std::borrow::Cow::<'static, ::std::primitive::str>
>::into(#expr);
if !#vtag_name.is_ascii() {
::std::panic!(
"a dynamic tag returned a tag name containing non ASCII characters: `{}`",
#vtag_name,
);
}
// convert to lowercase because the runtime checks rely on it.
#vtag_name.to_mut().make_ascii_lowercase();
::std::debug_assert!(
#vtag_name.is_ascii(),
"a dynamic tag returned a tag name containing non ASCII characters: `{}`",
#vtag_name,
);

#[allow(clippy::redundant_clone, unused_braces, clippy::let_and_return)]
let mut #vtag = match ::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name) {
"input" => {
let mut #vtag = match () {
_ if "input".eq_ignore_ascii_case(::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name)) => {
::yew::virtual_dom::VTag::__new_textarea(
#value,
#node_ref,
Expand All @@ -395,7 +404,7 @@ impl ToTokens for HtmlElement {
#listeners,
)
}
"textarea" => {
_ if "textarea".eq_ignore_ascii_case(::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name)) => {
::yew::virtual_dom::VTag::__new_textarea(
#value,
#node_ref,
Expand Down Expand Up @@ -429,17 +438,14 @@ impl ToTokens for HtmlElement {
//
// check void element
if !#vtag.children().is_empty() {
match #vtag.tag() {
"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.",
#vtag.tag(),
);
}
_ => {}
}
::std::debug_assert!(
!::std::matches!(#vtag.tag().to_ascii_lowercase().as_str(),
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input"
| "link" | "meta" | "param" | "source" | "track" | "wbr"
),
"a dynamic tag tried to create a `<{0}>` tag with children. `<{0}>` is a void element which can't have any children.",
#vtag.tag(),
);
}

::std::convert::Into::<::yew::virtual_dom::VNode>::into(#vtag)
Expand Down
3 changes: 3 additions & 0 deletions packages/yew-macro/tests/html_lints/fail.rs
Expand Up @@ -13,5 +13,8 @@ fn main() {
let bad_img = html! {
<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");
}
10 changes: 8 additions & 2 deletions packages/yew-macro/tests/html_lints/fail.stderr
Expand Up @@ -22,8 +22,14 @@ warning: All `<img>` tags should have an `alt` attribute which provides a human-
14 | <img src="img.jpeg"/>
| ^^^

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:17:10
|
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:16:5
--> tests/html_lints/fail.rs:19:5
|
16 | compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of lints");
19 | compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of lints");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 changes: 11 additions & 0 deletions packages/yew/src/dom_bundle/btag/mod.rs
Expand Up @@ -845,9 +845,20 @@ mod tests {
<@{"tExTAREa"}/>
};
let vtag = assert_vtag_ref(&el);
// textarea is a special element, so it gets normalized
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]
fn reset_node_ref() {
let (root, scope, parent) = setup_parent();
Expand Down

1 comment on commit 8978baa

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yew master branch benchmarks (Lower is better)

Benchmark suite Current: 8978baa Previous: 4bc61b8 Ratio
yew-struct-keyed 01_run1k 262.495 213.8005 1.23
yew-struct-keyed 02_replace1k 249.9845 235.4005 1.06
yew-struct-keyed 03_update10th1k_x16 410.8965 400.2185 1.03
yew-struct-keyed 04_select1k 80.51050000000001 72.283 1.11
yew-struct-keyed 05_swap1k 99.658 90.8485 1.10
yew-struct-keyed 06_remove-one-1k 31.9765 35.832499999999996 0.89
yew-struct-keyed 07_create10k 3219.188 3127.124 1.03
yew-struct-keyed 08_create1k-after1k_x2 638.591 569.808 1.12
yew-struct-keyed 09_clear1k_x8 228.1255 245.545 0.93
yew-struct-keyed 21_ready-memory 1.4005584716796875 1.4005584716796875 1
yew-struct-keyed 22_run-memory 1.6621971130371094 1.6623420715332031 1.00
yew-struct-keyed 23_update5-memory 1.6985511779785156 1.7012214660644531 1.00
yew-struct-keyed 24_run5-memory 1.7103385925292969 1.7113533020019531 1.00
yew-struct-keyed 25_run-clear-memory 1.3472366333007812 1.3290328979492188 1.01
yew-struct-keyed 31_startup-ci 1734.8000000000002 1732.546 1.00
yew-struct-keyed 32_startup-bt 33.158 32.227999999999994 1.03
yew-struct-keyed 34_startup-totalbytes 330.548828125 330.548828125 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.