Merge pull request #1810 from DioxusLabs/jk/disambiguate-exprs-in-rsx

Disambiguate if expressions in rsx by requiring curlies, allow shorthand component/element initialization
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2024-01-11 12:25:59 -08:00
commit f7bf156422
81 files changed
+868 -649

No files matched your search

+4
View File
@@ -182,6 +182,9 @@ impl Writer<'_> {
s.source.as_ref().unwrap().to_token_stream()
)?;
}
ContentField::Shorthand(e) => {
write!(self.out, "{}", e.to_token_stream())?;
}
ContentField::OnHandlerRaw(exp) => {
let out = prettyplease::unparse_expr(exp);
let mut lines = out.split('\n').peekable();
@@ -223,6 +226,7 @@ impl Writer<'_> {
.iter()
.map(|field| match &field.content {
ContentField::Formatted(s) => ifmt_to_string(s).len() ,
ContentField::Shorthand(e) => e.to_token_stream().to_string().len(),
ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
let formatted = prettyplease::unparse_expr(exp);
let len = if formatted.contains('\n') {
+3
View File
@@ -237,6 +237,9 @@ impl Writer<'_> {
ElementAttrValue::AttrLiteral(value) => {
write!(self.out, "{value}", value = ifmt_to_string(value))?;
}
ElementAttrValue::Shorthand(value) => {
write!(self.out, "{value}",)?;
}
ElementAttrValue::AttrExpr(value) => {
let out = prettyplease::unparse_expr(value);
let mut lines = out.split('\n').peekable();
+46 -4
View File
@@ -1,11 +1,11 @@
use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop};
use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop, IfChain};
use proc_macro2::{LineColumn, Span};
use quote::ToTokens;
use std::{
collections::{HashMap, VecDeque},
fmt::{Result, Write},
};
use syn::{spanned::Spanned, Expr, ExprIf};
use syn::{spanned::Spanned, Expr};
use crate::buffer::Buffer;
use crate::ifmt_to_string;
@@ -142,6 +142,7 @@ impl<'a> Writer<'a> {
}
ElementAttrValue::AttrLiteral(lit) => ifmt_to_string(lit).len(),
ElementAttrValue::AttrExpr(expr) => expr.span().line_length(),
ElementAttrValue::Shorthand(expr) => expr.span().line_length(),
ElementAttrValue::EventTokens(tokens) => {
let location = Location::new(tokens.span().start());
@@ -231,8 +232,49 @@ impl<'a> Writer<'a> {
Ok(())
}
fn write_if_chain(&mut self, ifchain: &ExprIf) -> std::fmt::Result {
self.write_raw_expr(ifchain.span())
fn write_if_chain(&mut self, ifchain: &IfChain) -> std::fmt::Result {
// Recurse in place by setting the next chain
let mut branch = Some(ifchain);
while let Some(chain) = branch {
let IfChain {
if_token,
cond,
then_branch,
else_if_branch,
else_branch,
} = chain;
write!(
self.out,
"{} {} {{",
if_token.to_token_stream(),
prettyplease::unparse_expr(cond)
)?;
self.write_body_indented(then_branch)?;
if let Some(else_if_branch) = else_if_branch {
// write the closing bracket and else
self.out.tabbed_line()?;
write!(self.out, "}} else ")?;
branch = Some(else_if_branch);
} else if let Some(else_branch) = else_branch {
self.out.tabbed_line()?;
write!(self.out, "}} else {{")?;
self.write_body_indented(else_branch)?;
branch = None;
} else {
branch = None;
}
}
self.out.tabbed_line()?;
write!(self.out, "}}")?;
Ok(())
}
}
@@ -8,17 +8,17 @@ rsx! {
"hello world"
// Comments
expr1,
{expr1},
// Comments
expr2,
{expr2},
// Comments
// Comments
// Comments
// Comments
// Comments
expr3,
{expr3},
div {
// todo some work in here
+4 -4
View File
@@ -18,7 +18,7 @@ rsx! {
to: "{to}",
span { class: "inline-block mr-3", icons::icon_0 {} }
span { "{name}" }
children.is_some().then(|| rsx! {
{children.is_some().then(|| rsx! {
span {
class: "inline-block ml-auto hover:bg-gray-500",
onclick: move |evt| {
@@ -27,9 +27,9 @@ rsx! {
},
icons::icon_8 {}
}
})
})}
}
div { class: "px-4", is_current.then(|| rsx!{ children }) }
div { class: "px-4", {is_current.then(|| rsx!{ children })} }
}
// No nesting
@@ -48,5 +48,5 @@ rsx! {
}
}
div { asdbascasdbasd, asbdasbdabsd, asbdabsdbasdbas }
div { "asdbascasdbasd", "asbdasbdabsd", {asbdabsdbasdbas} }
}
@@ -8,6 +8,20 @@ rsx! {
// Some ifchain
if a > 10 {
//
rsx! { div {} }
div {}
} else if a > 20 {
h1 {}
} else if a > 20 {
h1 {}
} else if a > 20 {
h1 {}
} else if a > 20 {
h1 {}
} else if a > 20 {
h1 {}
} else if a > 20 {
h1 {}
} else {
h3 {}
}
}
@@ -1,4 +1,4 @@
fn it_works() {
cx.render(rsx!(()))
cx.render(rsx!({()}))
}
+4 -4
View File
@@ -11,7 +11,7 @@ pub fn Explainer<'a>(
// pt-5 sm:pt-24 lg:pt-24
let mut right = rsx! {
div { class: "relative w-1/2", flasher }
div { class: "relative w-1/2", {flasher} }
};
let align = match invert {
@@ -24,7 +24,7 @@ pub fn Explainer<'a>(
h2 { class: "mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold",
"{title}"
}
content
{content}
}
};
@@ -34,8 +34,8 @@ pub fn Explainer<'a>(
cx.render(rsx! {
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
left,
right
{left},
{right}
}
})
}
@@ -6,7 +6,7 @@ rsx! {
section { class: "body-font overflow-hidden dark:bg-ideblack",
div { class: "container px-6 mx-auto",
div { class: "-my-8 divide-y-2 divide-gray-100",
POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })
{POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })}
}
}
}
+5 -5
View File
@@ -4,20 +4,20 @@ rsx! {
div {}
// hi
div { abcd, ball, s }
div { "abcd", "ball", "s" }
//
//
//
div { abcd, ball, s }
div { "abcd", "ball", "s" }
//
//
//
div {
abcd,
ball,
s,
"abcd"
"ball"
"s"
//
"asdasd"
@@ -1,7 +1,7 @@
fn it_works() {
cx.render(rsx! {
div {
span { "Description: ", package.description.as_deref().unwrap_or("❌❌❌❌ missing") }
span { "Description: ", {package.description.as_deref().unwrap_or("❌❌❌❌ missing")} }
}
})
}
@@ -1,8 +1,8 @@
fn ItWroks() {
cx.render(rsx! {
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
left,
right
{left},
{right}
}
})
}
@@ -1,5 +1,5 @@
fn ItWroks() {
cx.render(rsx! {
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
})
}
@@ -1,8 +1,8 @@
fn ItWroks() {
cx.render(rsx! {
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
left,
right
{left},
{right}
}
})
}
@@ -1,5 +1,5 @@
fn ItWroks() {
cx.render(rsx! {
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
})
}
+2 -2
View File
@@ -267,10 +267,10 @@ async fn test_auto_fmt() {
let test_rsx = r#"
//
rsx! {
div {}
}
//
//
+16 -1
View File
@@ -804,6 +804,15 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str {
}
}
impl IntoDynNode<'_> for &String {
fn into_dyn_node(self, cx: &ScopeState) -> DynamicNode {
DynamicNode::Text(VText {
value: cx.bump().alloc_str(self),
id: Default::default(),
})
}
}
impl IntoDynNode<'_> for String {
fn into_dyn_node(self, cx: &ScopeState) -> DynamicNode {
DynamicNode::Text(VText {
@@ -868,7 +877,7 @@ where
nodes.extend(self.into_iter().map(|node| node.into_vnode(cx)));
match nodes.into_bump_slice() {
children if children.is_empty() => DynamicNode::default(),
[] => DynamicNode::default(),
children => DynamicNode::Fragment(children),
}
}
@@ -898,6 +907,12 @@ impl<'a> IntoAttributeValue<'a> for String {
}
}
impl<'a> IntoAttributeValue<'a> for &String {
fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Text(cx.alloc_str(self))
}
}
impl<'a> IntoAttributeValue<'a> for f64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Float(self)
+4 -4
View File
@@ -117,7 +117,7 @@ impl ScopeContext {
}
let mut search_parent = self.parent_id;
match with_runtime(|runtime: &crate::runtime::Runtime| {
let cur_runtime = with_runtime(|runtime: &crate::runtime::Runtime| {
while let Some(parent_id) = search_parent {
let parent = runtime.get_context(parent_id).unwrap();
tracing::trace!(
@@ -135,9 +135,9 @@ impl ScopeContext {
search_parent = parent.parent_id;
}
None
})
.flatten()
{
});
match cur_runtime.flatten() {
Some(ctx) => Some(ctx),
None => {
tracing::trace!(
+1 -1
View File
@@ -78,7 +78,7 @@ use std::{
/// #[component]
/// fn Title<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
/// cx.render(rsx! {
/// div { id: "title", children }
/// div { id: "title", {children} }
/// })
/// }
/// ```
+4 -4
View File
@@ -84,7 +84,7 @@ fn create() {
fn create_list() {
let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! {
(0..3).map(|f| rsx!( div { "hello" } ))
{(0..3).map(|f| rsx!( div { "hello" } ))}
})
});
@@ -148,7 +148,7 @@ fn create_components() {
fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! {
h1 {}
div { &cx.props.children }
div { {&cx.props.children} }
p {}
})
}
@@ -163,10 +163,10 @@ fn anchors() {
let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! {
if true {
rsx!( div { "hello" } )
div { "hello" }
}
if false {
rsx!( div { "goodbye" } )
div { "goodbye" }
}
})
});
+9 -9
View File
@@ -7,7 +7,7 @@ use dioxus_core::ElementId;
#[test]
fn empty_fragment_creates_nothing() {
fn app(cx: Scope) -> Element {
cx.render(rsx!(()))
cx.render(rsx!({}))
}
let mut vdom = VirtualDom::new(app);
@@ -43,18 +43,18 @@ fn fragments_nested() {
cx.render(rsx!(
div { "hello" }
div { "goodbye" }
rsx! {
{rsx! {
div { "hello" }
div { "goodbye" }
rsx! {
{rsx! {
div { "hello" }
div { "goodbye" }
rsx! {
{rsx! {
div { "hello" }
div { "goodbye" }
}
}
}
}}
}}
}}
))
});
@@ -79,7 +79,7 @@ fn fragments_across_components() {
let world = "world";
cx.render(rsx! {
"hellO!"
world
{world}
})
}
@@ -94,7 +94,7 @@ fn list_fragments() {
fn app(cx: Scope) -> Element {
cx.render(rsx!(
h1 {"hello"}
(0..6).map(|f| rsx!( span { "{f}" }))
{(0..6).map(|f| rsx!( span { "{f}" }))}
))
}
assert_eq!(
+2 -2
View File
@@ -11,12 +11,12 @@ use dioxus_core::ElementId;
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
(0..3).map(|i| rsx! {
for i in 0..3 {
div {
h1 { "hello world! "}
p { "{i}" }
}
})
}
}
})
}
+2 -2
View File
@@ -20,7 +20,7 @@ fn nested_passthru_creates() {
#[component]
fn PassThru<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
cx.render(rsx!(children))
cx.render(rsx!({ children }))
}
let mut dom = VirtualDom::new(App);
@@ -60,7 +60,7 @@ fn nested_passthru_creates_add() {
#[component]
fn ChildComp<'a>(cx: Scope, children: Element<'a>) -> Element {
cx.render(rsx! { children })
cx.render(rsx! { {children} })
}
let mut dom = VirtualDom::new(App);
+1 -1
View File
@@ -41,7 +41,7 @@ fn component_swap() {
cx.render(rsx! {
h1 {
"NavBar"
(0..3).map(|_| rsx!(nav_link {}))
{(0..3).map(|_| rsx!(nav_link {}))}
}
})
}
+12 -12
View File
@@ -17,7 +17,7 @@ fn keyed_diffing_out_of_order() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
{
@@ -59,7 +59,7 @@ fn keyed_diffing_out_of_order_adds() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -85,7 +85,7 @@ fn keyed_diffing_out_of_order_adds_3() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -111,7 +111,7 @@ fn keyed_diffing_out_of_order_adds_4() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -137,7 +137,7 @@ fn keyed_diffing_out_of_order_adds_5() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -162,7 +162,7 @@ fn keyed_diffing_additions() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -187,7 +187,7 @@ fn keyed_diffing_additions_and_moves_on_ends() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -216,7 +216,7 @@ fn keyed_diffing_additions_and_moves_in_middle() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -250,7 +250,7 @@ fn controlled_keyed_diffing_out_of_order() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -284,7 +284,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -313,7 +313,7 @@ fn remove_list() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
@@ -338,7 +338,7 @@ fn no_common_keys() {
_ => unreachable!(),
};
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
});
_ = dom.rebuild();
+15 -13
View File
@@ -9,9 +9,9 @@ fn list_creates_one_by_one() {
cx.render(rsx! {
div {
(0..gen).map(|i| rsx! {
for i in 0..gen {
div { "{i}" }
})
}
}
})
});
@@ -78,9 +78,9 @@ fn removes_one_by_one() {
cx.render(rsx! {
div {
(0..gen).map(|i| rsx! {
for i in 0..gen {
div { "{i}" }
})
}
}
})
});
@@ -153,10 +153,10 @@ fn list_shrink_multiroot() {
let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! {
div {
(0..cx.generation()).map(|i| rsx! {
for i in 0..cx.generation() {
div { "{i}" }
div { "{i}" }
})
}
}
})
});
@@ -214,10 +214,10 @@ fn removes_one_by_one_multiroot() {
cx.render(rsx! {
div {
(0..gen).map(|i| rsx! {
{(0..gen).map(|i| rsx! {
div { "{i}" }
div { "{i}" }
})
})}
}
})
});
@@ -276,9 +276,9 @@ fn removes_one_by_one_multiroot() {
fn two_equal_fragments_are_equal_static() {
let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! {
(0..5).map(|_| rsx! {
for _ in 0..5 {
div { "hello" }
})
}
})
});
@@ -290,9 +290,9 @@ fn two_equal_fragments_are_equal_static() {
fn two_equal_fragments_are_equal() {
let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! {
(0..5).map(|i| rsx! {
for i in 0..5 {
div { "hello {i}" }
})
}
})
});
@@ -311,7 +311,9 @@ fn remove_many() {
};
cx.render(rsx! {
(0..num).map(|i| rsx! { div { "hello {i}" } })
for i in 0..num {
div { "hello {i}" }
}
})
});
+2 -2
View File
@@ -58,11 +58,11 @@ fn app(cx: Scope) -> Element {
*CLICKS.lock().unwrap() += 1;
},
vec![
{vec![
render! {
problematic_child {}
}
].into_iter()
].into_iter()}
}
}
}
+1 -1
View File
@@ -51,7 +51,7 @@ fn events_generate() {
"Click me!"
}
}),
_ => cx.render(rsx!(())),
_ => None,
}
};
+2 -2
View File
@@ -39,9 +39,9 @@ fn App(cx: Scope) -> Element {
}
button { onclick: move |_| idx -= 1, "-" }
ul {
(0..**idx).map(|i| rsx! {
{(0..**idx).map(|i| rsx! {
ChildExample { i: i, onhover: onhover }
})
})}
}
}
})
+2 -2
View File
@@ -15,7 +15,7 @@ fn test_memory_leak() {
cx.spawn(async {});
if val == 2 || val == 4 {
return cx.render(rsx!(()));
return render!({});
}
let name = cx.use_hook(|| String::from("numbers: "));
@@ -73,7 +73,7 @@ fn memo_works_properly() {
let val = cx.generation();
if val == 2 || val == 4 {
return cx.render(rsx!(()));
return None;
}
let name = cx.use_hook(|| String::from("asd"));
+1 -1
View File
@@ -25,7 +25,7 @@ async fn it_works() {
});
});
cx.render(rsx!(()))
None
}
let mut dom = VirtualDom::new(app);
+1 -1
View File
@@ -89,7 +89,7 @@ fn check_html_renders(cx: Scope) -> Element {
h1 {
"text"
}
dyn_element
{dyn_element}
}
}
}
@@ -71,7 +71,7 @@ fn app(cx: Scope) -> Element {
"Hover, click, type or scroll to see the info down below"
}
div { width: "80%", height: "50%", flex_direction: "column", events_rendered }
div { width: "80%", height: "50%", flex_direction: "column", {events_rendered} }
}
})
}
+23 -33
View File
@@ -27,26 +27,25 @@ fn Button(cx: Scope<ButtonProps>) -> Element {
height: "100%",
background_color: "{color}",
tabindex: "{cx.props.layer}",
onkeydown: |e| {
onkeydown: move |e| {
if let Code::Space = e.inner().code() {
toggle.modify(|f| !f);
}
},
onclick: |_| {
onclick: move |_| {
toggle.modify(|f| !f);
},
onmouseenter: |_|{
onmouseenter: move |_| {
hovered.set(true);
},
onmouseleave: |_|{
onmouseleave: move |_|{
hovered.set(false);
},
justify_content: "center",
align_items: "center",
display: "flex",
flex_direction: "column",
p{"tabindex: {cx.props.layer}"}
p{ "tabindex: {cx.props.layer}" }
}
})
}
@@ -58,37 +57,28 @@ fn app(cx: Scope) -> Element {
flex_direction: "column",
width: "100%",
height: "100%",
(1..8).map(|y|
rsx!{
div{
display: "flex",
flex_direction: "row",
width: "100%",
height: "100%",
(1..8).map(|x|{
if (x + y) % 2 == 0{
rsx!{
div{
width: "100%",
height: "100%",
background_color: "rgb(100, 100, 100)",
}
}
for y in 1..8 {
div {
display: "flex",
flex_direction: "row",
width: "100%",
height: "100%",
for x in 1..8 {
if (x + y) % 2 == 0 {
div {
width: "100%",
height: "100%",
background_color: "rgb(100, 100, 100)",
}
else{
let layer = (x + y) % 3;
rsx!{
Button{
color_offset: x * y,
layer: layer as u16,
}
}
} else {
Button {
color_offset: x * y,
layer: ((x + y) % 3) as u16,
}
})
}
}
}
)
}
}
})
}
+15 -22
View File
@@ -14,32 +14,25 @@ fn app(cx: Scope) -> Element {
width: "100%",
height: "100%",
flex_direction: "column",
(0..=steps).map(|x|
{
let hue = x as f32*360.0/steps as f32;
rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "row",
(0..=steps).map(|y|
{
let alpha = y as f32*100.0/steps as f32;
rsx! {
div {
left: "{x}px",
top: "{y}px",
width: "10%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
}
}
for x in 0..=steps {
div { width: "100%", height: "100%", flex_direction: "row",
for y in 0..=steps {
{
let hue = x as f32*360.0/steps as f32;
let alpha = y as f32*100.0/steps as f32;
rsx! {
div {
left: "{x}px",
top: "{y}px",
width: "10%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
}
)
}
}
}
}
)
}
}
})
}
+2 -2
View File
@@ -19,9 +19,9 @@ fn app(cx: Scope) -> Element {
ul {
flex_direction: "column",
padding_left: "3px",
(0..10).map(|i| rsx!(
for i in 0..10 {
"> hello {i}"
))
}
}
}
})
@@ -71,32 +71,28 @@ fn Grid(cx: Scope<GridProps>) -> Element {
width: "100%",
height: "100%",
flex_direction: "column",
(0..size).map(|x|
{
rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "row",
(0..size).map(|y|
{
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
let key = format!("{}-{}", x, y);
rsx! {
Box {
x: x,
y: y,
alpha: 100.0,
hue: alpha,
key: "{key}",
}
}
for x in 0..size {
div{
width: "100%",
height: "100%",
flex_direction: "row",
for y in 0..size {
{
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
let key = format!("{}-{}", x, y);
rsx! {
Box {
x: x,
y: y,
alpha: 100.0,
hue: alpha,
key: "{key}",
}
)
}
}
}
}
)
}
}
}
}
+6 -4
View File
@@ -17,10 +17,12 @@ fn app(cx: Scope) -> Element {
cx.render(rsx! (
table {
tbody {
(0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
rsx!( table_row { row_id: f, label: label } )
})
for f in 0..10_000_usize {
table_row {
row_id: f,
label: Label::new(&mut rng)
}
}
}
}
))
@@ -306,15 +306,10 @@ fn persist_removes() {
_ => unreachable!(),
};
render!(
div{
(0..children).map(|i|{
rsx!{
p{
key: "{i}",
"{i}"
}
}
})
div {
for i in 0..children {
p { key: "{i}", "{i}" }
}
}
)
}
@@ -387,15 +382,10 @@ fn persist_instertions_before() {
_ => unreachable!(),
};
render!(
div{
(0..children).map(|i|{
rsx!{
p{
key: "{i}",
"{i}"
}
}
})
div {
for i in 0..children {
p { key: "{i}", "{i}" }
}
}
)
}
@@ -446,14 +436,9 @@ fn persist_instertions_after() {
};
render!(
div{
(0..children).map(|i|{
rsx!{
p{
key: "{i}",
"{i}"
}
}
})
for i in 0..children {
p { key: "{i}", "{i}" }
}
}
)
}
+1 -1
View File
@@ -103,7 +103,7 @@ fn Route1(cx: Scope, user_id: usize, dynamic: usize, query: String, extra: Strin
fn Route2(cx: Scope, user_id: usize) -> Element {
render! {
pre { "Route2{{\n\tuser_id:{user_id}\n}}" }
(0..*user_id).map(|i| rsx!{ p { "{i}" } }),
{(0..*user_id).map(|i| rsx!{ p { "{i}" } })},
p { "Footer" }
Link {
to: Route::Route3 {
@@ -77,7 +77,7 @@ pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
disabled: "{disabled}",
prevent_default: "onclick",
onclick: move |_| router.go_back(),
children
{children}
}
}
}
@@ -149,7 +149,7 @@ pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
disabled: "{disabled}",
prevent_default: "onclick",
onclick: move |_| router.go_forward(),
children
{children}
}
}
}
+2 -2
View File
@@ -167,7 +167,7 @@ impl Debug for LinkProps<'_> {
/// new_tab: true,
/// rel: "link_rel",
/// to: Route::Index {},
///
///
/// "A fully configured link"
/// }
/// }
@@ -250,7 +250,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
id: "{id}",
rel: "{rel}",
target: "{tag_target}",
children
{children}
}
}
}
+29 -7
View File
@@ -20,6 +20,13 @@ impl AttributeType {
}
}
pub fn matches_attr_name(&self, other: &Self) -> bool {
match (self, other) {
(Self::Named(a), Self::Named(b)) => a.attr.name == b.attr.name,
_ => false,
}
}
pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::Named(a), Self::Named(b)) => a.try_combine(b).map(Self::Named),
@@ -102,16 +109,25 @@ impl ToTokens for ElementAttrNamed {
};
let attribute = {
let value = &self.attr.value;
let is_shorthand_event = match &attr.value {
ElementAttrValue::Shorthand(s) => s.to_string().starts_with("on"),
_ => false,
};
match &attr.value {
ElementAttrValue::AttrLiteral(_)
| ElementAttrValue::AttrExpr(_)
| ElementAttrValue::AttrOptionalExpr { .. } => {
| ElementAttrValue::Shorthand(_)
| ElementAttrValue::AttrOptionalExpr { .. }
if !is_shorthand_event =>
{
let name = &self.attr.name;
let ns = ns(name);
let volitile = volitile(name);
let attribute = attribute(name);
let value = &self.attr.value;
let value = quote! { #value };
quote! {
__cx.attr(
#attribute,
@@ -123,12 +139,13 @@ impl ToTokens for ElementAttrNamed {
}
ElementAttrValue::EventTokens(tokens) => match &self.attr.name {
ElementAttrName::BuiltIn(name) => {
quote! {
dioxus_elements::events::#name(__cx, #tokens)
}
quote! { dioxus_elements::events::#name(__cx, #tokens) }
}
ElementAttrName::Custom(_) => todo!(),
},
_ => {
quote! { dioxus_elements::events::#value(__cx, #value) }
}
}
};
@@ -144,6 +161,8 @@ pub struct ElementAttr {
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum ElementAttrValue {
/// attribute,
Shorthand(Ident),
/// attribute: "value"
AttrLiteral(IfmtInput),
/// attribute: if bool { "value" }
@@ -159,7 +178,7 @@ pub enum ElementAttrValue {
impl Parse for ElementAttrValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(if input.peek(Token![if]) {
let element_attr_value = if input.peek(Token![if]) {
let if_expr = input.parse::<ExprIf>()?;
if is_if_chain_terminated(&if_expr) {
ElementAttrValue::AttrExpr(Expr::If(if_expr))
@@ -180,13 +199,16 @@ impl Parse for ElementAttrValue {
} else {
let value = input.parse::<Expr>()?;
ElementAttrValue::AttrExpr(value)
})
};
Ok(element_attr_value)
}
}
impl ToTokens for ElementAttrValue {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
ElementAttrValue::Shorthand(i) => tokens.append_all(quote! { #i }),
ElementAttrValue::AttrLiteral(lit) => tokens.append_all(quote! { #lit }),
ElementAttrValue::AttrOptionalExpr { condition, value } => {
tokens.append_all(quote! { if #condition { Some(#value) } else { None } })
+174 -148
View File
@@ -19,6 +19,7 @@ use syn::{
ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream},
spanned::Spanned,
token::Brace,
AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result, Token,
};
@@ -32,8 +33,85 @@ pub struct Component {
pub brace: syn::token::Brace,
}
impl Parse for Component {
fn parse(stream: ParseStream) -> Result<Self> {
let mut name = stream.parse::<syn::Path>()?;
Component::validate_component_path(&name)?;
// extract the path arguments from the path into prop_gen_args
let prop_gen_args = normalize_path(&mut name);
let content: ParseBuffer;
let brace = syn::braced!(content in stream);
let mut fields = Vec::new();
let mut children = Vec::new();
let mut manual_props = None;
while !content.is_empty() {
// if we splat into a component then we're merging properties
if content.peek(Token![..]) {
content.parse::<Token![..]>()?;
manual_props = Some(content.parse()?);
} else if
// Named fields
(content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]))
// shorthand struct initialization
// Not a div {}, mod::Component {}, or web-component {}
|| (content.peek(Ident)
&& !content.peek2(Brace)
&& !content.peek2(Token![:])
&& !content.peek2(Token![-]))
{
fields.push(content.parse()?);
} else {
children.push(content.parse()?);
}
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(Self {
name,
prop_gen_args,
fields,
children,
manual_props,
brace,
})
}
}
impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let Self {
name,
prop_gen_args,
..
} = self;
let builder = self
.manual_props
.as_ref()
.map(|props| self.collect_manual_props(props))
.unwrap_or_else(|| self.collect_props());
let fn_name = self.fn_name();
tokens.append_all(quote! {
__cx.component(
#name #prop_gen_args,
#builder,
#fn_name
)
})
}
}
impl Component {
pub fn validate_component_path(path: &syn::Path) -> Result<()> {
fn validate_component_path(path: &syn::Path) -> Result<()> {
// ensure path segments doesn't have PathArguments, only the last
// segment is allowed to have one.
if path
@@ -67,134 +145,53 @@ impl Component {
_ => None,
}
}
}
impl Parse for Component {
fn parse(stream: ParseStream) -> Result<Self> {
let mut name = stream.parse::<syn::Path>()?;
Component::validate_component_path(&name)?;
// extract the path arguments from the path into prop_gen_args
let prop_gen_args = name.segments.last_mut().and_then(|seg| {
if let PathArguments::AngleBracketed(args) = seg.arguments.clone() {
seg.arguments = PathArguments::None;
Some(args)
} else {
None
fn collect_manual_props(&self, manual_props: &Expr) -> TokenStream2 {
let mut toks = quote! { let mut __manual_props = #manual_props; };
for field in &self.fields {
if field.name == "key" {
continue;
}
});
let ComponentField { name, content } = field;
toks.append_all(quote! { __manual_props.#name = #content; });
}
toks.append_all(quote! { __manual_props });
quote! {{ #toks }}
}
let content: ParseBuffer;
fn collect_props(&self) -> TokenStream2 {
let name = &self.name;
// if we see a `{` then we have a block
// else parse as a function-like call
let brace = syn::braced!(content in stream);
let mut fields = Vec::new();
let mut children = Vec::new();
let mut manual_props = None;
while !content.is_empty() {
// if we splat into a component then we're merging properties
if content.peek(Token![..]) {
content.parse::<Token![..]>()?;
manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
fields.push(content.parse::<ComponentField>()?);
} else {
children.push(content.parse::<BodyNode>()?);
}
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
let mut toks = match &self.prop_gen_args {
Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) },
None => quote! { fc_to_builder(__cx, #name) },
};
for field in &self.fields {
match field.name.to_string().as_str() {
"key" => {}
_ => toks.append_all(quote! {#field}),
}
}
if !self.children.is_empty() {
let renderer: TemplateRenderer = TemplateRenderer {
roots: &self.children,
location: None,
};
Ok(Self {
name,
prop_gen_args,
fields,
children,
manual_props,
brace,
})
toks.append_all(quote! {
.children(
Some({ #renderer })
)
});
}
toks.append_all(quote! {
.build()
});
toks
}
}
impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let prop_gen_args = &self.prop_gen_args;
let builder = match &self.manual_props {
Some(manual_props) => {
let mut toks = quote! {
let mut __manual_props = #manual_props;
};
for field in &self.fields {
if field.name == "key" {
// skip keys
} else {
let name = &field.name;
let val = &field.content;
toks.append_all(quote! {
__manual_props.#name = #val;
});
}
}
toks.append_all(quote! {
__manual_props
});
quote! {{
#toks
}}
}
None => {
let mut toks = match prop_gen_args {
Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) },
None => quote! { fc_to_builder(__cx, #name) },
};
for field in &self.fields {
match field.name.to_string().as_str() {
"key" => {}
_ => toks.append_all(quote! {#field}),
}
}
if !self.children.is_empty() {
let renderer: TemplateRenderer = TemplateRenderer {
roots: &self.children,
location: None,
};
toks.append_all(quote! {
.children(
Some({ #renderer })
)
});
}
toks.append_all(quote! {
.build()
});
toks
}
};
let fn_name = self.name.segments.last().unwrap().ident.to_string();
let gen_name = match &self.prop_gen_args {
Some(gen) => quote! { #name #gen },
None => quote! { #name },
};
tokens.append_all(quote! {
__cx.component(
#gen_name,
#builder,
#fn_name
)
})
fn fn_name(&self) -> String {
self.name.segments.last().unwrap().ident.to_string()
}
}
@@ -207,21 +204,50 @@ pub struct ComponentField {
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum ContentField {
Shorthand(Ident),
ManExpr(Expr),
Formatted(IfmtInput),
OnHandlerRaw(Expr),
}
impl ContentField {
fn new_from_name(name: &Ident, input: ParseStream) -> Result<Self> {
if name.to_string().starts_with("on") {
return Ok(ContentField::OnHandlerRaw(input.parse()?));
}
if *name == "key" {
return Ok(ContentField::Formatted(input.parse()?));
}
if input.peek(LitStr) {
let forked = input.fork();
let t: LitStr = forked.parse()?;
// the string literal must either be the end of the input or a followed by a comma
let res =
match (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
true => ContentField::Formatted(input.parse()?),
false => ContentField::ManExpr(input.parse()?),
};
return Ok(res);
}
Ok(ContentField::ManExpr(input.parse()?))
}
}
impl ToTokens for ContentField {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
ContentField::Shorthand(i) if i.to_string().starts_with("on") => {
tokens.append_all(quote! { __cx.event_handler(#i) })
}
ContentField::Shorthand(i) => tokens.append_all(quote! { #i }),
ContentField::ManExpr(e) => e.to_tokens(tokens),
ContentField::Formatted(s) => tokens.append_all(quote! {
__cx.raw_text(#s)
}),
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
__cx.event_handler(#e)
}),
ContentField::Formatted(s) => tokens.append_all(quote! { __cx.raw_text(#s) }),
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { __cx.event_handler(#e) }),
}
}
}
@@ -229,30 +255,21 @@ impl ToTokens for ContentField {
impl Parse for ComponentField {
fn parse(input: ParseStream) -> Result<Self> {
let name = Ident::parse_any(input)?;
input.parse::<Token![:]>()?;
let content = {
if name.to_string().starts_with("on") {
ContentField::OnHandlerRaw(input.parse()?)
} else if name == "key" {
let content = ContentField::Formatted(input.parse()?);
return Ok(Self { name, content });
} else if input.peek(LitStr) {
let forked = input.fork();
let t: LitStr = forked.parse()?;
// the string literal must either be the end of the input or a followed by a comma
if (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
ContentField::Formatted(input.parse()?)
} else {
ContentField::ManExpr(input.parse()?)
}
} else {
ContentField::ManExpr(input.parse()?)
}
// if the next token is not a colon, then it's a shorthand field
if input.parse::<Token![:]>().is_err() {
return Ok(Self {
content: ContentField::Shorthand(name.clone()),
name,
});
};
let content = ContentField::new_from_name(&name, input)?;
if input.peek(LitStr) || input.peek(Ident) {
missing_trailing_comma!(content.span());
}
Ok(Self { name, content })
}
}
@@ -260,9 +277,7 @@ impl Parse for ComponentField {
impl ToTokens for ComponentField {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ComponentField { name, content, .. } = self;
tokens.append_all(quote! {
.#name(#content)
})
tokens.append_all(quote! { .#name(#content) })
}
}
@@ -281,3 +296,14 @@ fn is_literal_foramtted(lit: &LitStr) -> bool {
false
}
fn normalize_path(name: &mut syn::Path) -> Option<AngleBracketedGenericArguments> {
let seg = name.segments.last_mut()?;
match seg.arguments.clone() {
PathArguments::AngleBracketed(args) => {
seg.arguments = PathArguments::None;
Some(args)
}
_ => None,
}
}
+65 -35
View File
@@ -8,6 +8,7 @@ use syn::{
parse::{Parse, ParseBuffer, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
token::Brace,
Expr, Ident, LitStr, Result, Token,
};
@@ -59,6 +60,7 @@ impl Parse for Element {
}
// Parse the raw literal fields
// "def": 456,
if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
let name = content.parse::<LitStr>()?;
let ident = name.clone();
@@ -84,6 +86,8 @@ impl Parse for Element {
continue;
}
// Parse
// abc: 123,
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
let name = content.parse::<Ident>()?;
@@ -102,22 +106,17 @@ impl Parse for Element {
value: ElementAttrValue::EventTokens(content.parse()?),
},
}));
} else if name_str == "key" {
key = Some(content.parse()?);
} else {
match name_str.as_str() {
"key" => {
key = Some(content.parse()?);
}
_ => {
let value = content.parse::<ElementAttrValue>()?;
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr {
name: ElementAttrName::BuiltIn(name),
value,
},
}));
}
}
let value = content.parse::<ElementAttrValue>()?;
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr {
name: ElementAttrName::BuiltIn(name),
value,
},
}));
}
if content.is_empty() {
@@ -130,6 +129,47 @@ impl Parse for Element {
continue;
}
// Parse shorthand fields
if content.peek(Ident)
&& !content.peek2(Brace)
&& !content.peek2(Token![:])
&& !content.peek2(Token![-])
{
let name = content.parse::<Ident>()?;
let name_ = name.clone();
// If the shorthand field is children, these are actually children!
if name == "children" {
return Err(syn::Error::new(
name.span(),
r#"Shorthand element children are not supported.
To pass children into elements, wrap them in curly braces.
Like so:
div {{ {{children}} }}
"#,
));
};
let value = ElementAttrValue::Shorthand(name.clone());
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr {
name: ElementAttrName::BuiltIn(name),
value,
},
}));
if content.is_empty() {
break;
}
if content.parse::<Token![,]>().is_err() {
missing_trailing_comma!(name_.span());
}
continue;
}
break;
}
@@ -137,31 +177,21 @@ impl Parse for Element {
// For example, if there are two `class` attributes, combine them into one
let mut merged_attributes: Vec<AttributeType> = Vec::new();
for attr in &attributes {
if let Some(old_attr_index) = merged_attributes.iter().position(|a| {
matches!((a, attr), (
AttributeType::Named(ElementAttrNamed {
attr: ElementAttr {
name: ElementAttrName::BuiltIn(old_name),
..
},
..
}),
AttributeType::Named(ElementAttrNamed {
attr: ElementAttr {
name: ElementAttrName::BuiltIn(new_name),
..
},
..
}),
) if old_name == new_name)
}) {
let attr_index = merged_attributes
.iter()
.position(|a| a.matches_attr_name(attr));
if let Some(old_attr_index) = attr_index {
let old_attr = &mut merged_attributes[old_attr_index];
if let Some(combined) = old_attr.try_combine(attr) {
*old_attr = combined;
}
} else {
merged_attributes.push(attr.clone());
continue;
}
merged_attributes.push(attr.clone());
}
while !content.is_empty() {
+7 -7
View File
@@ -576,7 +576,7 @@ fn create_template() {
p {
"hello world"
}
(0..10).map(|i| rsx!{"{i}"})
{(0..10).map(|i| rsx!{"{i}"})}
}
};
@@ -668,9 +668,9 @@ fn diff_template() {
p {
"hello world"
}
(0..10).map(|i| rsx!{"{i}"}),
(0..10).map(|i| rsx!{"{i}"}),
(0..11).map(|i| rsx!{"{i}"}),
{(0..10).map(|i| rsx!{"{i}"})},
{(0..10).map(|i| rsx!{"{i}"})},
{(0..11).map(|i| rsx!{"{i}"})},
Comp{}
}
};
@@ -714,9 +714,9 @@ fn diff_template() {
"height2": "100px",
width: 100,
Comp{}
(0..11).map(|i| rsx!{"{i}"}),
(0..10).map(|i| rsx!{"{i}"}),
(0..10).map(|i| rsx!{"{i}"}),
{(0..11).map(|i| rsx!{"{i}"})},
{(0..10).map(|i| rsx!{"{i}"})},
{(0..10).map(|i| rsx!{"{i}"})},
p {
"hello world"
}
+115 -67
View File
@@ -6,7 +6,8 @@ use syn::{
braced,
parse::{Parse, ParseStream},
spanned::Spanned,
token, Expr, ExprIf, LitStr, Pat, Result,
token::{self, Brace},
Expr, ExprIf, LitStr, Pat, Result,
};
/*
@@ -15,14 +16,14 @@ Parse
-> Component {}
-> component()
-> "text {with_args}"
-> (0..10).map(|f| rsx!("asd")), // <--- notice the comma - must be a complete expr
-> {(0..10).map(|f| rsx!("asd"))} // <--- notice the curly braces
*/
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum BodyNode {
Element(Element),
Component(Component),
ForLoop(ForLoop),
IfChain(ExprIf),
IfChain(IfChain),
Text(IfmtInput),
RawExpr(Expr),
}
@@ -97,7 +98,6 @@ impl Parse for BodyNode {
// Input::<InputProps<'_, i32> {}
// crate::Input::<InputProps<'_, i32> {}
if body_stream.peek(token::Brace) {
Component::validate_component_path(&path)?;
return Ok(BodyNode::Component(stream.parse()?));
}
}
@@ -112,7 +112,27 @@ impl Parse for BodyNode {
return Ok(BodyNode::IfChain(stream.parse()?));
}
Ok(BodyNode::RawExpr(stream.parse::<Expr>()?))
// Match statements are special but have no special arm syntax
// we could allow arm syntax if we wanted
//
// ```
// match {
// val => div {}
// other_val => div {}
// }
// ```
if stream.peek(Token![match]) {
return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
}
if stream.peek(token::Brace) {
return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
}
Err(syn::Error::new(
stream.span(),
"Expected a valid body node.\nExpressions must be wrapped in curly braces.",
))
}
}
@@ -151,71 +171,52 @@ impl ToTokens for BodyNode {
})
}
BodyNode::IfChain(chain) => {
if is_if_chain_terminated(chain) {
tokens.append_all(quote! {
{
let ___nodes = (#chain).into_dyn_node(__cx);
___nodes
}
});
} else {
let ExprIf {
let mut body = TokenStream2::new();
let mut terminated = false;
let mut elif = Some(chain);
while let Some(chain) = elif {
let IfChain {
if_token,
cond,
then_branch,
else_if_branch,
else_branch,
..
} = chain;
let mut body = TokenStream2::new();
let mut renderer: TemplateRenderer = TemplateRenderer {
roots: then_branch,
location: None,
};
body.append_all(quote! {
if #cond {
Some(#then_branch)
}
});
body.append_all(quote! { #if_token #cond { Some({#renderer}) } });
let mut elif = else_branch;
while let Some((_, ref branch)) = elif {
match branch.as_ref() {
Expr::If(ref eelif) => {
let ExprIf {
cond,
then_branch,
else_branch,
..
} = eelif;
body.append_all(quote! {
else if #cond {
Some(#then_branch)
}
});
elif = else_branch;
}
_ => {
body.append_all(quote! {
else {
#branch
}
});
break;
}
}
if let Some(next) = else_if_branch {
body.append_all(quote! { else });
elif = Some(next);
} else if let Some(else_branch) = else_branch {
renderer.roots = else_branch;
body.append_all(quote! { else { Some({#renderer}) } });
terminated = true;
break;
} else {
elif = None;
}
}
if !terminated {
body.append_all(quote! {
else { None }
});
tokens.append_all(quote! {
{
let ___nodes = (#body).into_dyn_node(__cx);
___nodes
}
});
}
tokens.append_all(quote! {
{
let ___nodes = (#body).into_dyn_node(__cx);
___nodes
}
});
}
}
}
@@ -240,26 +241,73 @@ impl Parse for ForLoop {
let in_token: Token![in] = input.parse()?;
let expr: Expr = input.call(Expr::parse_without_eager_brace)?;
let content;
let brace_token = braced!(content in input);
let mut children = vec![];
while !content.is_empty() {
children.push(content.parse()?);
}
let (brace_token, body) = parse_buffer_as_braced_children(input)?;
Ok(Self {
for_token,
pat,
in_token,
body: children,
expr: Box::new(expr),
body,
brace_token,
expr: Box::new(expr),
})
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct IfChain {
pub if_token: Token![if],
pub cond: Box<Expr>,
pub then_branch: Vec<BodyNode>,
pub else_if_branch: Option<Box<IfChain>>,
pub else_branch: Option<Vec<BodyNode>>,
}
impl Parse for IfChain {
fn parse(input: ParseStream) -> Result<Self> {
let if_token: Token![if] = input.parse()?;
// stolen from ExprIf
let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);
let (_, then_branch) = parse_buffer_as_braced_children(input)?;
let mut else_branch = None;
let mut else_if_branch = None;
// if the next token is `else`, set the else branch as the next if chain
if input.peek(Token![else]) {
input.parse::<Token![else]>()?;
if input.peek(Token![if]) {
else_if_branch = Some(Box::new(input.parse::<IfChain>()?));
} else {
let (_, else_branch_nodes) = parse_buffer_as_braced_children(input)?;
else_branch = Some(else_branch_nodes);
}
}
Ok(Self {
cond,
if_token,
then_branch,
else_if_branch,
else_branch,
})
}
}
fn parse_buffer_as_braced_children(
input: &syn::parse::ParseBuffer<'_>,
) -> Result<(Brace, Vec<BodyNode>)> {
let content;
let brace_token = braced!(content in input);
let mut then_branch = vec![];
while !content.is_empty() {
then_branch.push(content.parse()?);
}
Ok((brace_token, then_branch))
}
pub(crate) fn is_if_chain_terminated(chain: &ExprIf) -> bool {
let mut current = chain;
loop {
+3 -1
View File
@@ -242,7 +242,9 @@ fn to_string_works() {
div {}
div { "nest 2" }
"{dyn2}"
(0..5).map(|i| rsx! { div { "finalize {i}" } })
for i in (0..5) {
div { "finalize {i}" }
}
}
}
}
+3 -7
View File
@@ -70,7 +70,7 @@ fn text_nodes() {
fn app(cx: Scope) -> Element {
let dynamic_text = "hello";
render! {
div { dynamic_text }
div { {dynamic_text} }
}
}
@@ -124,7 +124,7 @@ fn components_hydrate() {
fn Child2(cx: Scope) -> Element {
let dyn_text = "hello";
render! {
div { dyn_text }
div { {dyn_text} }
}
}
@@ -159,11 +159,7 @@ fn components_hydrate() {
fn Child4(cx: Scope) -> Element {
render! {
for _ in 0..2 {
render! {
render! {
"{1}"
}
}
{render! { "{1}" }}
}
}
}
+7 -5
View File
@@ -23,9 +23,9 @@ fn lists() {
assert_eq!(
dioxus_ssr::render_lazy(rsx! {
ul {
(0..5).map(|i| rsx! {
for i in 0..5 {
li { "item {i}" }
})
}
}
}),
"<ul><li>item 0</li><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li></ul>"
@@ -53,9 +53,9 @@ fn components() {
assert_eq!(
dioxus_ssr::render_lazy(rsx! {
div {
(0..5).map(|name| rsx! {
for name in 0..5 {
MyComponent { name: name }
})
}
}
}),
"<div><div>component 0</div><div>component 1</div><div>component 2</div><div>component 3</div><div>component 4</div></div>"
@@ -67,7 +67,9 @@ fn fragments() {
assert_eq!(
dioxus_ssr::render_lazy(rsx! {
div {
(0..5).map(|_| rsx! (()))
for _ in 0..5 {
{}
}
}
}),
"<div></div>"
+2 -2
View File
@@ -12,11 +12,11 @@ fn app(cx: Scope) -> Element {
"asd"
Bapp {}
}
(0..10).map(|f| rsx!{
{(0..10).map(|f| rsx!{
div {
"thing {f}"
}
})
})}
})
}
+5 -2
View File
@@ -24,8 +24,11 @@ fn app(cx: Scope) -> Element {
start();
*count.write() += 1;
},
// format is needed as {count} does not seemed to work in `if` within content
if **started { format!("Current score: {}", count.write()) } else { "Start".to_string() }
if **started {
"Current score: {count.read()}"
} else {
"Start"
}
}
})
}
+1 -1
View File
@@ -43,7 +43,7 @@ fn rehydrates() {
},
"listener test"
}
false.then(|| rsx!{ "hello" })
{false.then(|| rsx!{ "hello" })}
}
})
}