diff --git a/Cargo.lock b/Cargo.lock index a0755cc49ce..933ae34a456 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5302,7 +5302,6 @@ dependencies = [ "rustversion", "syn 2.0.117", "trybuild", - "version_check", "yew", ] diff --git a/Cargo.toml b/Cargo.toml index 7364b8c8638..05b3cf7a5c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,6 @@ unexpected_cfgs = { level = "warn", check-cfg = [ "cfg(verbose_tests)", "cfg(yew_lints)", "cfg(nightly_yew)", - "cfg(yew_macro_nightly)", "cfg(wasm_bindgen_unstable_test_coverage)" ]} [workspace.dependencies] diff --git a/packages/yew-macro/Cargo.toml b/packages/yew-macro/Cargo.toml index 09e10d941bf..5ecb8c6946c 100644 --- a/packages/yew-macro/Cargo.toml +++ b/packages/yew-macro/Cargo.toml @@ -15,9 +15,6 @@ rust-version.workspace = true [lib] proc-macro = true -[build-dependencies] -version_check = "0.9" - [dependencies] proc-macro-error = "1" proc-macro2.workspace = true diff --git a/packages/yew-macro/build.rs b/packages/yew-macro/build.rs deleted file mode 100644 index b87a9b117ed..00000000000 --- a/packages/yew-macro/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - if version_check::is_feature_flaggable().unwrap_or(false) { - println!("cargo:rustc-cfg=yew_macro_nightly"); - } -} diff --git a/packages/yew-macro/src/html_tree/html_block.rs b/packages/yew-macro/src/html_tree/html_block.rs index dd63ff301b2..9bcf715528c 100644 --- a/packages/yew-macro/src/html_tree/html_block.rs +++ b/packages/yew-macro/src/html_tree/html_block.rs @@ -1,4 +1,4 @@ -use proc_macro2::Delimiter; +use proc_macro2::{Delimiter, TokenStream}; use quote::{ToTokens, quote, quote_spanned}; use syn::buffer::Cursor; use syn::parse::{Parse, ParseStream}; @@ -11,6 +11,7 @@ use crate::PeekValue; pub struct HtmlBlock { pub content: BlockContent, brace: token::Brace, + pub(super) deprecations: TokenStream, } pub enum BlockContent { @@ -28,32 +29,36 @@ impl Parse for HtmlBlock { fn parse(input: ParseStream) -> syn::Result { let content; let brace = braced!(content in input); + let mut deprecations = TokenStream::new(); let content = if HtmlIterable::peek(content.cursor()).is_some() { BlockContent::Iterable(Box::new(content.parse()?)) } else { let node: HtmlNode = content.parse()?; if let HtmlNode::Expression(ref expr) = node { - check_deprecated_html_call(expr); + deprecations = check_deprecated_html_call(expr); } BlockContent::Node(Box::new(node)) }; - Ok(HtmlBlock { content, brace }) + Ok(HtmlBlock { + content, + brace, + deprecations, + }) } } /// Check for deprecated `html!` usage patterns inside expression blocks. -fn check_deprecated_html_call(expr: &Expr) { +fn check_deprecated_html_call(expr: &Expr) -> TokenStream { // Pattern 1: { match expr { arm => html! { ... }, ... } } if let Expr::Match(match_expr) = expr { for arm in &match_expr.arms { if let Some(span) = html_macro_call_span(&arm.body) { - super::emit_deprecated!( + return super::deprecated_call( span, "Use bare elements in arms directly \n\nmatch value {\n pattern => \ - ,\n}" + ,\n}", ); - return; } } } @@ -66,13 +71,46 @@ fn check_deprecated_html_call(expr: &Expr) { .last() .and_then(stmt_tail_html_macro_span) { - super::emit_deprecated!( + return super::deprecated_call( span, "`html!` is not needed inside expression blocks. Use `let` bindings and bare \ - elements directly" + elements directly", ); } } + + // Pattern 3: { if cond { html! { ... } } else { html! { ... } } } + if let Expr::If(if_expr) = expr { + if let Some(span) = if_branch_html_macro_span(if_expr) { + return super::deprecated_call( + span, + "`html!` is not needed inside `if`/`else` branches. Use bare elements directly", + ); + } + } + + TokenStream::new() +} + +/// Walk through an `if`/`else if`/`else` chain and return the span of the first tail `html!` call. +fn if_branch_html_macro_span(if_expr: &syn::ExprIf) -> Option { + if let Some(span) = if_expr + .then_branch + .stmts + .last() + .and_then(stmt_tail_html_macro_span) + { + return Some(span); + } + match if_expr.else_branch.as_ref().map(|(_, expr)| expr.as_ref()) { + Some(Expr::Block(block_expr)) => block_expr + .block + .stmts + .last() + .and_then(stmt_tail_html_macro_span), + Some(Expr::If(nested)) => if_branch_html_macro_span(nested), + _ => None, + } } /// Check if a statement is a tail `html!`/`html_nested!` macro call (no trailing semicolon). @@ -101,25 +139,41 @@ fn macro_path_html_span(path: &syn::Path) -> Option { impl ToTokens for HtmlBlock { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let HtmlBlock { content, .. } = self; + let HtmlBlock { + content, + deprecations, + .. + } = self; let new_tokens = match content { BlockContent::Iterable(html_iterable) => quote! {#html_iterable}, BlockContent::Node(html_node) => quote! {#html_node}, }; - tokens.extend(quote! {#new_tokens}); + if deprecations.is_empty() { + tokens.extend(new_tokens); + } else { + tokens.extend(quote! { { #deprecations #new_tokens } }); + } } } impl ToNodeIterator for HtmlBlock { fn to_node_iterator_stream(&self) -> Option { - let HtmlBlock { content, brace } = self; + let HtmlBlock { + content, + brace, + deprecations, + } = self; let new_tokens = match content { BlockContent::Iterable(iterable) => iterable.to_node_iterator_stream(), BlockContent::Node(node) => node.to_node_iterator_stream(), }?; - Some(quote_spanned! {brace.span=> #new_tokens}) + if deprecations.is_empty() { + Some(quote_spanned! {brace.span=> #new_tokens}) + } else { + Some(quote_spanned! {brace.span=> { #deprecations #new_tokens }}) + } } fn is_singular(&self) -> bool { diff --git a/packages/yew-macro/src/html_tree/html_for.rs b/packages/yew-macro/src/html_tree/html_for.rs index a04de7de6bc..006b1204f8e 100644 --- a/packages/yew-macro/src/html_tree/html_for.rs +++ b/packages/yew-macro/src/html_tree/html_for.rs @@ -24,6 +24,7 @@ pub struct HtmlFor { iter: Expr, let_stmts: Vec, body: HtmlChildrenTree, + deprecations: TokenStream, } impl PeekValue<()> for HtmlFor { @@ -53,7 +54,7 @@ impl Parse for HtmlFor { } let body = HtmlChildrenTree::parse_delimited_with_nodes(&body_stream)?; - super::check_unnecessary_fragment(&body); + let deprecations = super::check_unnecessary_fragment(&body); // TODO: more concise code by using if-let guards (MSRV 1.95) for child in body.0.iter() { let HtmlTree::Element(element) = child else { @@ -77,6 +78,7 @@ impl Parse for HtmlFor { iter, let_stmts, body, + deprecations, }) } } @@ -88,6 +90,7 @@ impl ToTokens for HtmlFor { iter, let_stmts, body, + deprecations, } = self; let acc = Ident::new("__yew_v", iter.span()); @@ -129,6 +132,7 @@ impl ToTokens for HtmlFor { }); tokens.extend(quote!({ + #deprecations let mut #acc = ::std::vec::Vec::<::yew::virtual_dom::VNode>::new(); ::std::iter::Iterator::for_each( ::std::iter::IntoIterator::into_iter(#iter), diff --git a/packages/yew-macro/src/html_tree/html_match.rs b/packages/yew-macro/src/html_tree/html_match.rs index ce7e3a7242e..c3bda8083c1 100644 --- a/packages/yew-macro/src/html_tree/html_match.rs +++ b/packages/yew-macro/src/html_tree/html_match.rs @@ -30,8 +30,12 @@ enum HtmlMatchArmBody { brace: token::Brace, let_stmts: Vec, children: HtmlChildrenTree, + deprecations: TokenStream, + }, + Unbraced { + tree: Box, + deprecations: TokenStream, }, - Unbraced(Box), } impl PeekValue<()> for HtmlMatch { @@ -100,7 +104,7 @@ impl Parse for HtmlMatchArm { let fat_arrow_token: Token![=>] = input.parse()?; - let body = if input.cursor().group(Delimiter::Brace).is_some() { + let mut body = if input.cursor().group(Delimiter::Brace).is_some() { let content; let brace = braced!(content in input); let mut let_stmts = Vec::new(); @@ -112,17 +116,31 @@ impl Parse for HtmlMatchArm { } } let children = HtmlChildrenTree::parse_delimited_with_nodes(&content)?; - super::check_unnecessary_fragment(&children); + let deprecations = super::check_unnecessary_fragment(&children); HtmlMatchArmBody::Braced { brace, let_stmts, children, + deprecations, } } else { - HtmlMatchArmBody::Unbraced(Box::new(super::HtmlTree::parse_or_node(input)?)) + HtmlMatchArmBody::Unbraced { + tree: Box::new(super::HtmlTree::parse_or_node(input)?), + deprecations: TokenStream::new(), + } }; - check_arm_html_macro_call(&body); + let arm_deprecations = check_arm_html_macro_call(&body); + if !arm_deprecations.is_empty() { + match &mut body { + HtmlMatchArmBody::Braced { deprecations, .. } => { + deprecations.extend(arm_deprecations) + } + HtmlMatchArmBody::Unbraced { deprecations, .. } => { + deprecations.extend(arm_deprecations); + } + } + } let comma: Option = input.parse()?; @@ -143,9 +161,11 @@ impl ToTokens for HtmlMatchArmBody { brace, let_stmts, children, + deprecations, } => { tokens.extend(quote_spanned! {brace.span.span()=> { + #deprecations #(#let_stmts)* ::yew::virtual_dom::VNode::VList(::std::rc::Rc::new( ::yew::virtual_dom::VList::with_children( @@ -155,9 +175,12 @@ impl ToTokens for HtmlMatchArmBody { } }); } - Self::Unbraced(tree) => { + Self::Unbraced { tree, deprecations } => { tokens.extend(quote_spanned! {tree.span()=> - { ::std::convert::Into::<::yew::virtual_dom::VNode>::into(#tree) } + { + #deprecations + ::std::convert::Into::<::yew::virtual_dom::VNode>::into(#tree) + } }); } } @@ -197,22 +220,22 @@ impl ToTokens for HtmlMatch { } } -fn check_arm_html_macro_call(body: &HtmlMatchArmBody) { +fn check_arm_html_macro_call(body: &HtmlMatchArmBody) -> TokenStream { let trees: Box + '_> = match body { HtmlMatchArmBody::Braced { children, .. } => Box::new(children.0.iter()), - HtmlMatchArmBody::Unbraced(tree) => Box::new(std::iter::once(tree.as_ref())), + HtmlMatchArmBody::Unbraced { tree, .. } => Box::new(std::iter::once(tree.as_ref())), }; for tree in trees { if let super::HtmlTree::Node(node) = tree { if let HtmlNode::Expression(expr) = node.as_ref() { if let Some(span) = html_macro_call_span(expr) { - super::emit_deprecated!( + return super::deprecated_call( span, - "`html!` is not needed in `match` arms. Use bare elements directly" + "`html!` is not needed in `match` arms. Use bare elements directly", ); - return; } } } } + TokenStream::new() } diff --git a/packages/yew-macro/src/html_tree/mod.rs b/packages/yew-macro/src/html_tree/mod.rs index 0e08d94cbd8..2000c1e428d 100644 --- a/packages/yew-macro/src/html_tree/mod.rs +++ b/packages/yew-macro/src/html_tree/mod.rs @@ -1,12 +1,3 @@ -macro_rules! emit_deprecated { - ($($tt:tt)*) => {{ - #[cfg(yew_macro_nightly)] - proc_macro_error::emit_warning!($($tt)*); - #[cfg(not(yew_macro_nightly))] - proc_macro_error::emit_error!($($tt)*); - }}; -} -pub(crate) use emit_deprecated; use proc_macro2::{Delimiter, Ident, Span, TokenStream}; use quote::{ToTokens, quote, quote_spanned}; use syn::buffer::Cursor; @@ -169,23 +160,34 @@ impl ToTokens for HtmlTree { pub struct HtmlRoot { children: HtmlChildrenTree, + deprecations: TokenStream, } impl Parse for HtmlRoot { fn parse(input: ParseStream) -> syn::Result { let children = HtmlChildrenTree::parse_delimited_with_nodes(input)?; - check_unnecessary_fragment(&children); - Ok(Self { children }) + let deprecations = check_unnecessary_fragment(&children); + Ok(Self { + children, + deprecations, + }) } } impl ToTokens for HtmlRoot { fn to_tokens(&self, tokens: &mut TokenStream) { + let deprecations = &self.deprecations; match &self.children.0[..] { [] => tokens.extend(quote! { - <::yew::virtual_dom::VNode as ::std::default::Default>::default() + { #deprecations <::yew::virtual_dom::VNode as ::std::default::Default>::default() } }), - [single] => single.to_tokens(tokens), + [single] => { + if deprecations.is_empty() { + single.to_tokens(tokens); + } else { + tokens.extend(quote! { { #deprecations #single } }); + } + } _ => { let children = &self.children; let vlist = match children.fully_keyed() { @@ -200,7 +202,10 @@ impl ToTokens for HtmlRoot { }, }; tokens.extend(quote! { - ::yew::virtual_dom::VNode::VList(::std::rc::Rc::new(#vlist)) + { + #deprecations + ::yew::virtual_dom::VNode::VList(::std::rc::Rc::new(#vlist)) + } }); } } @@ -345,10 +350,15 @@ impl HtmlChildrenTree { // Or further nested once deref pattern (https://github.com/rust-lang/rust/issues/87121) is stable. if let HtmlBlock { content: BlockContent::Node(children), + deprecations, .. } = m.as_ref() { - Some(quote! { #children }) + if deprecations.is_empty() { + Some(quote! { #children }) + } else { + Some(quote! { { #deprecations #children } }) + } } else { Some(quote! { ::yew::html::ChildrenRenderer::new(#self) }) } @@ -372,10 +382,19 @@ impl HtmlChildrenTree { // Or further nested once deref pattern (https://github.com/rust-lang/rust/issues/87121) is stable. if let HtmlBlock { content: BlockContent::Node(children), + deprecations, .. } = m.as_ref() { - quote! { ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(#children) } + if deprecations.is_empty() { + quote! { ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value(#children) } + } else { + quote! { + ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value( + { #deprecations #children } + ) + } + } } else { quote! { ::yew::html::IntoPropValue::<::yew::virtual_dom::VNode>::into_prop_value( @@ -447,6 +466,7 @@ pub struct HtmlRootBraced { brace: token::Brace, let_stmts: Vec, children: HtmlChildrenTree, + deprecations: TokenStream, } impl PeekValue<()> for HtmlRootBraced { @@ -470,27 +490,40 @@ impl Parse for HtmlRootBraced { } let children = HtmlChildrenTree::parse_delimited_with_nodes(&content)?; - check_unnecessary_fragment(&children); + let deprecations = check_unnecessary_fragment(&children); Ok(HtmlRootBraced { brace, let_stmts, children, + deprecations, }) } } +pub(super) fn deprecated_call(span: Span, note: &str) -> TokenStream { + quote_spanned! {span=> + { + #[deprecated = #note] + fn __yew_deprecated() {} + __yew_deprecated(); + } + } +} + /// Lint when a braced body contains a single keyless fragment, since the children /// can be placed directly in the body without the `<>...` wrapper. -pub(super) fn check_unnecessary_fragment(children: &HtmlChildrenTree) { - if let [HtmlTree::List(list)] = &children.0[..] { - if list.open.props.key.is_none() { - emit_deprecated!( - list.open_spanned(), - "unnecessary `<>...`. Children can be placed directly in the body" - ); - } +pub(super) fn check_unnecessary_fragment(children: &HtmlChildrenTree) -> TokenStream { + let [HtmlTree::List(list)] = &children.0[..] else { + return TokenStream::new(); + }; + if list.open.props.key.is_some() { + return TokenStream::new(); } + deprecated_call( + list.open_spanned().to_token_stream().span(), + "unnecessary `<>...`. Children can be placed directly in the body", + ) } impl ToTokens for HtmlRootBraced { @@ -499,10 +532,12 @@ impl ToTokens for HtmlRootBraced { brace, let_stmts, children, + deprecations, } = self; tokens.extend(quote_spanned! {brace.span.span()=> { + #deprecations #(#let_stmts)* ::yew::virtual_dom::VNode::VList(::std::rc::Rc::new( ::yew::virtual_dom::VList::with_children(#children, ::std::option::Option::None) diff --git a/packages/yew-macro/tests/html_deprecation/fail.rs b/packages/yew-macro/tests/html_deprecation/fail.rs new file mode 100644 index 00000000000..88871e3c16e --- /dev/null +++ b/packages/yew-macro/tests/html_deprecation/fail.rs @@ -0,0 +1,93 @@ +use yew::prelude::*; + +fn root_level_fragment() { + let _ = html! { <> }; + let _ = html! { <>
}; + let _ = html! { + <> + { "a" } + { "b" } + + }; +} + +fn root_level_fragment_with_keyed_children() { + let _ = html! { + <> +

{"Menu Header"}

+ + }; +} + +fn fragment_in_for_body() { + let _ = html! { + for _ in 0..3 { + <>{"a"}{"b"} + } + }; +} + +fn fragment_in_if_body() { + let _ = html! { if true { <>
} }; +} + +fn fragment_in_else_body() { + let _ = html! { if true {
} else { <> } }; +} + +fn fragment_in_match_arm() { + let _ = html! { + match 1 { + 1 => { <>

{"Hello"}

{"World"}

} + _ =>

{"Goodbye"}

, + } + }; +} + +fn html_in_unbraced_match_arm() { + let _ = html! { + match 1 { + 1 => html! {

{"Hello"}

}, + _ =>

{"Goodbye"}

, + } + }; +} + +fn html_in_braced_match_arm() { + let _ = html! { + match 1 { + 1 => { let a = 1; html! {

{a}

} }, + _ =>

{"Goodbye"}

, + } + }; +} + +fn match_in_block_with_html_arms() { + let status: u8 = 0; + let _ = html! { +
{ + match status { + 0 => html! { {"loading"} }, + _ => html! { {"done"} }, + } + }
+ }; +} + +fn nested_block_with_html_tail() { + let item = "test"; + let _ = html! { +
{{ let processed = item.to_uppercase(); html! { {processed} } }}
+ }; +} + +fn if_else_block_with_html_branches() { + let cond = true; + let _ = html! { +
{ if cond { html! { {"yes"} } } else { html! { {"no"} } } }
+ }; +} + +fn main() { + compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of deprecation lints"); +} diff --git a/packages/yew-macro/tests/html_deprecation/fail.stderr b/packages/yew-macro/tests/html_deprecation/fail.stderr new file mode 100644 index 00000000000..bb252135d93 --- /dev/null +++ b/packages/yew-macro/tests/html_deprecation/fail.stderr @@ -0,0 +1,89 @@ +error: This macro call exists to deliberately fail the compilation of the test so we can verify output of deprecation lints + --> tests/html_deprecation/fail.rs:92:5 + | +92 | compile_error!("This macro call exists to deliberately fail the compilation of the test so we can verify output of deprecation lints... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: use of deprecated function `root_level_fragment::__yew_deprecated`: unnecessary `<>...`. Children can be placed directly in the body + --> tests/html_deprecation/fail.rs:4:21 + | +4 | let _ = html! { <> }; + | ^ + | + = note: `#[warn(deprecated)]` on by default + +warning: use of deprecated function `root_level_fragment::__yew_deprecated`: unnecessary `<>...`. Children can be placed directly in the body + --> tests/html_deprecation/fail.rs:5:21 + | +5 | let _ = html! { <>
}; + | ^ + +warning: use of deprecated function `root_level_fragment::__yew_deprecated`: unnecessary `<>...`. Children can be placed directly in the body + --> tests/html_deprecation/fail.rs:7:9 + | +7 | <> + | ^ + +warning: use of deprecated function `root_level_fragment_with_keyed_children::__yew_deprecated`: unnecessary `<>...`. Children can be placed directly in the body + --> tests/html_deprecation/fail.rs:16:9 + | +16 | <> + | ^ + +warning: use of deprecated function `fragment_in_for_body::__yew_deprecated`: unnecessary `<>...`. Children can be placed directly in the body + --> tests/html_deprecation/fail.rs:25:13 + | +25 | <>{"a"}{"b"} + | ^ + +warning: use of deprecated function `fragment_in_if_body::__yew_deprecated`: unnecessary `<>...`. Children can be placed directly in the body + --> tests/html_deprecation/fail.rs:31:31 + | +31 | let _ = html! { if true { <>
} }; + | ^ + +warning: use of deprecated function `fragment_in_else_body::__yew_deprecated`: unnecessary `<>...`. Children can be placed directly in the body + --> tests/html_deprecation/fail.rs:35:47 + | +35 | let _ = html! { if true {
} else { <> } }; + | ^ + +warning: use of deprecated function `fragment_in_match_arm::__yew_deprecated`: unnecessary `<>...`. Children can be placed directly in the body + --> tests/html_deprecation/fail.rs:41:20 + | +41 | 1 => { <>

{"Hello"}

{"World"}

} + | ^ + +warning: use of deprecated function `html_in_unbraced_match_arm::__yew_deprecated`: `html!` is not needed in `match` arms. Use bare elements directly + --> tests/html_deprecation/fail.rs:50:18 + | +50 | 1 => html! {

{"Hello"}

}, + | ^^^^ + +warning: use of deprecated function `html_in_braced_match_arm::__yew_deprecated`: `html!` is not needed in `match` arms. Use bare elements directly + --> tests/html_deprecation/fail.rs:59:31 + | +59 | 1 => { let a = 1; html! {

{a}

} }, + | ^^^^ + +warning: use of deprecated function `match_in_block_with_html_arms::__yew_deprecated`: Use bare elements in arms directly + + match value { + pattern => , + } + --> tests/html_deprecation/fail.rs:70:22 + | +70 | 0 => html! { {"loading"} }, + | ^^^^ + +warning: use of deprecated function `nested_block_with_html_tail::__yew_deprecated`: `html!` is not needed inside expression blocks. Use `let` bindings and bare elements directly + --> tests/html_deprecation/fail.rs:80:54 + | +80 |
{{ let processed = item.to_uppercase(); html! { {processed} } }}
+ | ^^^^ + +warning: use of deprecated function `if_else_block_with_html_branches::__yew_deprecated`: `html!` is not needed inside `if`/`else` branches. Use bare elements directly + --> tests/html_deprecation/fail.rs:87:26 + | +87 |
{ if cond { html! { {"yes"} } } else { html! { {"no"} } } }
+ | ^^^^ diff --git a/packages/yew-macro/tests/html_deprecation/pass.rs b/packages/yew-macro/tests/html_deprecation/pass.rs new file mode 100644 index 00000000000..a1a2b753a89 --- /dev/null +++ b/packages/yew-macro/tests/html_deprecation/pass.rs @@ -0,0 +1,93 @@ +#![deny(deprecated)] + +use yew::prelude::*; + +#[allow(deprecated)] +fn silence_unnecessary_root_fragment() { + let _ = html! { <>
}; +} + +#[allow(deprecated)] +fn silence_unnecessary_if_fragment() { + let _ = html! { if true { <>
} }; +} + +#[allow(deprecated)] +fn silence_unnecessary_else_fragment() { + let _ = html! { if true {
} else { <> } }; +} + +#[allow(deprecated)] +fn silence_unnecessary_match_fragment() { + let _ = html! { + match 1 { + 1 => { <>

{"Hello"}

{"World"}

} + _ =>

{"Goodbye"}

, + } + }; +} + +#[allow(deprecated)] +fn silence_html_in_match_arm() { + let _ = html! { + match 1 { + 1 => html! {

{"Hello"}

}, + _ =>

{"Goodbye"}

, + } + }; +} + +#[allow(deprecated)] +fn silence_html_in_block_match() { + let status: u8 = 0; + let _ = html! { +
{ + match status { + 0 => html! { {"loading"} }, + _ => html! { {"done"} }, + } + }
+ }; +} + +#[allow(deprecated)] +fn silence_html_in_nested_block() { + let item = "test"; + let _ = html! { +
{{ let processed = item.to_uppercase(); html! { {processed} } }}
+ }; +} + +#[allow(deprecated)] +fn silence_keyed_children_fragment() { + let _ = html! { + if true { + <> +
+
+ + } + }; +} + +#[allow(deprecated)] +fn silence_for_loop_fragment() { + let _ = html! { + for _ in 0..3 { + <>{"a"}{"b"} + } + }; +} + +fn keyed_fragment_no_warning() { + let _ = html! { + if true { + +
+
+ + } + }; +} + +fn main() {} diff --git a/packages/yew-macro/tests/html_deprecation_test.rs b/packages/yew-macro/tests/html_deprecation_test.rs new file mode 100644 index 00000000000..f73fb7d0dec --- /dev/null +++ b/packages/yew-macro/tests/html_deprecation_test.rs @@ -0,0 +1,7 @@ +#[allow(dead_code)] +#[rustversion::attr(stable(1.85.0), test)] +fn test_html_deprecation() { + let t = trybuild::TestCases::new(); + t.pass("tests/html_deprecation/pass.rs"); + t.compile_fail("tests/html_deprecation/fail.rs"); +} diff --git a/packages/yew-macro/tests/html_macro/block-fail.rs b/packages/yew-macro/tests/html_macro/block-fail.rs index 458d92e1776..eccfb86c7e1 100644 --- a/packages/yew-macro/tests/html_macro/block-fail.rs +++ b/packages/yew-macro/tests/html_macro/block-fail.rs @@ -11,27 +11,4 @@ fn compile_fail() { }; } -fn deprecated_match_with_html() { - let status: u8 = 0; - - // Old pattern: match inside block with html! arms - html! { -
{ - match status { - 0 => html! { {"loading"} }, - _ => html! { {"done"} }, - } - }
- }; -} - -fn deprecated_block_with_html() { - let item = "test"; - - // Old pattern: nested block with html! as tail expression - html! { -
{{ let processed = item.to_uppercase(); html! { {processed} } }}
- }; -} - fn main() {} diff --git a/packages/yew-macro/tests/html_macro/block-fail.stderr b/packages/yew-macro/tests/html_macro/block-fail.stderr index 4b86390cfdb..dbaf466481d 100644 --- a/packages/yew-macro/tests/html_macro/block-fail.stderr +++ b/packages/yew-macro/tests/html_macro/block-fail.stderr @@ -1,19 +1,3 @@ -error: Use bare elements in arms directly - - match value { - pattern => , - } - --> tests/html_macro/block-fail.rs:21:22 - | -21 | 0 => html! { {"loading"} }, - | ^^^^ - -error: `html!` is not needed inside expression blocks. Use `let` bindings and bare elements directly - --> tests/html_macro/block-fail.rs:33:54 - | -33 |
{{ let processed = item.to_uppercase(); html! { {processed} } }}
- | ^^^^ - error[E0277]: the trait bound `(): Into` is not satisfied --> tests/html_macro/block-fail.rs:10:20 | diff --git a/packages/yew-macro/tests/html_macro/for-fail.rs b/packages/yew-macro/tests/html_macro/for-fail.rs index 44c2dd4efb0..e40eece597b 100644 --- a/packages/yew-macro/tests/html_macro/for-fail.rs +++ b/packages/yew-macro/tests/html_macro/for-fail.rs @@ -21,9 +21,4 @@ fn main() { _ = ::yew::html!{for _ in 0 .. 10 {
}}; - - // Unnecessary fragment in for body - _ = ::yew::html!{for _ in 0 .. 5 { - <>{"a"}{"b"} - }}; } diff --git a/packages/yew-macro/tests/html_macro/for-fail.stderr b/packages/yew-macro/tests/html_macro/for-fail.stderr index c4b9453d1d5..e0f8c8b943f 100644 --- a/packages/yew-macro/tests/html_macro/for-fail.stderr +++ b/packages/yew-macro/tests/html_macro/for-fail.stderr @@ -36,12 +36,6 @@ error: duplicate key for a node in a `for`-loop 22 |
| ^^^^ -error: unnecessary `<>...`. Children can be placed directly in the body - --> tests/html_macro/for-fail.rs:27:9 - | -27 | <>{"a"}{"b"} - | ^^ - error[E0267]: `break` inside of a closure --> tests/html_macro/for-fail.rs:14:16 | diff --git a/packages/yew-macro/tests/html_macro/html-if-fail.rs b/packages/yew-macro/tests/html_macro/html-if-fail.rs index 06faedf4c52..d1722636f73 100644 --- a/packages/yew-macro/tests/html_macro/html-if-fail.rs +++ b/packages/yew-macro/tests/html_macro/html-if-fail.rs @@ -9,12 +9,4 @@ fn compile_fail() { html! { if true {} else if true {} else }; } -fn unnecessary_fragment() { - // Fragment in if body - html! { if true { <>
} }; - - // Fragment in else body - html! { if true {
} else { <> } }; -} - fn main() {} diff --git a/packages/yew-macro/tests/html_macro/html-if-fail.stderr b/packages/yew-macro/tests/html_macro/html-if-fail.stderr index 7e7e087a52c..4d20890640e 100644 --- a/packages/yew-macro/tests/html_macro/html-if-fail.stderr +++ b/packages/yew-macro/tests/html_macro/html-if-fail.stderr @@ -28,18 +28,6 @@ error: expected block or `if` after `else` 9 | html! { if true {} else if true {} else }; | ^^^^ -error: unnecessary `<>...`. Children can be placed directly in the body - --> tests/html_macro/html-if-fail.rs:14:23 - | -14 | html! { if true { <>
} }; - | ^^ - -error: unnecessary `<>...`. Children can be placed directly in the body - --> tests/html_macro/html-if-fail.rs:17:39 - | -17 | html! { if true {
} else { <> } }; - | ^^ - error[E0308]: mismatched types --> tests/html_macro/html-if-fail.rs:5:16 | diff --git a/packages/yew-macro/tests/html_macro/html-match-fail.rs b/packages/yew-macro/tests/html_macro/html-match-fail.rs index d6f6b5a1981..f300d7e3f10 100644 --- a/packages/yew-macro/tests/html_macro/html-match-fail.rs +++ b/packages/yew-macro/tests/html_macro/html-match-fail.rs @@ -9,28 +9,4 @@ fn main() { // Empty match (no arms) html! { match 42 {} }; - - // html! in unbraced match arm - html! { - match 1 { - 1 => html! {

{"Hello"}

}, - _ =>

{"Goodbye"}

, - } - }; - - // html! in braced match arm with let binding - html! { - match 1 { - 1 => { let a = 1; html! {

{a}

} }, - _ =>

{"Goodbye"}

, - } - }; - - // Unnecessary fragment in braced match arm - html! { - match 1 { - 1 => { <>

{"Hello"}

{"World"}

} - _ =>

{"Goodbye"}

, - } - }; } diff --git a/packages/yew-macro/tests/html_macro/html-match-fail.stderr b/packages/yew-macro/tests/html_macro/html-match-fail.stderr index 3a13154b94b..ff8dda5d8a0 100644 --- a/packages/yew-macro/tests/html_macro/html-match-fail.stderr +++ b/packages/yew-macro/tests/html_macro/html-match-fail.stderr @@ -15,21 +15,3 @@ error: `match` expression must have at least one arm | 11 | html! { match 42 {} }; | ^^ - -error: `html!` is not needed in `match` arms. Use bare elements directly - --> tests/html_macro/html-match-fail.rs:16:18 - | -16 | 1 => html! {

{"Hello"}

}, - | ^^^^ - -error: `html!` is not needed in `match` arms. Use bare elements directly - --> tests/html_macro/html-match-fail.rs:24:31 - | -24 | 1 => { let a = 1; html! {

{a}

} }, - | ^^^^ - -error: unnecessary `<>...`. Children can be placed directly in the body - --> tests/html_macro/html-match-fail.rs:32:20 - | -32 | 1 => { <>

{"Hello"}

{"World"}

} - | ^^ diff --git a/packages/yew-macro/tests/html_macro/list-fail.rs b/packages/yew-macro/tests/html_macro/list-fail.rs index 1886c7f9aad..1cd4c8200d2 100644 --- a/packages/yew-macro/tests/html_macro/list-fail.rs +++ b/packages/yew-macro/tests/html_macro/list-fail.rs @@ -21,16 +21,6 @@ fn compile_fail() { html! { }; // invalid prop html! { }; - - // unnecessary root-level fragment - html! { <> }; - html! { <>
}; - html! { - <> - { "a" } - { "b" } - - }; } fn main() {} diff --git a/packages/yew-macro/tests/html_macro/list-fail.stderr b/packages/yew-macro/tests/html_macro/list-fail.stderr index a3936e5cd01..9c000e9275b 100644 --- a/packages/yew-macro/tests/html_macro/list-fail.stderr +++ b/packages/yew-macro/tests/html_macro/list-fail.stderr @@ -73,21 +73,3 @@ error: fragments only accept the `key` prop | 23 | html! { }; | ^^^^^^^^^ - -error: unnecessary `<>...`. Children can be placed directly in the body - --> tests/html_macro/list-fail.rs:26:13 - | -26 | html! { <> }; - | ^^ - -error: unnecessary `<>...`. Children can be placed directly in the body - --> tests/html_macro/list-fail.rs:27:13 - | -27 | html! { <>
}; - | ^^ - -error: unnecessary `<>...`. Children can be placed directly in the body - --> tests/html_macro/list-fail.rs:29:9 - | -29 | <> - | ^^