Skip to content

Commit 8978baa

Browse files
authored
Fix casing of dynamic tags (#2578)
* fix casing of dynamic tags * add test case for unknown tag names * add lint for non-normalized tags
1 parent 4bc61b8 commit 8978baa

File tree

4 files changed

+53
-27
lines changed

4 files changed

+53
-27
lines changed

packages/yew-macro/src/html_tree/html_element.rs

+31-25
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::stringify::{Stringify, Value};
44
use crate::{non_capitalized_ascii, Peek, PeekValue};
55
use boolinator::Boolinator;
66
use proc_macro2::{Delimiter, TokenStream};
7+
use proc_macro_error::emit_warning;
78
use quote::{quote, quote_spanned, ToTokens};
89
use syn::buffer::Cursor;
910
use syn::parse::{Parse, ParseStream};
@@ -295,9 +296,20 @@ impl ToTokens for HtmlElement {
295296
};
296297

297298
tokens.extend(match &name {
298-
TagName::Lit(name) => {
299-
let name_span = name.span();
300-
let name = name.to_ascii_lowercase_string();
299+
TagName::Lit(dashedname) => {
300+
let name_span = dashedname.span();
301+
let name = dashedname.to_ascii_lowercase_string();
302+
if name != dashedname.to_string() {
303+
emit_warning!(
304+
dashedname.span(),
305+
format!(
306+
"The tag '{0}' is not matching its normalized form '{1}'. If you want \
307+
to keep this form, change this to a dynamic tag `@{{\"{0}\"}}`.",
308+
dashedname,
309+
name,
310+
)
311+
)
312+
}
301313
let node = match &*name {
302314
"input" => {
303315
quote! {
@@ -375,18 +387,15 @@ impl ToTokens for HtmlElement {
375387
let mut #vtag_name = ::std::convert::Into::<
376388
::std::borrow::Cow::<'static, ::std::primitive::str>
377389
>::into(#expr);
378-
if !#vtag_name.is_ascii() {
379-
::std::panic!(
380-
"a dynamic tag returned a tag name containing non ASCII characters: `{}`",
381-
#vtag_name,
382-
);
383-
}
384-
// convert to lowercase because the runtime checks rely on it.
385-
#vtag_name.to_mut().make_ascii_lowercase();
390+
::std::debug_assert!(
391+
#vtag_name.is_ascii(),
392+
"a dynamic tag returned a tag name containing non ASCII characters: `{}`",
393+
#vtag_name,
394+
);
386395

387396
#[allow(clippy::redundant_clone, unused_braces, clippy::let_and_return)]
388-
let mut #vtag = match ::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name) {
389-
"input" => {
397+
let mut #vtag = match () {
398+
_ if "input".eq_ignore_ascii_case(::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name)) => {
390399
::yew::virtual_dom::VTag::__new_textarea(
391400
#value,
392401
#node_ref,
@@ -395,7 +404,7 @@ impl ToTokens for HtmlElement {
395404
#listeners,
396405
)
397406
}
398-
"textarea" => {
407+
_ if "textarea".eq_ignore_ascii_case(::std::convert::AsRef::<::std::primitive::str>::as_ref(&#vtag_name)) => {
399408
::yew::virtual_dom::VTag::__new_textarea(
400409
#value,
401410
#node_ref,
@@ -429,17 +438,14 @@ impl ToTokens for HtmlElement {
429438
//
430439
// check void element
431440
if !#vtag.children().is_empty() {
432-
match #vtag.tag() {
433-
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input"
434-
| "link" | "meta" | "param" | "source" | "track" | "wbr"
435-
=> {
436-
::std::panic!(
437-
"a dynamic tag tried to create a `<{0}>` tag with children. `<{0}>` is a void element which can't have any children.",
438-
#vtag.tag(),
439-
);
440-
}
441-
_ => {}
442-
}
441+
::std::debug_assert!(
442+
!::std::matches!(#vtag.tag().to_ascii_lowercase().as_str(),
443+
"area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input"
444+
| "link" | "meta" | "param" | "source" | "track" | "wbr"
445+
),
446+
"a dynamic tag tried to create a `<{0}>` tag with children. `<{0}>` is a void element which can't have any children.",
447+
#vtag.tag(),
448+
);
443449
}
444450

445451
::std::convert::Into::<::yew::virtual_dom::VNode>::into(#vtag)

packages/yew-macro/tests/html_lints/fail.rs

+3
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ fn main() {
1313
let bad_img = html! {
1414
<img src="img.jpeg"/>
1515
};
16+
let misformed_tagname = html! {
17+
<tExTAreA />
18+
};
1619
compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of lints");
1720
}

packages/yew-macro/tests/html_lints/fail.stderr

+8-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ warning: All `<img>` tags should have an `alt` attribute which provides a human-
2222
14 | <img src="img.jpeg"/>
2323
| ^^^
2424

25+
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"}`.
26+
--> tests/html_lints/fail.rs:17:10
27+
|
28+
17 | <tExTAreA />
29+
| ^^^^^^^^
30+
2531
error: This macro call exists to deliberately fail the compilation of the test so we can verify output of lints
26-
--> tests/html_lints/fail.rs:16:5
32+
--> tests/html_lints/fail.rs:19:5
2733
|
28-
16 | compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of lints");
34+
19 | compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of lints");
2935
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

packages/yew/src/dom_bundle/btag/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -845,9 +845,20 @@ mod tests {
845845
<@{"tExTAREa"}/>
846846
};
847847
let vtag = assert_vtag_ref(&el);
848+
// textarea is a special element, so it gets normalized
848849
assert_eq!(vtag.tag(), "textarea");
849850
}
850851

852+
#[test]
853+
fn dynamic_tags_allow_custom_capitalization() {
854+
let el = html! {
855+
<@{"clipPath"}/>
856+
};
857+
let vtag = assert_vtag_ref(&el);
858+
// no special treatment for elements not recognized e.g. clipPath
859+
assert_eq!(vtag.tag(), "clipPath");
860+
}
861+
851862
#[test]
852863
fn reset_node_ref() {
853864
let (root, scope, parent) = setup_parent();

0 commit comments

Comments
 (0)