Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
3 changes: 0 additions & 3 deletions packages/yew-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 0 additions & 5 deletions packages/yew-macro/build.rs

This file was deleted.

80 changes: 67 additions & 13 deletions packages/yew-macro/src/html_tree/html_block.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -11,6 +11,7 @@ use crate::PeekValue;
pub struct HtmlBlock {
pub content: BlockContent,
brace: token::Brace,
pub(super) deprecations: TokenStream,
}

pub enum BlockContent {
Expand All @@ -28,32 +29,36 @@ impl Parse for HtmlBlock {
fn parse(input: ParseStream) -> syn::Result<Self> {
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(
Comment thread
Madoshakalaka marked this conversation as resolved.
span,
"Use bare elements in arms directly \n\nmatch value {\n pattern => \
<Element />,\n}"
<Element />,\n}",
);
return;
}
}
}
Expand All @@ -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<proc_macro2::Span> {
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).
Expand Down Expand Up @@ -101,25 +139,41 @@ fn macro_path_html_span(path: &syn::Path) -> Option<proc_macro2::Span> {

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<proc_macro2::TokenStream> {
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 {
Expand Down
6 changes: 5 additions & 1 deletion packages/yew-macro/src/html_tree/html_for.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub struct HtmlFor {
iter: Expr,
let_stmts: Vec<Local>,
body: HtmlChildrenTree,
deprecations: TokenStream,
}

impl PeekValue<()> for HtmlFor {
Expand Down Expand Up @@ -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 {
Expand All @@ -77,6 +78,7 @@ impl Parse for HtmlFor {
iter,
let_stmts,
body,
deprecations,
})
}
}
Expand All @@ -88,6 +90,7 @@ impl ToTokens for HtmlFor {
iter,
let_stmts,
body,
deprecations,
} = self;
let acc = Ident::new("__yew_v", iter.span());

Expand Down Expand Up @@ -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),
Expand Down
47 changes: 35 additions & 12 deletions packages/yew-macro/src/html_tree/html_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ enum HtmlMatchArmBody {
brace: token::Brace,
let_stmts: Vec<Local>,
children: HtmlChildrenTree,
deprecations: TokenStream,
},
Unbraced {
tree: Box<super::HtmlTree>,
deprecations: TokenStream,
},
Unbraced(Box<super::HtmlTree>),
}

impl PeekValue<()> for HtmlMatch {
Expand Down Expand Up @@ -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();
Expand All @@ -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<Token![,]> = input.parse()?;

Expand All @@ -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(
Expand All @@ -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)
}
});
}
}
Expand Down Expand Up @@ -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<dyn Iterator<Item = &super::HtmlTree> + '_> = 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()
}
Loading
Loading