Autocomplete rsx (#2421)
* autocomplete rsx * hide the completions module * fix autocomplete for identifiers that start with a raw identifier * add field completion hints for props * remove extra import * Revert "add field completion hints for props" This reverts commit 194899c73783e0f9b5d601d53365746d838f2048. * fix autocomplete inside raw expressions * fix autofmt * feat: just use regular ifmt if it's compatible reuses the tokens for ifmt so RA provides its HIR lowering on simple formatted strings * fix merging ifmt strings * add a better error message for components called like functions --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
245003a5d4
commit
2523581cf4
21 files changed
+788
-217
No files matched your search
@@ -52,6 +52,10 @@ impl Writer<'_> {
|
|||||||
..
|
..
|
||||||
} = el;
|
} = el;
|
||||||
|
|
||||||
|
let brace = brace
|
||||||
|
.as_ref()
|
||||||
|
.expect("braces should always be present in strict mode");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
1. Write the tag
|
1. Write the tag
|
||||||
2. Write the key
|
2. Write the key
|
||||||
@@ -426,7 +430,7 @@ impl Writer<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_expr_length(expr: &Expr) -> Option<usize> {
|
fn get_expr_length(expr: &impl Spanned) -> Option<usize> {
|
||||||
let span = expr.span();
|
let span = expr.span();
|
||||||
let (start, end) = (span.start(), span.end());
|
let (start, end) = (span.start(), span.end());
|
||||||
if start.line == end.line {
|
if start.line == end.line {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use collect_macros::byte_offset;
|
|||||||
use dioxus_rsx::{BodyNode, CallBody, IfmtInput};
|
use dioxus_rsx::{BodyNode, CallBody, IfmtInput};
|
||||||
use proc_macro2::LineColumn;
|
use proc_macro2::LineColumn;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::{ExprMacro, MacroDelimiter};
|
use syn::{parse::Parser, ExprMacro, MacroDelimiter};
|
||||||
|
|
||||||
mod buffer;
|
mod buffer;
|
||||||
mod collect_macros;
|
mod collect_macros;
|
||||||
@@ -77,7 +77,7 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = item.parse_body::<CallBody>().unwrap();
|
let body = item.parse_body_with(CallBody::parse_strict).unwrap();
|
||||||
|
|
||||||
let rsx_start = macro_path.span().start();
|
let rsx_start = macro_path.span().start();
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ fn write_body(buf: &mut Writer, body: &CallBody) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
||||||
let body = syn::parse2::<CallBody>(expr.mac.tokens).unwrap();
|
let body = CallBody::parse_strict.parse2(expr.mac.tokens).unwrap();
|
||||||
|
|
||||||
let mut buf = Writer::new(raw);
|
let mut buf = Writer::new(raw);
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option<String> {
|
pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option<String> {
|
||||||
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
|
let body = CallBody::parse_strict.parse_str(block).unwrap();
|
||||||
|
|
||||||
let mut buf = Writer::new(block);
|
let mut buf = Writer::new(block);
|
||||||
|
|
||||||
|
|||||||
@@ -51,11 +51,15 @@ impl ToTokens for ComponentBody {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let completion_hints = self.completion_hints();
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
#props_struct
|
#props_struct
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#comp_fn
|
#comp_fn
|
||||||
|
|
||||||
|
#completion_hints
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,6 +225,31 @@ impl ComponentBody {
|
|||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We generate an extra enum to help us autocomplete the braces after the component.
|
||||||
|
// This is a bit of a hack, but it's the only way to get the braces to autocomplete.
|
||||||
|
fn completion_hints(&self) -> TokenStream {
|
||||||
|
let comp_fn = &self.item_fn.sig.ident;
|
||||||
|
let completions_mod = Ident::new(&format!("{}_completions", comp_fn), comp_fn.span());
|
||||||
|
|
||||||
|
let vis = &self.item_fn.vis;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
mod #completions_mod {
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
/// This enum is generated to help autocomplete the braces after the component. It does nothing
|
||||||
|
pub enum Component {
|
||||||
|
#comp_fn {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#vis use #completions_mod::Component::#comp_fn;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DocField<'a> {
|
struct DocField<'a> {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ pub mod prelude {
|
|||||||
pub use dioxus_html as dioxus_elements;
|
pub use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
#[cfg(feature = "html")]
|
#[cfg(feature = "html")]
|
||||||
pub use dioxus_elements::{prelude::*, GlobalAttributes, SvgAttributes};
|
pub use dioxus_elements::{global_attributes, prelude::*, svg_attributes};
|
||||||
|
|
||||||
pub use dioxus_core;
|
pub use dioxus_core;
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ pub mod prelude {
|
|||||||
|
|
||||||
#[cfg(feature = "html")]
|
#[cfg(feature = "html")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "html")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "html")))]
|
||||||
pub use dioxus_elements::{prelude::*, GlobalAttributes, SvgAttributes};
|
pub use dioxus_elements::{global_attributes, prelude::*, svg_attributes};
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")),
|
not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")),
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ pub fn impl_extension_attributes(input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ImplExtensionAttributes {
|
struct ImplExtensionAttributes {
|
||||||
is_element: bool,
|
|
||||||
name: Ident,
|
name: Ident,
|
||||||
attrs: Punctuated<Ident, Token![,]>,
|
attrs: Punctuated<Ident, Token![,]>,
|
||||||
}
|
}
|
||||||
@@ -23,16 +22,11 @@ impl Parse for ImplExtensionAttributes {
|
|||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
let element: Ident = input.parse()?;
|
|
||||||
let name = input.parse()?;
|
let name = input.parse()?;
|
||||||
braced!(content in input);
|
braced!(content in input);
|
||||||
let attrs = content.parse_terminated(Ident::parse, Token![,])?;
|
let attrs = content.parse_terminated(Ident::parse, Token![,])?;
|
||||||
|
|
||||||
Ok(ImplExtensionAttributes {
|
Ok(ImplExtensionAttributes { name, attrs })
|
||||||
is_element: element == "ELEMENT",
|
|
||||||
name,
|
|
||||||
attrs,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,22 +38,10 @@ impl ToTokens for ImplExtensionAttributes {
|
|||||||
.strip_prefix("r#")
|
.strip_prefix("r#")
|
||||||
.unwrap_or(&name_string)
|
.unwrap_or(&name_string)
|
||||||
.to_case(Case::UpperCamel);
|
.to_case(Case::UpperCamel);
|
||||||
let impl_name = Ident::new(format!("{}Impl", &camel_name).as_str(), name.span());
|
|
||||||
let extension_name = Ident::new(format!("{}Extension", &camel_name).as_str(), name.span());
|
let extension_name = Ident::new(format!("{}Extension", &camel_name).as_str(), name.span());
|
||||||
|
|
||||||
if !self.is_element {
|
|
||||||
tokens.append_all(quote! {
|
|
||||||
struct #impl_name;
|
|
||||||
impl #name for #impl_name {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let impls = self.attrs.iter().map(|ident| {
|
let impls = self.attrs.iter().map(|ident| {
|
||||||
let d = if self.is_element {
|
let d = quote! { #name::#ident };
|
||||||
quote! { #name::#ident }
|
|
||||||
} else {
|
|
||||||
quote! { <#impl_name as #name>::#ident }
|
|
||||||
};
|
|
||||||
quote! {
|
quote! {
|
||||||
fn #ident(self, value: impl IntoAttributeValue) -> Self {
|
fn #ident(self, value: impl IntoAttributeValue) -> Self {
|
||||||
let d = #d;
|
let d = #d;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use dioxus_html_internal_macro::impl_extension_attributes;
|
|||||||
use crate::AttributeDiscription;
|
use crate::AttributeDiscription;
|
||||||
|
|
||||||
#[cfg(feature = "hot-reload-context")]
|
#[cfg(feature = "hot-reload-context")]
|
||||||
macro_rules! trait_method_mapping {
|
macro_rules! mod_method_mapping {
|
||||||
(
|
(
|
||||||
$matching:ident;
|
$matching:ident;
|
||||||
$(#[$attr:meta])*
|
$(#[$attr:meta])*
|
||||||
@@ -68,11 +68,11 @@ macro_rules! html_to_rsx_attribute_mapping {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! trait_methods {
|
macro_rules! mod_methods {
|
||||||
(
|
(
|
||||||
@base
|
@base
|
||||||
$(#[$trait_attr:meta])*
|
$(#[$mod_attr:meta])*
|
||||||
$trait:ident;
|
$mod:ident;
|
||||||
$fn:ident;
|
$fn:ident;
|
||||||
$fn_html_to_rsx:ident;
|
$fn_html_to_rsx:ident;
|
||||||
$(
|
$(
|
||||||
@@ -80,18 +80,19 @@ macro_rules! trait_methods {
|
|||||||
$name:ident $(: $($arg:literal),*)*;
|
$name:ident $(: $($arg:literal),*)*;
|
||||||
)+
|
)+
|
||||||
) => {
|
) => {
|
||||||
$(#[$trait_attr])*
|
$(#[$mod_attr])*
|
||||||
pub trait $trait {
|
pub mod $mod {
|
||||||
|
use super::*;
|
||||||
$(
|
$(
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
const $name: AttributeDiscription = trait_methods! { $name $(: $($arg),*)*; };
|
pub const $name: AttributeDiscription = mod_methods! { $name $(: $($arg),*)*; };
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "hot-reload-context")]
|
#[cfg(feature = "hot-reload-context")]
|
||||||
pub(crate) fn $fn(attr: &str) -> Option<(&'static str, Option<&'static str>)> {
|
pub(crate) fn $fn(attr: &str) -> Option<(&'static str, Option<&'static str>)> {
|
||||||
$(
|
$(
|
||||||
trait_method_mapping! {
|
mod_method_mapping! {
|
||||||
attr;
|
attr;
|
||||||
$name$(: $($arg),*)*;
|
$name$(: $($arg),*)*;
|
||||||
}
|
}
|
||||||
@@ -111,7 +112,7 @@ macro_rules! trait_methods {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_extension_attributes![GLOBAL $trait { $($name,)* }];
|
impl_extension_attributes![$mod { $($name,)* }];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rename the incoming ident and apply a custom namespace
|
// Rename the incoming ident and apply a custom namespace
|
||||||
@@ -124,10 +125,10 @@ macro_rules! trait_methods {
|
|||||||
( $name:ident; ) => { (stringify!($name), None, false) };
|
( $name:ident; ) => { (stringify!($name), None, false) };
|
||||||
}
|
}
|
||||||
|
|
||||||
trait_methods! {
|
mod_methods! {
|
||||||
@base
|
@base
|
||||||
|
|
||||||
GlobalAttributes;
|
global_attributes;
|
||||||
map_global_attributes;
|
map_global_attributes;
|
||||||
map_html_global_attributes_to_rsx;
|
map_html_global_attributes_to_rsx;
|
||||||
|
|
||||||
@@ -1640,9 +1641,9 @@ trait_methods! {
|
|||||||
aria_setsize: "aria-setsize";
|
aria_setsize: "aria-setsize";
|
||||||
}
|
}
|
||||||
|
|
||||||
trait_methods! {
|
mod_methods! {
|
||||||
@base
|
@base
|
||||||
SvgAttributes;
|
svg_attributes;
|
||||||
map_svg_attributes;
|
map_svg_attributes;
|
||||||
map_html_svg_attributes_to_rsx;
|
map_html_svg_attributes_to_rsx;
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ use dioxus_rsx::HotReloadingContext;
|
|||||||
|
|
||||||
#[cfg(feature = "hot-reload-context")]
|
#[cfg(feature = "hot-reload-context")]
|
||||||
use crate::{map_global_attributes, map_svg_attributes};
|
use crate::{map_global_attributes, map_svg_attributes};
|
||||||
use crate::{GlobalAttributes, SvgAttributes};
|
|
||||||
|
|
||||||
pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
|
pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
|
||||||
|
|
||||||
@@ -115,9 +114,11 @@ macro_rules! impl_element {
|
|||||||
) => {
|
) => {
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
pub struct $name;
|
pub mod $name {
|
||||||
|
#[allow(unused)]
|
||||||
|
use super::*;
|
||||||
|
pub use crate::attribute_groups::global_attributes::*;
|
||||||
|
|
||||||
impl $name {
|
|
||||||
pub const TAG_NAME: &'static str = stringify!($name);
|
pub const TAG_NAME: &'static str = stringify!($name);
|
||||||
pub const NAME_SPACE: Option<&'static str> = None;
|
pub const NAME_SPACE: Option<&'static str> = None;
|
||||||
|
|
||||||
@@ -128,8 +129,6 @@ macro_rules! impl_element {
|
|||||||
);
|
);
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalAttributes for $name {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -141,13 +140,12 @@ macro_rules! impl_element {
|
|||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
pub struct $name;
|
pub mod $name {
|
||||||
|
#[allow(unused)]
|
||||||
|
use super::*;
|
||||||
|
pub use crate::attribute_groups::svg_attributes::*;
|
||||||
|
|
||||||
impl SvgAttributes for $name {}
|
|
||||||
|
|
||||||
impl $name {
|
|
||||||
pub const TAG_NAME: &'static str = stringify!($name);
|
pub const TAG_NAME: &'static str = stringify!($name);
|
||||||
pub const NAME_SPACE: Option<&'static str> = Some($namespace);
|
pub const NAME_SPACE: Option<&'static str> = Some($namespace);
|
||||||
|
|
||||||
@@ -171,11 +169,11 @@ macro_rules! impl_element {
|
|||||||
) => {
|
) => {
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
pub struct $element;
|
pub mod $element {
|
||||||
|
#[allow(unused)]
|
||||||
|
use super::*;
|
||||||
|
pub use crate::attribute_groups::svg_attributes::*;
|
||||||
|
|
||||||
impl SvgAttributes for $element {}
|
|
||||||
|
|
||||||
impl $element {
|
|
||||||
pub const TAG_NAME: &'static str = $name;
|
pub const TAG_NAME: &'static str = $name;
|
||||||
pub const NAME_SPACE: Option<&'static str> = Some($namespace);
|
pub const NAME_SPACE: Option<&'static str> = Some($namespace);
|
||||||
|
|
||||||
@@ -384,10 +382,23 @@ macro_rules! builder_constructors {
|
|||||||
);
|
);
|
||||||
)*
|
)*
|
||||||
|
|
||||||
|
/// This module contains helpers for rust analyzer autocompletion
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub mod completions {
|
||||||
|
/// This helper tells rust analyzer that it should autocomplete the element name with braces.
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum CompleteWithBraces {
|
||||||
|
$(
|
||||||
|
$(#[$attr])*
|
||||||
|
$name {}
|
||||||
|
),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) mod extensions {
|
pub(crate) mod extensions {
|
||||||
use super::*;
|
use super::*;
|
||||||
$(
|
$(
|
||||||
impl_extension_attributes![ELEMENT $name { $($fil,)* }];
|
impl_extension_attributes![$name { $($fil,)* }];
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Currently, we don't validate for structures, but do validate attributes.
|
//! Currently, we don't validate for structures, but do validate attributes.
|
||||||
|
|
||||||
mod elements;
|
pub mod elements;
|
||||||
#[cfg(feature = "hot-reload-context")]
|
#[cfg(feature = "hot-reload-context")]
|
||||||
pub use elements::HtmlCtx;
|
pub use elements::HtmlCtx;
|
||||||
#[cfg(feature = "html-to-rsx")]
|
#[cfg(feature = "html-to-rsx")]
|
||||||
@@ -24,8 +24,8 @@ pub use elements::{map_html_attribute_to_rsx, map_html_element_to_rsx};
|
|||||||
pub mod events;
|
pub mod events;
|
||||||
pub(crate) mod file_data;
|
pub(crate) mod file_data;
|
||||||
pub use file_data::*;
|
pub use file_data::*;
|
||||||
|
mod attribute_groups;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
mod global_attributes;
|
|
||||||
pub mod input_data;
|
pub mod input_data;
|
||||||
#[cfg(feature = "native-bind")]
|
#[cfg(feature = "native-bind")]
|
||||||
pub mod native_bind;
|
pub mod native_bind;
|
||||||
@@ -40,25 +40,25 @@ mod transit;
|
|||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
pub use transit::*;
|
pub use transit::*;
|
||||||
|
|
||||||
|
pub use attribute_groups::*;
|
||||||
pub use elements::*;
|
pub use elements::*;
|
||||||
pub use events::*;
|
pub use events::*;
|
||||||
pub use global_attributes::*;
|
|
||||||
pub use render_template::*;
|
pub use render_template::*;
|
||||||
|
|
||||||
#[cfg(feature = "eval")]
|
#[cfg(feature = "eval")]
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
|
|
||||||
pub mod extensions {
|
pub mod extensions {
|
||||||
|
pub use crate::attribute_groups::{GlobalAttributesExtension, SvgAttributesExtension};
|
||||||
pub use crate::elements::extensions::*;
|
pub use crate::elements::extensions::*;
|
||||||
pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
pub use crate::attribute_groups::{GlobalAttributesExtension, SvgAttributesExtension};
|
||||||
pub use crate::elements::extensions::*;
|
pub use crate::elements::extensions::*;
|
||||||
#[cfg(feature = "eval")]
|
#[cfg(feature = "eval")]
|
||||||
pub use crate::eval::*;
|
pub use crate::eval::*;
|
||||||
pub use crate::events::*;
|
pub use crate::events::*;
|
||||||
pub use crate::global_attributes::{GlobalAttributesExtension, SvgAttributesExtension};
|
|
||||||
pub use crate::point_interaction::*;
|
pub use crate::point_interaction::*;
|
||||||
pub use keyboard_types::{self, Code, Key, Location, Modifiers};
|
pub use keyboard_types::{self, Code, Key, Location, Modifiers};
|
||||||
}
|
}
|
||||||
@@ -64,50 +64,46 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AttributeType::Named(ElementAttrNamed {
|
AttributeType::Named(ElementAttrNamed::new(el_name.clone(), attr))
|
||||||
el_name: el_name.clone(),
|
|
||||||
attr,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let class = el.classes.join(" ");
|
let class = el.classes.join(" ");
|
||||||
if !class.is_empty() {
|
if !class.is_empty() {
|
||||||
attributes.push(AttributeType::Named(ElementAttrNamed {
|
attributes.push(AttributeType::Named(ElementAttrNamed::new(
|
||||||
el_name: el_name.clone(),
|
el_name.clone(),
|
||||||
attr: ElementAttr {
|
ElementAttr {
|
||||||
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
|
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
|
||||||
"class",
|
"class",
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
)),
|
)),
|
||||||
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(&class)),
|
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(&class)),
|
||||||
},
|
},
|
||||||
}));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = &el.id {
|
if let Some(id) = &el.id {
|
||||||
attributes.push(AttributeType::Named(ElementAttrNamed {
|
attributes.push(AttributeType::Named(ElementAttrNamed::new(
|
||||||
el_name: el_name.clone(),
|
el_name.clone(),
|
||||||
attr: ElementAttr {
|
ElementAttr {
|
||||||
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
|
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
|
||||||
"id",
|
"id",
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
)),
|
)),
|
||||||
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(id)),
|
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(id)),
|
||||||
},
|
},
|
||||||
}));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let children = el.children.iter().filter_map(rsx_node_from_html).collect();
|
let children = el.children.iter().filter_map(rsx_node_from_html).collect();
|
||||||
|
|
||||||
Some(BodyNode::Element(Element {
|
Some(BodyNode::Element(Element::new(
|
||||||
name: el_name,
|
None,
|
||||||
children,
|
el_name,
|
||||||
attributes,
|
attributes,
|
||||||
merged_attributes: Default::default(),
|
children,
|
||||||
key: None,
|
Default::default(),
|
||||||
brace: Default::default(),
|
)))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We ignore comments
|
// We ignore comments
|
||||||
@@ -132,19 +128,18 @@ pub fn collect_svgs(children: &mut [BodyNode], out: &mut Vec<BodyNode>) {
|
|||||||
segments.push(new_name.clone().into());
|
segments.push(new_name.clone().into());
|
||||||
|
|
||||||
// Replace this instance with a component
|
// Replace this instance with a component
|
||||||
let mut new_comp = BodyNode::Component(Component {
|
let mut new_comp = BodyNode::Component(Component::new(
|
||||||
name: syn::Path {
|
syn::Path {
|
||||||
leading_colon: None,
|
leading_colon: None,
|
||||||
segments,
|
segments,
|
||||||
},
|
},
|
||||||
prop_gen_args: None,
|
None,
|
||||||
fields: vec![],
|
vec![],
|
||||||
children: vec![],
|
vec![],
|
||||||
manual_props: None,
|
None,
|
||||||
key: None,
|
None,
|
||||||
brace: Default::default(),
|
Default::default(),
|
||||||
location: Default::default(),
|
));
|
||||||
});
|
|
||||||
|
|
||||||
std::mem::swap(child, &mut new_comp);
|
std::mem::swap(child, &mut new_comp);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter};
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::quote;
|
use quote::{quote, quote_spanned};
|
||||||
use syn::{parse_quote, spanned::Spanned, Expr, ExprIf, Ident, LitStr};
|
use syn::{parse_quote, spanned::Spanned, Expr, ExprIf, Ident, LitStr};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
@@ -95,6 +95,9 @@ impl AttributeType {
|
|||||||
pub struct ElementAttrNamed {
|
pub struct ElementAttrNamed {
|
||||||
pub el_name: ElementName,
|
pub el_name: ElementName,
|
||||||
pub attr: ElementAttr,
|
pub attr: ElementAttr,
|
||||||
|
// If this is the last attribute of an element and it doesn't have a tailing comma,
|
||||||
|
// we add hints so that rust analyzer completes it either as an attribute or element
|
||||||
|
pub(crate) followed_by_comma: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for ElementAttrNamed {
|
impl Hash for ElementAttrNamed {
|
||||||
@@ -112,6 +115,15 @@ impl PartialEq for ElementAttrNamed {
|
|||||||
impl Eq for ElementAttrNamed {}
|
impl Eq for ElementAttrNamed {}
|
||||||
|
|
||||||
impl ElementAttrNamed {
|
impl ElementAttrNamed {
|
||||||
|
/// Create a new ElementAttrNamed
|
||||||
|
pub fn new(el_name: ElementName, attr: ElementAttr) -> Self {
|
||||||
|
Self {
|
||||||
|
el_name,
|
||||||
|
attr,
|
||||||
|
followed_by_comma: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
|
pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
|
||||||
if self.el_name == other.el_name && self.attr.name == other.attr.name {
|
if self.el_name == other.el_name && self.attr.name == other.attr.name {
|
||||||
if let Some(separator) = self.attr.name.multi_attribute_separator() {
|
if let Some(separator) = self.attr.name.multi_attribute_separator() {
|
||||||
@@ -121,16 +133,64 @@ impl ElementAttrNamed {
|
|||||||
name: self.attr.name.clone(),
|
name: self.attr.name.clone(),
|
||||||
value: self.attr.value.combine(separator, &other.attr.value),
|
value: self.attr.value.combine(separator, &other.attr.value),
|
||||||
},
|
},
|
||||||
|
followed_by_comma: self.followed_by_comma || other.followed_by_comma,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this is the last attribute of an element and it doesn't have a tailing comma,
|
||||||
|
/// we add hints so that rust analyzer completes it either as an attribute or element
|
||||||
|
fn completion_hints(&self) -> TokenStream2 {
|
||||||
|
let ElementAttrNamed {
|
||||||
|
el_name,
|
||||||
|
attr,
|
||||||
|
followed_by_comma,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
// If there is a trailing comma, rust analyzer does a good job of completing the attribute by itself
|
||||||
|
if *followed_by_comma {
|
||||||
|
return quote! {};
|
||||||
|
}
|
||||||
|
// Only add hints if the attribute is:
|
||||||
|
// - a built in attribute (not a literal)
|
||||||
|
// - an build in element (not a custom element)
|
||||||
|
// - a shorthand attribute
|
||||||
|
let (
|
||||||
|
ElementName::Ident(el),
|
||||||
|
ElementAttrName::BuiltIn(name),
|
||||||
|
ElementAttrValue::Shorthand(_),
|
||||||
|
) = (el_name, &attr.name, &attr.value)
|
||||||
|
else {
|
||||||
|
return quote! {};
|
||||||
|
};
|
||||||
|
// If the attribute is a shorthand attribute, but it is an event handler, rust analyzer already does a good job of completing the attribute by itself
|
||||||
|
if name.to_string().starts_with("on") {
|
||||||
|
return quote! {};
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
mod __completions {
|
||||||
|
// Autocomplete as an attribute
|
||||||
|
pub use super::dioxus_elements::#el::*;
|
||||||
|
// Autocomplete as an element
|
||||||
|
pub use super::dioxus_elements::elements::completions::CompleteWithBraces::*;
|
||||||
|
fn ignore() {
|
||||||
|
#name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ElementAttrNamed {
|
impl ToTokens for ElementAttrNamed {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let ElementAttrNamed { el_name, attr } = self;
|
let ElementAttrNamed { el_name, attr, .. } = self;
|
||||||
|
|
||||||
let ns = |name: &ElementAttrName| match (el_name, name) {
|
let ns = |name: &ElementAttrName| match (el_name, name) {
|
||||||
(ElementName::Ident(i), ElementAttrName::BuiltIn(_)) => {
|
(ElementName::Ident(i), ElementAttrName::BuiltIn(_)) => {
|
||||||
@@ -186,19 +246,25 @@ impl ToTokens for ElementAttrNamed {
|
|||||||
}
|
}
|
||||||
ElementAttrValue::EventTokens(tokens) => match &self.attr.name {
|
ElementAttrValue::EventTokens(tokens) => match &self.attr.name {
|
||||||
ElementAttrName::BuiltIn(name) => {
|
ElementAttrName::BuiltIn(name) => {
|
||||||
quote! {
|
quote_spanned! { tokens.span() =>
|
||||||
dioxus_elements::events::#name(#tokens)
|
dioxus_elements::events::#name(#tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ElementAttrName::Custom(_) => unreachable!("Handled elsewhere in the macro"),
|
ElementAttrName::Custom(_) => unreachable!("Handled elsewhere in the macro"),
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
quote! { dioxus_elements::events::#value(#value) }
|
quote_spanned! { value.span() => dioxus_elements::events::#value(#value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tokens.append_all(attribute);
|
let completion_hints = self.completion_hints();
|
||||||
|
tokens.append_all(quote! {
|
||||||
|
{
|
||||||
|
#completion_hints
|
||||||
|
#attribute
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,6 +355,11 @@ impl ToTokens for ElementAttrValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ElementAttrValue {
|
impl ElementAttrValue {
|
||||||
|
/// Create a new ElementAttrValue::Shorthand from an Ident and normalize the identifier
|
||||||
|
pub(crate) fn shorthand(name: &Ident) -> Self {
|
||||||
|
Self::Shorthand(normalize_raw_ident(name))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_shorthand(&self) -> bool {
|
pub fn is_shorthand(&self) -> bool {
|
||||||
matches!(self, ElementAttrValue::Shorthand(_))
|
matches!(self, ElementAttrValue::Shorthand(_))
|
||||||
}
|
}
|
||||||
@@ -388,6 +459,16 @@ impl ElementAttrValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and normalize a built-in attribute name
|
||||||
|
// If the identifier is a reserved keyword, this method will create a raw identifier
|
||||||
|
fn normalize_raw_ident(ident: &Ident) -> Ident {
|
||||||
|
if syn::parse2::<syn::Ident>(ident.to_token_stream()).is_err() {
|
||||||
|
syn::Ident::new_raw(&ident.to_string(), ident.span())
|
||||||
|
} else {
|
||||||
|
ident.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
pub enum ElementAttrName {
|
pub enum ElementAttrName {
|
||||||
BuiltIn(Ident),
|
BuiltIn(Ident),
|
||||||
@@ -395,6 +476,10 @@ pub enum ElementAttrName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ElementAttrName {
|
impl ElementAttrName {
|
||||||
|
pub(crate) fn built_in(name: &Ident) -> Self {
|
||||||
|
Self::BuiltIn(normalize_raw_ident(name))
|
||||||
|
}
|
||||||
|
|
||||||
fn multi_attribute_separator(&self) -> Option<&'static str> {
|
fn multi_attribute_separator(&self) -> Option<&'static str> {
|
||||||
match self {
|
match self {
|
||||||
ElementAttrName::BuiltIn(i) => match i.to_string().as_str() {
|
ElementAttrName::BuiltIn(i) => match i.to_string().as_str() {
|
||||||
|
|||||||
+123
-19
@@ -11,18 +11,18 @@
|
|||||||
//! - [ ] Keys
|
//! - [ ] Keys
|
||||||
//! - [ ] Properties spreading with with `..` syntax
|
//! - [ ] Properties spreading with with `..` syntax
|
||||||
|
|
||||||
use self::location::CallerLocation;
|
use self::{location::CallerLocation, util::try_parse_braces};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::{quote, quote_spanned};
|
||||||
use syn::{
|
use syn::{
|
||||||
ext::IdentExt, parse::ParseBuffer, spanned::Spanned, token::Brace,
|
ext::IdentExt, spanned::Spanned, token::Brace, AngleBracketedGenericArguments, Error, Expr,
|
||||||
AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Token,
|
Ident, LitStr, PathArguments, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Component {
|
pub struct Component {
|
||||||
pub name: syn::Path,
|
pub name: syn::Path,
|
||||||
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
|
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
|
||||||
@@ -30,8 +30,35 @@ pub struct Component {
|
|||||||
pub fields: Vec<ComponentField>,
|
pub fields: Vec<ComponentField>,
|
||||||
pub children: Vec<BodyNode>,
|
pub children: Vec<BodyNode>,
|
||||||
pub manual_props: Option<Expr>,
|
pub manual_props: Option<Expr>,
|
||||||
pub brace: syn::token::Brace,
|
pub brace: Option<syn::token::Brace>,
|
||||||
pub location: CallerLocation,
|
pub location: CallerLocation,
|
||||||
|
errors: Vec<syn::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Component {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.name == other.name
|
||||||
|
&& self.prop_gen_args == other.prop_gen_args
|
||||||
|
&& self.key == other.key
|
||||||
|
&& self.fields == other.fields
|
||||||
|
&& self.children == other.children
|
||||||
|
&& self.manual_props == other.manual_props
|
||||||
|
&& self.brace == other.brace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Component {}
|
||||||
|
|
||||||
|
impl Hash for Component {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.name.hash(state);
|
||||||
|
self.prop_gen_args.hash(state);
|
||||||
|
self.key.hash(state);
|
||||||
|
self.fields.hash(state);
|
||||||
|
self.children.hash(state);
|
||||||
|
self.manual_props.hash(state);
|
||||||
|
self.brace.hash(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Component {
|
impl Parse for Component {
|
||||||
@@ -42,8 +69,10 @@ impl Parse for Component {
|
|||||||
// extract the path arguments from the path into prop_gen_args
|
// extract the path arguments from the path into prop_gen_args
|
||||||
let prop_gen_args = normalize_path(&mut name);
|
let prop_gen_args = normalize_path(&mut name);
|
||||||
|
|
||||||
let content: ParseBuffer;
|
let Ok((brace, content)) = try_parse_braces(stream) else {
|
||||||
let brace = syn::braced!(content in stream);
|
// If there are no braces, this is an incomplete component. We still parse it so that we can autocomplete it, but we don't need to parse the children
|
||||||
|
return Ok(Self::incomplete(name));
|
||||||
|
};
|
||||||
|
|
||||||
let mut fields = Vec::new();
|
let mut fields = Vec::new();
|
||||||
let mut children = Vec::new();
|
let mut children = Vec::new();
|
||||||
@@ -65,7 +94,7 @@ impl Parse for Component {
|
|||||||
&& !content.peek2(Token![:])
|
&& !content.peek2(Token![:])
|
||||||
&& !content.peek2(Token![-]))
|
&& !content.peek2(Token![-]))
|
||||||
{
|
{
|
||||||
// If it
|
// If it is a key, make sure it isn't static and then add it to the component
|
||||||
if content.fork().parse::<Ident>()? == "key" {
|
if content.fork().parse::<Ident>()? == "key" {
|
||||||
_ = content.parse::<Ident>()?;
|
_ = content.parse::<Ident>()?;
|
||||||
_ = content.parse::<Token![:]>()?;
|
_ = content.parse::<Token![:]>()?;
|
||||||
@@ -94,8 +123,9 @@ impl Parse for Component {
|
|||||||
fields,
|
fields,
|
||||||
children,
|
children,
|
||||||
manual_props,
|
manual_props,
|
||||||
brace,
|
brace: Some(brace),
|
||||||
key,
|
key,
|
||||||
|
errors: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,19 +146,78 @@ impl ToTokens for Component {
|
|||||||
|
|
||||||
let fn_name = self.fn_name();
|
let fn_name = self.fn_name();
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
let errors = self.errors();
|
||||||
|
|
||||||
|
let component_node = quote_spanned! { name.span() =>
|
||||||
dioxus_core::DynamicNode::Component({
|
dioxus_core::DynamicNode::Component({
|
||||||
|
#[allow(unused_imports)]
|
||||||
use dioxus_core::prelude::Properties;
|
use dioxus_core::prelude::Properties;
|
||||||
(#builder).into_vcomponent(
|
(#builder).into_vcomponent(
|
||||||
#name #prop_gen_args,
|
#name #prop_gen_args,
|
||||||
#fn_name
|
#fn_name
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
};
|
||||||
|
|
||||||
|
let component = if errors.is_empty() {
|
||||||
|
component_node
|
||||||
|
} else {
|
||||||
|
quote_spanned! {
|
||||||
|
name.span() => {
|
||||||
|
#errors
|
||||||
|
#component_node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tokens.append_all(component);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component {
|
impl Component {
|
||||||
|
/// Create a new Component
|
||||||
|
pub fn new(
|
||||||
|
name: syn::Path,
|
||||||
|
prop_gen_args: Option<AngleBracketedGenericArguments>,
|
||||||
|
fields: Vec<ComponentField>,
|
||||||
|
children: Vec<BodyNode>,
|
||||||
|
manual_props: Option<Expr>,
|
||||||
|
key: Option<IfmtInput>,
|
||||||
|
brace: syn::token::Brace,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
errors: vec![],
|
||||||
|
name,
|
||||||
|
prop_gen_args,
|
||||||
|
fields,
|
||||||
|
children,
|
||||||
|
manual_props,
|
||||||
|
brace: Some(brace),
|
||||||
|
key,
|
||||||
|
location: CallerLocation::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn incomplete(name: syn::Path) -> Self {
|
||||||
|
Self {
|
||||||
|
errors: vec![syn::Error::new(
|
||||||
|
name.span(),
|
||||||
|
format!(
|
||||||
|
"Missing braces after component name `{}`",
|
||||||
|
name.segments.last().unwrap().ident
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
name,
|
||||||
|
prop_gen_args: None,
|
||||||
|
fields: Vec::new(),
|
||||||
|
children: Vec::new(),
|
||||||
|
manual_props: None,
|
||||||
|
brace: None,
|
||||||
|
key: None,
|
||||||
|
location: CallerLocation::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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
|
// ensure path segments doesn't have PathArguments, only the last
|
||||||
// segment is allowed to have one.
|
// segment is allowed to have one.
|
||||||
@@ -157,15 +246,18 @@ impl Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn collect_manual_props(&self, manual_props: &Expr) -> TokenStream2 {
|
fn collect_manual_props(&self, manual_props: &Expr) -> TokenStream2 {
|
||||||
let mut toks = quote! { let mut __manual_props = #manual_props; };
|
let mut toks =
|
||||||
|
quote_spanned! { manual_props.span() => let mut __manual_props = #manual_props; };
|
||||||
for field in &self.fields {
|
for field in &self.fields {
|
||||||
if field.name == "key" {
|
if field.name == "key" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let ComponentField { name, content } = field;
|
let ComponentField { name, content } = field;
|
||||||
toks.append_all(quote! { __manual_props.#name = #content; });
|
toks.append_all(
|
||||||
|
quote_spanned! { manual_props.span() => __manual_props.#name = #content; },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
toks.append_all(quote! { __manual_props });
|
toks.append_all(quote_spanned! { manual_props.span() => __manual_props });
|
||||||
quote! {{ #toks }}
|
quote! {{ #toks }}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,23 +265,35 @@ impl Component {
|
|||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
|
|
||||||
let mut toks = match &self.prop_gen_args {
|
let mut toks = match &self.prop_gen_args {
|
||||||
Some(gen_args) => quote! { fc_to_builder(#name #gen_args) },
|
Some(gen_args) => quote_spanned! { name.span() => fc_to_builder(#name #gen_args) },
|
||||||
None => quote! { fc_to_builder(#name) },
|
None => quote_spanned! { name.span() => fc_to_builder(#name) },
|
||||||
};
|
};
|
||||||
for field in &self.fields {
|
for field in &self.fields {
|
||||||
toks.append_all(quote! {#field})
|
toks.append_all(quote! {#field})
|
||||||
}
|
}
|
||||||
if !self.children.is_empty() {
|
if !self.children.is_empty() {
|
||||||
let renderer = TemplateRenderer::as_tokens(&self.children, None);
|
let renderer = TemplateRenderer::as_tokens(&self.children, None);
|
||||||
toks.append_all(quote! { .children( { #renderer } ) });
|
toks.append_all(quote_spanned! { name.span() => .children( #renderer ) });
|
||||||
}
|
}
|
||||||
toks.append_all(quote! { .build() });
|
toks.append_all(quote_spanned! { name.span() => .build() });
|
||||||
toks
|
toks
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fn_name(&self) -> String {
|
fn fn_name(&self) -> String {
|
||||||
self.name.segments.last().unwrap().ident.to_string()
|
self.name.segments.last().unwrap().ident.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this element is only partially complete, return the errors that occurred during parsing
|
||||||
|
pub(crate) fn errors(&self) -> TokenStream2 {
|
||||||
|
let Self { errors, .. } = self;
|
||||||
|
|
||||||
|
let mut tokens = quote! {};
|
||||||
|
for error in errors {
|
||||||
|
tokens.append_all(error.to_compile_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the struct's fields info
|
// the struct's fields info
|
||||||
|
|||||||
+22
-12
@@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::quote;
|
use quote::{quote, quote_spanned};
|
||||||
|
|
||||||
/// As we create the dynamic nodes, we want to keep track of them in a linear fashion
|
/// As we create the dynamic nodes, we want to keep track of them in a linear fashion
|
||||||
/// We'll use the size of the vecs to determine the index of the dynamic node in the final output
|
/// We'll use the size of the vecs to determine the index of the dynamic node in the final output
|
||||||
@@ -112,15 +112,13 @@ impl<'a> DynamicContext<'a> {
|
|||||||
|
|
||||||
fn render_static_element(&mut self, el: &'a Element) -> TokenStream2 {
|
fn render_static_element(&mut self, el: &'a Element) -> TokenStream2 {
|
||||||
let el_name = &el.name;
|
let el_name = &el.name;
|
||||||
let ns = |name| match el_name {
|
let ns = el_name.namespace();
|
||||||
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
|
let span = el_name.span();
|
||||||
ElementName::Custom(_) => quote! { None },
|
|
||||||
};
|
|
||||||
|
|
||||||
let static_attrs = el
|
let static_attrs = el
|
||||||
.merged_attributes
|
.merged_attributes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|attr| self.render_merged_attributes(attr, ns, el_name))
|
.map(|attr| self.render_merged_attributes(attr, el_name))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let children = el
|
let children = el
|
||||||
@@ -130,16 +128,30 @@ impl<'a> DynamicContext<'a> {
|
|||||||
.map(|(idx, root)| self.render_children_nodes(idx, root))
|
.map(|(idx, root)| self.render_children_nodes(idx, root))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let ns = ns(quote!(NAME_SPACE));
|
|
||||||
let el_name = el_name.tag_name();
|
let el_name = el_name.tag_name();
|
||||||
|
let completion_hints = el.completion_hints();
|
||||||
|
let errors = el.errors();
|
||||||
|
|
||||||
quote! {
|
let element = quote_spanned! {
|
||||||
|
span =>
|
||||||
dioxus_core::TemplateNode::Element {
|
dioxus_core::TemplateNode::Element {
|
||||||
tag: #el_name,
|
tag: #el_name,
|
||||||
namespace: #ns,
|
namespace: #ns,
|
||||||
attrs: &[ #(#static_attrs)* ],
|
attrs: &[ #(#static_attrs)* ],
|
||||||
children: &[ #(#children),* ],
|
children: &[ #(#children),* ],
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if errors.is_empty() && completion_hints.is_empty() {
|
||||||
|
element
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
#completion_hints
|
||||||
|
#errors
|
||||||
|
#element
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,13 +166,12 @@ impl<'a> DynamicContext<'a> {
|
|||||||
fn render_merged_attributes(
|
fn render_merged_attributes(
|
||||||
&mut self,
|
&mut self,
|
||||||
attr: &'a AttributeType,
|
attr: &'a AttributeType,
|
||||||
ns: impl Fn(TokenStream2) -> TokenStream2,
|
|
||||||
el_name: &ElementName,
|
el_name: &ElementName,
|
||||||
) -> TokenStream2 {
|
) -> TokenStream2 {
|
||||||
// Rendering static attributes requires a bit more work than just a dynamic attrs
|
// Rendering static attributes requires a bit more work than just a dynamic attrs
|
||||||
match attr.as_static_str_literal() {
|
match attr.as_static_str_literal() {
|
||||||
// If it's static, we'll take this little optimization
|
// If it's static, we'll take this little optimization
|
||||||
Some((name, value)) => Self::render_static_attr(value, name, ns, el_name),
|
Some((name, value)) => Self::render_static_attr(value, name, el_name),
|
||||||
|
|
||||||
// Otherwise, we'll just render it as a dynamic attribute
|
// Otherwise, we'll just render it as a dynamic attribute
|
||||||
// This will also insert the attribute into the dynamic_attributes list to assemble the final template
|
// This will also insert the attribute into the dynamic_attributes list to assemble the final template
|
||||||
@@ -171,13 +182,12 @@ impl<'a> DynamicContext<'a> {
|
|||||||
fn render_static_attr(
|
fn render_static_attr(
|
||||||
value: &IfmtInput,
|
value: &IfmtInput,
|
||||||
name: &ElementAttrName,
|
name: &ElementAttrName,
|
||||||
ns: impl Fn(TokenStream2) -> TokenStream2,
|
|
||||||
el_name: &ElementName,
|
el_name: &ElementName,
|
||||||
) -> TokenStream2 {
|
) -> TokenStream2 {
|
||||||
let value = value.to_static().unwrap();
|
let value = value.to_static().unwrap();
|
||||||
|
|
||||||
let ns = match name {
|
let ns = match name {
|
||||||
ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
|
ElementAttrName::BuiltIn(name) => quote! { #el_name::#name.1 },
|
||||||
ElementAttrName::Custom(_) => quote!(None),
|
ElementAttrName::Custom(_) => quote!(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+198
-63
@@ -1,25 +1,55 @@
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
use crate::errors::missing_trailing_comma;
|
||||||
|
|
||||||
|
use self::util::try_parse_braces;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::ParseBuffer, punctuated::Punctuated, spanned::Spanned, token::Brace, Expr, Ident,
|
ext::IdentExt, punctuated::Punctuated, spanned::Spanned, token::Brace, Expr, Ident, LitStr,
|
||||||
LitStr, Token,
|
Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// Parse the VNode::Element type
|
// Parse the VNode::Element type
|
||||||
// =======================================
|
// =======================================
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Element {
|
pub struct Element {
|
||||||
pub name: ElementName,
|
pub name: ElementName,
|
||||||
pub key: Option<IfmtInput>,
|
pub key: Option<IfmtInput>,
|
||||||
pub attributes: Vec<AttributeType>,
|
pub attributes: Vec<AttributeType>,
|
||||||
pub merged_attributes: Vec<AttributeType>,
|
pub merged_attributes: Vec<AttributeType>,
|
||||||
pub children: Vec<BodyNode>,
|
pub children: Vec<BodyNode>,
|
||||||
pub brace: syn::token::Brace,
|
pub brace: Option<syn::token::Brace>,
|
||||||
|
// Non-fatal errors that occurred during parsing
|
||||||
|
errors: Vec<syn::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Element {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.name == other.name
|
||||||
|
&& self.key == other.key
|
||||||
|
&& self.attributes == other.attributes
|
||||||
|
&& self.merged_attributes == other.merged_attributes
|
||||||
|
&& self.children == other.children
|
||||||
|
&& self.brace == other.brace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Element {}
|
||||||
|
|
||||||
|
impl Hash for Element {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.name.hash(state);
|
||||||
|
self.key.hash(state);
|
||||||
|
self.attributes.hash(state);
|
||||||
|
self.merged_attributes.hash(state);
|
||||||
|
self.children.hash(state);
|
||||||
|
self.brace.hash(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element {
|
impl Element {
|
||||||
@@ -58,22 +88,61 @@ impl Element {
|
|||||||
attributes,
|
attributes,
|
||||||
merged_attributes,
|
merged_attributes,
|
||||||
children,
|
children,
|
||||||
brace,
|
brace: Some(brace),
|
||||||
|
errors: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Element {
|
/// Create a new incomplete element that has not been fully typed yet
|
||||||
fn parse(stream: ParseStream) -> Result<Self> {
|
fn incomplete(name: ElementName) -> Self {
|
||||||
|
Self {
|
||||||
|
errors: vec![syn::Error::new(
|
||||||
|
name.span(),
|
||||||
|
format!("Missing braces after element name `{}`", name),
|
||||||
|
)],
|
||||||
|
name,
|
||||||
|
key: None,
|
||||||
|
attributes: Vec::new(),
|
||||||
|
merged_attributes: Vec::new(),
|
||||||
|
children: Vec::new(),
|
||||||
|
brace: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_with_options(
|
||||||
|
stream: ParseStream,
|
||||||
|
partial_completions: bool,
|
||||||
|
) -> Result<Self> {
|
||||||
|
fn peek_any_ident(input: ParseStream) -> bool {
|
||||||
|
input.peek(Ident::peek_any)
|
||||||
|
&& !input.peek(Token![for])
|
||||||
|
&& !input.peek(Token![if])
|
||||||
|
&& !input.peek(Token![match])
|
||||||
|
}
|
||||||
|
|
||||||
let el_name = ElementName::parse(stream)?;
|
let el_name = ElementName::parse(stream)?;
|
||||||
|
|
||||||
// parse the guts
|
// parse the guts
|
||||||
let content: ParseBuffer;
|
let Ok((brace, content)) = try_parse_braces(stream) else {
|
||||||
let brace = syn::braced!(content in stream);
|
// If there are no braces, this is an incomplete element. We still parse it so that we can autocomplete it, but we don't need to parse the children
|
||||||
|
return Ok(Self::incomplete(el_name));
|
||||||
|
};
|
||||||
|
|
||||||
let mut attributes: Vec<AttributeType> = vec![];
|
let mut attributes: Vec<AttributeType> = vec![];
|
||||||
let mut children: Vec<BodyNode> = vec![];
|
let mut children: Vec<BodyNode> = vec![];
|
||||||
let mut key = None;
|
let mut key = None;
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
macro_rules! accumulate_or_return_error {
|
||||||
|
($error:expr) => {
|
||||||
|
let error = $error;
|
||||||
|
if partial_completions {
|
||||||
|
errors.push(error);
|
||||||
|
} else {
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// parse fields with commas
|
// parse fields with commas
|
||||||
// break when we don't get this pattern anymore
|
// break when we don't get this pattern anymore
|
||||||
@@ -92,7 +161,7 @@ impl Parse for Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if content.parse::<Token![,]>().is_err() {
|
if content.parse::<Token![,]>().is_err() {
|
||||||
missing_trailing_comma!(span);
|
accumulate_or_return_error!(missing_trailing_comma(span));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -106,28 +175,30 @@ impl Parse for Element {
|
|||||||
content.parse::<Token![:]>()?;
|
content.parse::<Token![:]>()?;
|
||||||
|
|
||||||
let value = content.parse::<ElementAttrValue>()?;
|
let value = content.parse::<ElementAttrValue>()?;
|
||||||
|
let followed_by_comma = content.parse::<Token![,]>().is_ok();
|
||||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr {
|
attr: ElementAttr {
|
||||||
name: ElementAttrName::Custom(name),
|
name: ElementAttrName::Custom(name),
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
|
followed_by_comma,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if content.is_empty() {
|
if content.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.parse::<Token![,]>().is_err() {
|
if !followed_by_comma {
|
||||||
missing_trailing_comma!(ident.span());
|
accumulate_or_return_error!(missing_trailing_comma(ident.span()));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
// abc: 123,
|
// abc: 123,
|
||||||
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
if peek_any_ident(&content) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||||
let name = content.parse::<Ident>()?;
|
let name = Ident::parse_any(&content)?;
|
||||||
|
|
||||||
let name_str = name.to_string();
|
let name_str = name.to_string();
|
||||||
content.parse::<Token![:]>()?;
|
content.parse::<Token![:]>()?;
|
||||||
@@ -136,36 +207,7 @@ impl Parse for Element {
|
|||||||
// for example the `hi` part of `class: "hi"`.
|
// for example the `hi` part of `class: "hi"`.
|
||||||
let span = content.span();
|
let span = content.span();
|
||||||
|
|
||||||
if name_str.starts_with("on") {
|
if name_str == "key" {
|
||||||
// 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" {
|
|
||||||
let _key: IfmtInput = content.parse()?;
|
let _key: IfmtInput = content.parse()?;
|
||||||
|
|
||||||
if _key.is_static() {
|
if _key.is_static() {
|
||||||
@@ -174,13 +216,39 @@ impl Parse for Element {
|
|||||||
|
|
||||||
key = Some(_key);
|
key = Some(_key);
|
||||||
} else {
|
} else {
|
||||||
let value = content.parse::<ElementAttrValue>()?;
|
let value = if name_str.starts_with("on") {
|
||||||
|
// 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),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
ElementAttrValue::EventTokens(content.parse()?)
|
||||||
|
} else {
|
||||||
|
content.parse::<ElementAttrValue>()?
|
||||||
|
};
|
||||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr {
|
attr: ElementAttr {
|
||||||
name: ElementAttrName::BuiltIn(name),
|
name: ElementAttrName::built_in(&name),
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
|
followed_by_comma: content.peek(Token![,]),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,18 +257,18 @@ impl Parse for Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if content.parse::<Token![,]>().is_err() {
|
if content.parse::<Token![,]>().is_err() {
|
||||||
missing_trailing_comma!(span);
|
accumulate_or_return_error!(missing_trailing_comma(span));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse shorthand fields
|
// Parse shorthand fields
|
||||||
if content.peek(Ident)
|
if peek_any_ident(&content)
|
||||||
&& !content.peek2(Brace)
|
&& !content.peek2(Brace)
|
||||||
&& !content.peek2(Token![:])
|
&& !content.peek2(Token![:])
|
||||||
&& !content.peek2(Token![-])
|
&& !content.peek2(Token![-])
|
||||||
{
|
{
|
||||||
let name = content.parse::<Ident>()?;
|
let name = Ident::parse_any(&content)?;
|
||||||
let name_ = name.clone();
|
let name_ = name.clone();
|
||||||
|
|
||||||
// If the shorthand field is children, these are actually children!
|
// If the shorthand field is children, these are actually children!
|
||||||
@@ -216,21 +284,37 @@ Like so:
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = ElementAttrValue::Shorthand(name.clone());
|
let followed_by_comma = content.parse::<Token![,]>().is_ok();
|
||||||
|
|
||||||
|
// If the shorthand field starts with a capital letter and it isn't followed by a comma, it's actually the start of typing a component
|
||||||
|
let starts_with_capital = match name.to_string().chars().next() {
|
||||||
|
Some(c) => c.is_uppercase(),
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if starts_with_capital && !followed_by_comma {
|
||||||
|
children.push(BodyNode::Component(Component::incomplete(name.into())));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, it is really a shorthand field
|
||||||
|
let value = ElementAttrValue::shorthand(&name);
|
||||||
|
|
||||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr {
|
attr: ElementAttr {
|
||||||
name: ElementAttrName::BuiltIn(name),
|
name: ElementAttrName::built_in(&name),
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
|
followed_by_comma,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if content.is_empty() {
|
if content.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.parse::<Token![,]>().is_err() {
|
if !followed_by_comma {
|
||||||
missing_trailing_comma!(name_.span());
|
accumulate_or_return_error!(missing_trailing_comma(name_.span()));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -239,15 +323,13 @@ Like so:
|
|||||||
}
|
}
|
||||||
|
|
||||||
while !content.is_empty() {
|
while !content.is_empty() {
|
||||||
if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
|
if ((content.peek(Ident) || content.peek(LitStr)) && content.peek2(Token![:]))
|
||||||
|
&& !content.peek3(Token![:])
|
||||||
|
{
|
||||||
attr_after_element!(content.span());
|
attr_after_element!(content.span());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.peek(Ident) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
|
children.push(BodyNode::parse_with_options(&content, partial_completions)?);
|
||||||
attr_after_element!(content.span());
|
|
||||||
}
|
|
||||||
|
|
||||||
children.push(content.parse::<BodyNode>()?);
|
|
||||||
// consume comma if it exists
|
// consume comma if it exists
|
||||||
// we don't actually care if there *are* commas after elements/text
|
// we don't actually care if there *are* commas after elements/text
|
||||||
if content.peek(Token![,]) {
|
if content.peek(Token![,]) {
|
||||||
@@ -255,7 +337,53 @@ Like so:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self::new(key, el_name, attributes, children, brace))
|
let mut myself = Self::new(key, el_name, attributes, children, brace);
|
||||||
|
|
||||||
|
myself.errors = errors;
|
||||||
|
|
||||||
|
Ok(myself)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this element doesn't include braces, the user is probably still typing the element name.
|
||||||
|
/// We can add hints for rust analyzer to complete the element name better.
|
||||||
|
pub(crate) fn completion_hints(&self) -> TokenStream2 {
|
||||||
|
let Element { name, brace, .. } = self;
|
||||||
|
|
||||||
|
// If there are braces, this is a complete element and we don't need to add any hints
|
||||||
|
if brace.is_some() {
|
||||||
|
return quote! {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only complete the element name if it's a built in element
|
||||||
|
let ElementName::Ident(name) = name else {
|
||||||
|
return quote! {};
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
{
|
||||||
|
// Autocomplete as an element
|
||||||
|
dioxus_elements::elements::completions::CompleteWithBraces::#name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this element is only partially complete, return the errors that occurred during parsing
|
||||||
|
pub(crate) fn errors(&self) -> TokenStream2 {
|
||||||
|
let Element { errors, .. } = self;
|
||||||
|
|
||||||
|
let mut tokens = quote! {};
|
||||||
|
for error in errors {
|
||||||
|
tokens.append_all(error.to_compile_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Element {
|
||||||
|
fn parse(stream: ParseStream) -> Result<Self> {
|
||||||
|
Self::parse_with_options(stream, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,10 +396,17 @@ pub enum ElementName {
|
|||||||
impl ElementName {
|
impl ElementName {
|
||||||
pub(crate) fn tag_name(&self) -> TokenStream2 {
|
pub(crate) fn tag_name(&self) -> TokenStream2 {
|
||||||
match self {
|
match self {
|
||||||
ElementName::Ident(i) => quote! { dioxus_elements::#i::TAG_NAME },
|
ElementName::Ident(i) => quote! { dioxus_elements::elements::#i::TAG_NAME },
|
||||||
ElementName::Custom(s) => quote! { #s },
|
ElementName::Custom(s) => quote! { #s },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn namespace(&self) -> TokenStream2 {
|
||||||
|
match self {
|
||||||
|
ElementName::Ident(i) => quote! { dioxus_elements::elements::#i::NAME_SPACE },
|
||||||
|
ElementName::Custom(_) => quote! { None },
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementName {
|
impl ElementName {
|
||||||
@@ -322,7 +457,7 @@ impl Parse for ElementName {
|
|||||||
impl ToTokens for ElementName {
|
impl ToTokens for ElementName {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
match self {
|
match self {
|
||||||
ElementName::Ident(i) => tokens.append_all(quote! { dioxus_elements::#i }),
|
ElementName::Ident(i) => tokens.append_all(quote! { dioxus_elements::elements::#i }),
|
||||||
ElementName::Custom(s) => tokens.append_all(quote! { #s }),
|
ElementName::Custom(s) => tokens.append_all(quote! { #s }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
|
use proc_macro2::Span;
|
||||||
|
|
||||||
|
pub(crate) fn missing_trailing_comma(span: Span) -> syn::Error {
|
||||||
|
syn::Error::new(span, "missing trailing comma")
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! missing_trailing_comma {
|
macro_rules! missing_trailing_comma {
|
||||||
($span:expr) => {
|
($span:expr) => {
|
||||||
return Err(syn::Error::new($span, "missing trailing comma"));
|
return Err(crate::errors::missing_trailing_comma($span));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,17 @@ impl IfmtInput {
|
|||||||
self.segments.push(Segment::Literal(separator.to_string()));
|
self.segments.push(Segment::Literal(separator.to_string()));
|
||||||
}
|
}
|
||||||
self.segments.extend(other.segments);
|
self.segments.extend(other.segments);
|
||||||
|
if let Some(source) = &other.source {
|
||||||
|
self.source = Some(LitStr::new(
|
||||||
|
&format!(
|
||||||
|
"{}{}{}",
|
||||||
|
self.source.as_ref().unwrap().value(),
|
||||||
|
separator,
|
||||||
|
source.value()
|
||||||
|
),
|
||||||
|
source.span(),
|
||||||
|
));
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +55,12 @@ impl IfmtInput {
|
|||||||
|
|
||||||
pub fn push_str(&mut self, s: &str) {
|
pub fn push_str(&mut self, s: &str) {
|
||||||
self.segments.push(Segment::Literal(s.to_string()));
|
self.segments.push(Segment::Literal(s.to_string()));
|
||||||
|
if let Some(source) = &self.source {
|
||||||
|
self.source = Some(LitStr::new(
|
||||||
|
&format!("{}{}", source.value(), s),
|
||||||
|
source.span(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_static(&self) -> bool {
|
pub fn is_static(&self) -> bool {
|
||||||
@@ -66,6 +83,15 @@ impl IfmtInput {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_simple_expr(&self) -> bool {
|
||||||
|
self.segments.iter().all(|seg| match seg {
|
||||||
|
Segment::Literal(_) => true,
|
||||||
|
Segment::Formatted(FormattedSegment { segment, .. }) => {
|
||||||
|
matches!(segment, FormattedSegmentType::Ident(_))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to convert this into a single _.to_string() call if possible
|
/// Try to convert this into a single _.to_string() call if possible
|
||||||
///
|
///
|
||||||
/// Using "{single_expression}" is pretty common, but you don't need to go through the whole format! machinery for that, so we optimize it here.
|
/// Using "{single_expression}" is pretty common, but you don't need to go through the whole format! machinery for that, so we optimize it here.
|
||||||
@@ -177,8 +203,19 @@ impl FromStr for IfmtInput {
|
|||||||
impl ToTokens for IfmtInput {
|
impl ToTokens for IfmtInput {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
// Try to turn it into a single _.to_string() call
|
// Try to turn it into a single _.to_string() call
|
||||||
if let Some(single_dynamic) = self.try_to_string() {
|
if !cfg!(debug_assertions) {
|
||||||
tokens.extend(single_dynamic);
|
if let Some(single_dynamic) = self.try_to_string() {
|
||||||
|
tokens.extend(single_dynamic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the segments are not complex exprs, we can just use format! directly to take advantage of RA rename/expansion
|
||||||
|
if self.is_simple_expr() {
|
||||||
|
let raw = &self.source;
|
||||||
|
tokens.extend(quote! {
|
||||||
|
::std::format_args!(#raw)
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+50
-4
@@ -13,6 +13,41 @@
|
|||||||
//! - [x] Good errors if parsing fails
|
//! - [x] Good errors if parsing fails
|
||||||
//!
|
//!
|
||||||
//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
|
//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
|
||||||
|
//!
|
||||||
|
//! # Completions
|
||||||
|
//! Rust analyzer completes macros by looking at the expansion of the macro and trying to match the start of identifiers in the macro to identifiers in the current scope
|
||||||
|
//!
|
||||||
|
//! Eg, if a macro expands to this:
|
||||||
|
//! ```rust, ignore
|
||||||
|
//! struct MyStruct;
|
||||||
|
//!
|
||||||
|
//! // macro expansion
|
||||||
|
//! My
|
||||||
|
//! ```
|
||||||
|
//! Then the analyzer will try to match the start of the identifier "My" to an identifier in the current scope (MyStruct in this case).
|
||||||
|
//!
|
||||||
|
//! In dioxus, our macros expand to the completions module if we know the identifier is incomplete:
|
||||||
|
//! ```rust, ignore
|
||||||
|
//! // In the root of the macro, identifiers must be elements
|
||||||
|
//! // rsx! { di }
|
||||||
|
//! dioxus_elements::elements::di
|
||||||
|
//!
|
||||||
|
//! // Before the first child element, every following identifier is either an attribute or an element
|
||||||
|
//! // rsx! { div { ta } }
|
||||||
|
//! // Isolate completions scope
|
||||||
|
//! mod completions__ {
|
||||||
|
//! // import both the attributes and elements this could complete to
|
||||||
|
//! use dioxus_elements::elements::div::*;
|
||||||
|
//! use dioxus_elements::elements::*;
|
||||||
|
//! fn complete() {
|
||||||
|
//! ta;
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! // After the first child element, every following identifier is another element
|
||||||
|
//! // rsx! { div { attribute: value, child {} di } }
|
||||||
|
//! dioxus_elements::elements::di
|
||||||
|
//! ```
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod errors;
|
mod errors;
|
||||||
@@ -145,14 +180,19 @@ impl CallBody {
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for CallBody {
|
/// Parse a stream into a CallBody. Return all error immediately instead of trying to partially expand the macro
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
///
|
||||||
|
/// This should be preferred over `parse` if you are outside of a macro
|
||||||
|
pub fn parse_strict(input: ParseStream) -> Result<Self> {
|
||||||
|
Self::parse_with_options(input, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_with_options(input: ParseStream, partial_completions: bool) -> Result<Self> {
|
||||||
let mut roots = Vec::new();
|
let mut roots = Vec::new();
|
||||||
|
|
||||||
while !input.is_empty() {
|
while !input.is_empty() {
|
||||||
let node = input.parse::<BodyNode>()?;
|
let node = BodyNode::parse_with_options(input, partial_completions)?;
|
||||||
|
|
||||||
if input.peek(Token![,]) {
|
if input.peek(Token![,]) {
|
||||||
let _ = input.parse::<Token![,]>();
|
let _ = input.parse::<Token![,]>();
|
||||||
@@ -165,6 +205,12 @@ impl Parse for CallBody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Parse for CallBody {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
Self::parse_with_options(input, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToTokens for CallBody {
|
impl ToTokens for CallBody {
|
||||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||||
// Empty templates just are placeholders for "none"
|
// Empty templates just are placeholders for "none"
|
||||||
|
|||||||
+131
-20
@@ -6,9 +6,10 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
|
|||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{
|
use syn::{
|
||||||
braced,
|
braced,
|
||||||
|
parse::ParseBuffer,
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
token::{self, Brace},
|
token::{self, Brace},
|
||||||
Expr, ExprIf, LitStr, Pat,
|
Expr, ExprCall, ExprIf, Ident, LitStr, Pat,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -19,17 +20,45 @@ Parse
|
|||||||
-> "text {with_args}"
|
-> "text {with_args}"
|
||||||
-> {(0..10).map(|f| rsx!("asd"))} // <--- notice the curly braces
|
-> {(0..10).map(|f| rsx!("asd"))} // <--- notice the curly braces
|
||||||
*/
|
*/
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BodyNode {
|
pub enum BodyNode {
|
||||||
Element(Element),
|
Element(Element),
|
||||||
Text(IfmtInput),
|
Text(IfmtInput),
|
||||||
RawExpr(Expr),
|
RawExpr(TokenStream2),
|
||||||
|
|
||||||
Component(Component),
|
Component(Component),
|
||||||
ForLoop(ForLoop),
|
ForLoop(ForLoop),
|
||||||
IfChain(IfChain),
|
IfChain(IfChain),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for BodyNode {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Self::Element(l), Self::Element(r)) => l == r,
|
||||||
|
(Self::Text(l), Self::Text(r)) => l == r,
|
||||||
|
(Self::RawExpr(l), Self::RawExpr(r)) => l.to_string() == r.to_string(),
|
||||||
|
(Self::Component(l), Self::Component(r)) => l == r,
|
||||||
|
(Self::ForLoop(l), Self::ForLoop(r)) => l == r,
|
||||||
|
(Self::IfChain(l), Self::IfChain(r)) => l == r,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for BodyNode {}
|
||||||
|
|
||||||
|
impl Hash for BodyNode {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
match self {
|
||||||
|
Self::Element(el) => el.hash(state),
|
||||||
|
Self::Text(text) => text.hash(state),
|
||||||
|
Self::RawExpr(exp) => exp.to_string().hash(state),
|
||||||
|
Self::Component(comp) => comp.hash(state),
|
||||||
|
Self::ForLoop(for_loop) => for_loop.hash(state),
|
||||||
|
Self::IfChain(if_chain) => if_chain.hash(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BodyNode {
|
impl BodyNode {
|
||||||
pub fn is_litstr(&self) -> bool {
|
pub fn is_litstr(&self) -> bool {
|
||||||
matches!(self, BodyNode::Text { .. })
|
matches!(self, BodyNode::Text { .. })
|
||||||
@@ -45,10 +74,16 @@ impl BodyNode {
|
|||||||
BodyNode::IfChain(f) => f.if_token.span(),
|
BodyNode::IfChain(f) => f.if_token.span(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for BodyNode {
|
pub(crate) fn parse_with_options(
|
||||||
fn parse(stream: ParseStream) -> Result<Self> {
|
stream: ParseStream,
|
||||||
|
partial_completions: bool,
|
||||||
|
) -> Result<Self> {
|
||||||
|
// Make sure the next token is a brace if we're not in partial completion mode
|
||||||
|
fn peek_brace(stream: &ParseBuffer, partial_completions: bool) -> bool {
|
||||||
|
partial_completions || stream.peek(token::Brace)
|
||||||
|
}
|
||||||
|
|
||||||
if stream.peek(LitStr) {
|
if stream.peek(LitStr) {
|
||||||
return Ok(BodyNode::Text(stream.parse()?));
|
return Ok(BodyNode::Text(stream.parse()?));
|
||||||
}
|
}
|
||||||
@@ -56,7 +91,7 @@ impl Parse for BodyNode {
|
|||||||
// if this is a dash-separated path, it's a web component (custom element)
|
// if this is a dash-separated path, it's a web component (custom element)
|
||||||
let body_stream = stream.fork();
|
let body_stream = stream.fork();
|
||||||
if let Ok(ElementName::Custom(name)) = body_stream.parse::<ElementName>() {
|
if let Ok(ElementName::Custom(name)) = body_stream.parse::<ElementName>() {
|
||||||
if name.value().contains('-') && body_stream.peek(token::Brace) {
|
if name.value().contains('-') && peek_brace(&body_stream, partial_completions) {
|
||||||
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,21 +108,65 @@ impl Parse for BodyNode {
|
|||||||
// example:
|
// example:
|
||||||
// div {}
|
// div {}
|
||||||
if let Some(ident) = path.get_ident() {
|
if let Some(ident) = path.get_ident() {
|
||||||
let el_name = ident.to_string();
|
if peek_brace(&body_stream, partial_completions)
|
||||||
|
&& !ident_looks_like_component(ident)
|
||||||
let first_char = el_name.chars().next().unwrap();
|
|
||||||
|
|
||||||
if body_stream.peek(token::Brace)
|
|
||||||
&& first_char.is_ascii_lowercase()
|
|
||||||
&& !el_name.contains('_')
|
|
||||||
{
|
{
|
||||||
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
return Ok(BodyNode::Element(Element::parse_with_options(
|
||||||
|
stream,
|
||||||
|
partial_completions,
|
||||||
|
)?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is a single function call with a name that looks like a component, it should probably be a component
|
||||||
|
// Eg, if we run into this:
|
||||||
|
// ```rust
|
||||||
|
// my_function(key, prop)
|
||||||
|
// ```
|
||||||
|
// We should tell the user that they need braces around props instead of turning the component call into an expression
|
||||||
|
if let Ok(call) = stream.fork().parse::<ExprCall>() {
|
||||||
|
if let Expr::Path(path) = call.func.as_ref() {
|
||||||
|
if let Some(ident) = path.path.get_ident() {
|
||||||
|
if ident_looks_like_component(ident) {
|
||||||
|
let function_args: Vec<_> = call
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| arg.to_token_stream().to_string())
|
||||||
|
.collect();
|
||||||
|
let function_call = format!("{}({})", ident, function_args.join(", "));
|
||||||
|
let component_call = if function_args.is_empty() {
|
||||||
|
format!("{} {{}}", ident)
|
||||||
|
} else {
|
||||||
|
let component_args: Vec<_> = call
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(prop_count, arg)| {
|
||||||
|
// Try to parse it as a shorthand field
|
||||||
|
if let Ok(simple_ident) =
|
||||||
|
syn::parse2::<Ident>(arg.to_token_stream())
|
||||||
|
{
|
||||||
|
format!("{}", simple_ident)
|
||||||
|
} else {
|
||||||
|
let ident = format!("prop{}", prop_count + 1);
|
||||||
|
format!("{}: {}", ident, arg.to_token_stream())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
format!("{} {{\n\t{}\n}}", ident, component_args.join(",\n\t"))
|
||||||
|
};
|
||||||
|
let error_text = format!(
|
||||||
|
"Expected a valid body node found a function call. Did you forget to add braces around props?\nComponents should be called with braces instead of being called as expressions.\nInstead of:\n```rust\n{function_call}\n```\nTry:\n```rust\n{component_call}\n```\nIf you are trying to call a function, not a component, you need to wrap your expression in braces.",
|
||||||
|
);
|
||||||
|
return Err(syn::Error::new(call.span(), error_text));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise this should be Component, allowed syntax:
|
// Otherwise this should be Component, allowed syntax:
|
||||||
// - syn::Path
|
// - syn::Path
|
||||||
// - PathArguments can only apper in last segment
|
// - PathArguments can only appear in last segment
|
||||||
// - followed by `{` or `(`, note `(` cannot be used with one ident
|
// - followed by `{` or `(`, note `(` cannot be used with one ident
|
||||||
//
|
//
|
||||||
// example
|
// example
|
||||||
@@ -99,7 +178,7 @@ impl Parse for BodyNode {
|
|||||||
// crate::component{}
|
// crate::component{}
|
||||||
// Input::<InputProps<'_, i32> {}
|
// Input::<InputProps<'_, i32> {}
|
||||||
// crate::Input::<InputProps<'_, i32> {}
|
// crate::Input::<InputProps<'_, i32> {}
|
||||||
if body_stream.peek(token::Brace) {
|
if peek_brace(&body_stream, partial_completions) {
|
||||||
return Ok(BodyNode::Component(stream.parse()?));
|
return Ok(BodyNode::Component(stream.parse()?));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,11 +203,26 @@ impl Parse for BodyNode {
|
|||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
if stream.peek(Token![match]) {
|
if stream.peek(Token![match]) {
|
||||||
return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
|
return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?.to_token_stream()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if stream.peek(token::Brace) {
|
if stream.peek(token::Brace) {
|
||||||
return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
|
// If we are in strict mode, make sure thing inside the braces is actually a valid expression
|
||||||
|
let combined = if !partial_completions {
|
||||||
|
stream.parse::<Expr>()?.to_token_stream()
|
||||||
|
} else {
|
||||||
|
// otherwise, just take whatever is inside the braces. It might be invalid, but we still want to spit it out so we get completions
|
||||||
|
let content;
|
||||||
|
let brace = braced!(content in stream);
|
||||||
|
let content: TokenStream2 = content.parse()?;
|
||||||
|
let mut combined = TokenStream2::new();
|
||||||
|
brace.surround(&mut combined, |inside_brace| {
|
||||||
|
inside_brace.append_all(content);
|
||||||
|
});
|
||||||
|
combined
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(BodyNode::RawExpr(combined));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(syn::Error::new(
|
Err(syn::Error::new(
|
||||||
@@ -138,6 +232,20 @@ impl Parse for BodyNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if an ident looks like a component
|
||||||
|
fn ident_looks_like_component(ident: &Ident) -> bool {
|
||||||
|
let as_string = ident.to_string();
|
||||||
|
let first_char = as_string.chars().next().unwrap();
|
||||||
|
// Components either start with an uppercase letter or have an underscore in them
|
||||||
|
first_char.is_ascii_uppercase() || as_string.contains('_')
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for BodyNode {
|
||||||
|
fn parse(stream: ParseStream) -> Result<Self> {
|
||||||
|
Self::parse_with_options(stream, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToTokens for BodyNode {
|
impl ToTokens for BodyNode {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
match self {
|
match self {
|
||||||
@@ -153,6 +261,7 @@ impl ToTokens for BodyNode {
|
|||||||
// Expressons too
|
// Expressons too
|
||||||
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
|
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
|
||||||
{
|
{
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
let ___nodes = (#exp).into_dyn_node();
|
let ___nodes = (#exp).into_dyn_node();
|
||||||
___nodes
|
___nodes
|
||||||
}
|
}
|
||||||
@@ -226,6 +335,7 @@ impl ToTokens for ForLoop {
|
|||||||
// And then we can return them into the dyn loop
|
// And then we can return them into the dyn loop
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
{
|
{
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
let ___nodes = (#expr).into_iter().map(|#pat| { #renderer }).into_dyn_node();
|
let ___nodes = (#expr).into_iter().map(|#pat| { #renderer }).into_dyn_node();
|
||||||
___nodes
|
___nodes
|
||||||
}
|
}
|
||||||
@@ -320,6 +430,7 @@ impl ToTokens for IfChain {
|
|||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
{
|
{
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
let ___nodes = (#body).into_dyn_node();
|
let ___nodes = (#body).into_dyn_node();
|
||||||
___nodes
|
___nodes
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ impl<'a> TemplateRenderer<'a> {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// NOTE: Allocating a temporary is important to make reads within rsx drop before the value is returned
|
// NOTE: Allocating a temporary is important to make reads within rsx drop before the value is returned
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
let __vnodes = dioxus_core::VNode::new(
|
let __vnodes = dioxus_core::VNode::new(
|
||||||
#key_tokens,
|
#key_tokens,
|
||||||
TEMPLATE,
|
TEMPLATE,
|
||||||
@@ -97,6 +98,14 @@ impl<'a> TemplateRenderer<'a> {
|
|||||||
let root_col = match self.roots.first() {
|
let root_col = match self.roots.first() {
|
||||||
Some(first_root) => {
|
Some(first_root) => {
|
||||||
let first_root_span = format!("{:?}", first_root.span());
|
let first_root_span = format!("{:?}", first_root.span());
|
||||||
|
|
||||||
|
// Rust analyzer will not autocomplete properly if we change the name every time you type a character
|
||||||
|
// If it looks like we are running in rust analyzer, we'll just use a placeholder location
|
||||||
|
let looks_like_rust_analyzer = first_root_span.contains("SpanData");
|
||||||
|
if looks_like_rust_analyzer {
|
||||||
|
return "0".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
first_root_span
|
first_root_span
|
||||||
.rsplit_once("..")
|
.rsplit_once("..")
|
||||||
.and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
|
.and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
|
pub(crate) fn try_parse_braces<'a>(
|
||||||
|
input: &syn::parse::ParseBuffer<'a>,
|
||||||
|
) -> syn::Result<(syn::token::Brace, syn::parse::ParseBuffer<'a>)> {
|
||||||
|
let content;
|
||||||
|
let brace = syn::braced!(content in input);
|
||||||
|
Ok((brace, content))
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
dioxus_core :: TemplateNode :: Element { tag : dioxus_elements :: circle :: TAG_NAME , namespace : dioxus_elements :: circle :: NAME_SPACE , attrs : & [dioxus_core :: TemplateAttribute :: Dynamic { id : 0usize } , dioxus_core :: TemplateAttribute :: Dynamic { id : 1usize } , dioxus_core :: TemplateAttribute :: Dynamic { id : 2usize } , dioxus_core :: TemplateAttribute :: Static { name : dioxus_elements :: circle :: stroke . 0 , namespace : dioxus_elements :: circle :: stroke . 1 , value : "green" , } , dioxus_core :: TemplateAttribute :: Static { name : dioxus_elements :: circle :: fill . 0 , namespace : dioxus_elements :: circle :: fill . 1 , value : "yellow" , } ,] , children : & [] , }
|
dioxus_core :: TemplateNode :: Element { tag : dioxus_elements :: elements :: circle :: TAG_NAME , namespace : dioxus_elements :: elements :: circle :: NAME_SPACE , attrs : & [dioxus_core :: TemplateAttribute :: Dynamic { id : 0usize } , dioxus_core :: TemplateAttribute :: Dynamic { id : 1usize } , dioxus_core :: TemplateAttribute :: Dynamic { id : 2usize } , dioxus_core :: TemplateAttribute :: Static { name : dioxus_elements :: elements :: circle :: stroke . 0 , namespace : dioxus_elements :: elements :: circle :: stroke . 1 , value : "green" , } , dioxus_core :: TemplateAttribute :: Static { name : dioxus_elements :: elements :: circle :: fill . 0 , namespace : dioxus_elements :: elements :: circle :: fill . 1 , value : "yellow" , } ,] , children : & [] , }
|
||||||
Reference in new issue
Block a user