Merge branch 'master' into jk/breaking-merged
This commit is contained in:
211 files changed
+21135
-3770
No files matched your search
@@ -4,14 +4,66 @@ use super::*;
|
||||
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{parse_quote, Expr, ExprIf, Ident, LitStr};
|
||||
use syn::{parse_quote, spanned::Spanned, Expr, ExprIf, Ident, LitStr};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum AttributeType {
|
||||
Named(ElementAttrNamed),
|
||||
Spread(Expr),
|
||||
}
|
||||
|
||||
impl AttributeType {
|
||||
pub fn start(&self) -> Span {
|
||||
match self {
|
||||
AttributeType::Named(n) => n.attr.start(),
|
||||
AttributeType::Spread(e) => e.span(),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for AttributeType {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self {
|
||||
AttributeType::Named(n) => tokens.append_all(quote! { #n }),
|
||||
AttributeType::Spread(e) => tokens.append_all(quote! { (&#e).into() }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ElementAttrNamed {
|
||||
pub el_name: ElementName,
|
||||
pub attr: ElementAttr,
|
||||
}
|
||||
|
||||
impl Hash for ElementAttrNamed {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.attr.name.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ElementAttrNamed {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.attr == other.attr
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ElementAttrNamed {}
|
||||
|
||||
impl ElementAttrNamed {
|
||||
pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
|
||||
if self.el_name == other.el_name && self.attr.name == other.attr.name {
|
||||
@@ -57,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! {
|
||||
::dioxus::core::Attribute::new(
|
||||
#attribute,
|
||||
@@ -84,6 +145,9 @@ impl ToTokens for ElementAttrNamed {
|
||||
}
|
||||
ElementAttrName::Custom(_) => todo!(),
|
||||
},
|
||||
_ => {
|
||||
quote! { dioxus_elements::events::#value(__cx, #value) }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,6 +163,8 @@ pub struct ElementAttr {
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum ElementAttrValue {
|
||||
/// attribute,
|
||||
Shorthand(Ident),
|
||||
/// attribute: "value"
|
||||
AttrLiteral(IfmtInput),
|
||||
/// attribute: if bool { "value" }
|
||||
@@ -114,7 +180,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))
|
||||
@@ -135,13 +201,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 } })
|
||||
|
||||
+172
-142
@@ -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! {
|
||||
::dioxus::core::DynamicNode::Component(::dioxus::core::VComponent::new(
|
||||
#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(#name #gen_args) },
|
||||
None => quote! { fc_to_builder(#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! {
|
||||
::dioxus::core::DynamicNode::Component(::dioxus::core::VComponent::new(
|
||||
#gen_name,
|
||||
#builder,
|
||||
#fn_name
|
||||
))
|
||||
})
|
||||
fn fn_name(&self) -> String {
|
||||
self.name.segments.last().unwrap().ident.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,14 +204,47 @@ 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! { EventHandler::new(#i) })
|
||||
}
|
||||
ContentField::Shorthand(i) => tokens.append_all(quote! { #i }),
|
||||
ContentField::ManExpr(e) => e.to_tokens(tokens),
|
||||
ContentField::Formatted(s) => tokens.append_all(quote! {
|
||||
#s
|
||||
@@ -229,30 +259,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 +281,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 +300,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,
|
||||
}
|
||||
}
|
||||
+134
-38
@@ -8,7 +8,8 @@ use syn::{
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
Ident, LitStr, Result, Token,
|
||||
token::Brace,
|
||||
Expr, Ident, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
// =======================================
|
||||
@@ -18,8 +19,8 @@ use syn::{
|
||||
pub struct Element {
|
||||
pub name: ElementName,
|
||||
pub key: Option<IfmtInput>,
|
||||
pub attributes: Vec<ElementAttrNamed>,
|
||||
pub merged_attributes: Vec<ElementAttrNamed>,
|
||||
pub attributes: Vec<AttributeType>,
|
||||
pub merged_attributes: Vec<AttributeType>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub brace: syn::token::Brace,
|
||||
}
|
||||
@@ -32,7 +33,7 @@ impl Parse for Element {
|
||||
let content: ParseBuffer;
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
||||
let mut attributes: Vec<AttributeType> = vec![];
|
||||
let mut children: Vec<BodyNode> = vec![];
|
||||
let mut key = None;
|
||||
|
||||
@@ -42,7 +43,24 @@ impl Parse for Element {
|
||||
// "def": 456,
|
||||
// abc: 123,
|
||||
loop {
|
||||
if content.peek(Token![..]) {
|
||||
content.parse::<Token![..]>()?;
|
||||
let expr = content.parse::<Expr>()?;
|
||||
let span = expr.span();
|
||||
attributes.push(attribute::AttributeType::Spread(expr));
|
||||
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
missing_trailing_comma!(span);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -50,13 +68,13 @@ impl Parse for Element {
|
||||
content.parse::<Token![:]>()?;
|
||||
|
||||
let value = content.parse::<ElementAttrValue>()?;
|
||||
attributes.push(ElementAttrNamed {
|
||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::Custom(name),
|
||||
value,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
if content.is_empty() {
|
||||
break;
|
||||
@@ -68,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>()?;
|
||||
|
||||
@@ -79,60 +99,120 @@ impl Parse for Element {
|
||||
let span = content.span();
|
||||
|
||||
if name_str.starts_with("on") {
|
||||
attributes.push(ElementAttrNamed {
|
||||
// check for any duplicate event listeners
|
||||
if attributes.iter().any(|f| {
|
||||
if let AttributeType::Named(ElementAttrNamed {
|
||||
attr:
|
||||
ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(n),
|
||||
value: ElementAttrValue::EventTokens(_),
|
||||
},
|
||||
..
|
||||
}) = f
|
||||
{
|
||||
n == &name_str
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
return Err(syn::Error::new(
|
||||
name.span(),
|
||||
format!("Duplicate event listener `{}`", name),
|
||||
));
|
||||
}
|
||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(name),
|
||||
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(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() {
|
||||
break;
|
||||
}
|
||||
|
||||
// todo: add a message saying you need to include commas between fields
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
missing_trailing_comma!(span);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// Deduplicate any attributes that can be combined
|
||||
// For example, if there are two `class` attributes, combine them into one
|
||||
let mut merged_attributes: Vec<ElementAttrNamed> = Vec::new();
|
||||
let mut merged_attributes: Vec<AttributeType> = Vec::new();
|
||||
for attr in &attributes {
|
||||
if let Some(old_attr_index) = merged_attributes
|
||||
let attr_index = merged_attributes
|
||||
.iter()
|
||||
.position(|a| a.attr.name == attr.attr.name)
|
||||
{
|
||||
.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() {
|
||||
@@ -173,15 +253,31 @@ impl ToTokens for Element {
|
||||
None => quote! { None },
|
||||
};
|
||||
|
||||
let listeners = self
|
||||
.merged_attributes
|
||||
.iter()
|
||||
.filter(|f| matches!(f.attr.value, ElementAttrValue::EventTokens { .. }));
|
||||
let listeners = self.merged_attributes.iter().filter(|f| {
|
||||
matches!(
|
||||
f,
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr: ElementAttr {
|
||||
value: ElementAttrValue::EventTokens { .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
let attr = self
|
||||
.merged_attributes
|
||||
.iter()
|
||||
.filter(|f| !matches!(f.attr.value, ElementAttrValue::EventTokens { .. }));
|
||||
let attr = self.merged_attributes.iter().filter(|f| {
|
||||
!matches!(
|
||||
f,
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr: ElementAttr {
|
||||
value: ElementAttrValue::EventTokens { .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
tokens.append_all(quote! {
|
||||
::dioxus::core::Element::new(
|
||||
|
||||
+74
-56
@@ -252,7 +252,9 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
||||
node_paths: &[ #(#node_paths),* ],
|
||||
attr_paths: &[ #(#attr_paths),* ],
|
||||
};
|
||||
::dioxus::core::VNode::new(
|
||||
|
||||
__cx.vnode(
|
||||
None.into(),
|
||||
#key_tokens,
|
||||
TEMPLATE,
|
||||
Box::new([ #( #node_printer ),* ]),
|
||||
@@ -265,7 +267,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
||||
#[cfg(feature = "hot_reload")]
|
||||
#[derive(Default, Debug)]
|
||||
struct DynamicMapping {
|
||||
attribute_to_idx: std::collections::HashMap<ElementAttr, Vec<usize>>,
|
||||
attribute_to_idx: std::collections::HashMap<AttributeType, Vec<usize>>,
|
||||
last_attribute_idx: usize,
|
||||
node_to_idx: std::collections::HashMap<BodyNode, Vec<usize>>,
|
||||
last_element_idx: usize,
|
||||
@@ -281,7 +283,7 @@ impl DynamicMapping {
|
||||
new
|
||||
}
|
||||
|
||||
fn get_attribute_idx(&mut self, attr: &ElementAttr) -> Option<usize> {
|
||||
fn get_attribute_idx(&mut self, attr: &AttributeType) -> Option<usize> {
|
||||
self.attribute_to_idx
|
||||
.get_mut(attr)
|
||||
.and_then(|idxs| idxs.pop())
|
||||
@@ -291,7 +293,7 @@ impl DynamicMapping {
|
||||
self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
|
||||
}
|
||||
|
||||
fn insert_attribute(&mut self, attr: ElementAttr) -> usize {
|
||||
fn insert_attribute(&mut self, attr: AttributeType) -> usize {
|
||||
let idx = self.last_attribute_idx;
|
||||
self.last_attribute_idx += 1;
|
||||
|
||||
@@ -313,10 +315,17 @@ impl DynamicMapping {
|
||||
match node {
|
||||
BodyNode::Element(el) => {
|
||||
for attr in el.merged_attributes {
|
||||
match &attr.attr.value {
|
||||
ElementAttrValue::AttrLiteral(input) if input.is_static() => {}
|
||||
match &attr {
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr:
|
||||
ElementAttr {
|
||||
value: ElementAttrValue::AttrLiteral(input),
|
||||
..
|
||||
},
|
||||
..
|
||||
}) if input.is_static() => {}
|
||||
_ => {
|
||||
self.insert_attribute(attr.attr);
|
||||
self.insert_attribute(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,7 +353,7 @@ impl DynamicMapping {
|
||||
#[derive(Default, Debug)]
|
||||
pub struct DynamicContext<'a> {
|
||||
dynamic_nodes: Vec<&'a BodyNode>,
|
||||
dynamic_attributes: Vec<&'a ElementAttrNamed>,
|
||||
dynamic_attributes: Vec<&'a AttributeType>,
|
||||
current_path: Vec<u8>,
|
||||
|
||||
node_paths: Vec<Vec<u8>>,
|
||||
@@ -364,10 +373,16 @@ impl<'a> DynamicContext<'a> {
|
||||
|
||||
let mut static_attrs = Vec::new();
|
||||
for attr in &el.merged_attributes {
|
||||
match &attr.attr.value {
|
||||
ElementAttrValue::AttrLiteral(value) if value.is_static() => {
|
||||
match &attr {
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr:
|
||||
ElementAttr {
|
||||
value: ElementAttrValue::AttrLiteral(value),
|
||||
name,
|
||||
},
|
||||
..
|
||||
}) if value.is_static() => {
|
||||
let value = value.source.as_ref().unwrap();
|
||||
let name = &attr.attr.name;
|
||||
let attribute_name_rust = name.to_string();
|
||||
let (name, namespace) =
|
||||
Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
|
||||
@@ -381,7 +396,7 @@ impl<'a> DynamicContext<'a> {
|
||||
|
||||
_ => {
|
||||
let idx = match mapping {
|
||||
Some(mapping) => mapping.get_attribute_idx(&attr.attr)?,
|
||||
Some(mapping) => mapping.get_attribute_idx(attr)?,
|
||||
None => self.dynamic_attributes.len(),
|
||||
};
|
||||
self.dynamic_attributes.push(attr);
|
||||
@@ -451,47 +466,50 @@ impl<'a> DynamicContext<'a> {
|
||||
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
|
||||
ElementName::Custom(_) => quote! { None },
|
||||
};
|
||||
let static_attrs = el
|
||||
.merged_attributes
|
||||
.iter()
|
||||
.map(|attr| match &attr.attr.value {
|
||||
ElementAttrValue::AttrLiteral(value) if value.is_static() => {
|
||||
let value = value.to_static().unwrap();
|
||||
let ns = {
|
||||
match &attr.attr.name {
|
||||
ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
|
||||
ElementAttrName::Custom(_) => quote!(None),
|
||||
}
|
||||
};
|
||||
let name = &attr.attr.name;
|
||||
let name = match (el_name, name) {
|
||||
(ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => {
|
||||
quote! { #el_name::#name.0 }
|
||||
}
|
||||
_ => {
|
||||
let as_string = name.to_string();
|
||||
quote! { #as_string }
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
::dioxus::core::TemplateAttribute::Static {
|
||||
name: #name,
|
||||
namespace: #ns,
|
||||
value: #value,
|
||||
let static_attrs = el.merged_attributes.iter().map(|attr| match attr {
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr:
|
||||
ElementAttr {
|
||||
value: ElementAttrValue::AttrLiteral(value),
|
||||
name,
|
||||
},
|
||||
..
|
||||
}) if value.is_static() => {
|
||||
let value = value.to_static().unwrap();
|
||||
let ns = {
|
||||
match name {
|
||||
ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
|
||||
ElementAttrName::Custom(_) => quote!(None),
|
||||
}
|
||||
};
|
||||
let name = match (el_name, name) {
|
||||
(ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => {
|
||||
quote! { #el_name::#name.0 }
|
||||
}
|
||||
_ => {
|
||||
let as_string = name.to_string();
|
||||
quote! { #as_string }
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
::dioxus::core::TemplateAttribute::Static {
|
||||
name: #name,
|
||||
namespace: #ns,
|
||||
value: #value,
|
||||
|
||||
// todo: we don't diff these so we never apply the volatile flag
|
||||
// volatile: dioxus_elements::#el_name::#name.2,
|
||||
}
|
||||
// todo: we don't diff these so we never apply the volatile flag
|
||||
// volatile: dioxus_elements::#el_name::#name.2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
let ct = self.dynamic_attributes.len();
|
||||
self.dynamic_attributes.push(attr);
|
||||
self.attr_paths.push(self.current_path.clone());
|
||||
quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
|
||||
}
|
||||
});
|
||||
_ => {
|
||||
let ct = self.dynamic_attributes.len();
|
||||
self.dynamic_attributes.push(attr);
|
||||
self.attr_paths.push(self.current_path.clone());
|
||||
quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
|
||||
}
|
||||
});
|
||||
|
||||
let attrs = quote! { #(#static_attrs),*};
|
||||
|
||||
@@ -555,7 +573,7 @@ fn create_template() {
|
||||
p {
|
||||
"hello world"
|
||||
}
|
||||
(0..10).map(|i| rsx!{"{i}"})
|
||||
{(0..10).map(|i| rsx!{"{i}"})}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -647,9 +665,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{}
|
||||
}
|
||||
};
|
||||
@@ -693,9 +711,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
@@ -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();
|
||||
___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();
|
||||
___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 {
|
||||
|
||||
Reference in new issue
Block a user