Fix rsx autocomplete and diagnostics in the root; provide better completions after attributes are finished (#2656)
* fix rsx autocomplete and diagnostics in the root; provide better completions after attributes are finished * clean up pr to use internal child parsing * Fix peek_lowercase name * fix comma diagnostics in the root * Fix parsing components with a path * remove simple routes trailing comma * Fix incomplete_root_elements test * Remove trailing commas from root body node in tests --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
71a48686dc
commit
fa4e5dbf62
8 files changed
+179
-79
No files matched your search
@@ -82,7 +82,8 @@ impl<'a> Writer<'a> {
|
|||||||
|
|
||||||
write!(self.out, "{name} {{")?;
|
write!(self.out, "{name} {{")?;
|
||||||
|
|
||||||
self.write_rsx_block(attributes, spreads, children, brace)?;
|
let brace = brace.unwrap_or_default();
|
||||||
|
self.write_rsx_block(attributes, spreads, children, &brace)?;
|
||||||
|
|
||||||
write!(self.out, "}}")?;
|
write!(self.out, "}}")?;
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ fn fragments_across_components() {
|
|||||||
|
|
||||||
fn demo_child() -> Element {
|
fn demo_child() -> Element {
|
||||||
let world = "world";
|
let world = "world";
|
||||||
rsx! { "hellO!", {world} }
|
rsx! { "hellO!" {world} }
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ fn dynamic_node_as_root() {
|
|||||||
fn app() -> Element {
|
fn app() -> Element {
|
||||||
let a = 123;
|
let a = 123;
|
||||||
let b = 456;
|
let b = 456;
|
||||||
rsx! { "{a}", "{b}" }
|
rsx! { "{a}" "{b}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dom = VirtualDom::new(app);
|
let mut dom = VirtualDom::new(app);
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ fn Route1(user_id: usize, dynamic: usize, query: String) -> Element {
|
|||||||
fn Route2(user_id: usize) -> Element {
|
fn Route2(user_id: usize) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
pre { "Route2{{\n\tuser_id:{user_id}\n}}" }
|
pre { "Route2{{\n\tuser_id:{user_id}\n}}" }
|
||||||
{(0..user_id).map(|i| rsx!{ p { "{i}" } })},
|
{(0..user_id).map(|i| rsx!{ p { "{i}" } })}
|
||||||
p { "Footer" }
|
p { "Footer" }
|
||||||
Link {
|
Link {
|
||||||
to: Route::Route3 {
|
to: Route::Route3 {
|
||||||
|
|||||||
+60
-15
@@ -35,7 +35,7 @@ pub struct Element {
|
|||||||
pub children: Vec<BodyNode>,
|
pub children: Vec<BodyNode>,
|
||||||
|
|
||||||
/// the brace of the `div { }`
|
/// the brace of the `div { }`
|
||||||
pub brace: Brace,
|
pub brace: Option<Brace>,
|
||||||
|
|
||||||
/// A list of diagnostics that were generated during parsing. This element might be a valid rsx_block
|
/// A list of diagnostics that were generated during parsing. This element might be a valid rsx_block
|
||||||
/// but not technically a valid element - these diagnostics tell us what's wrong and then are used
|
/// but not technically a valid element - these diagnostics tell us what's wrong and then are used
|
||||||
@@ -47,32 +47,51 @@ impl Parse for Element {
|
|||||||
fn parse(stream: ParseStream) -> Result<Self> {
|
fn parse(stream: ParseStream) -> Result<Self> {
|
||||||
let name = stream.parse::<ElementName>()?;
|
let name = stream.parse::<ElementName>()?;
|
||||||
|
|
||||||
let RsxBlock {
|
// We very liberally parse elements - they might not even have a brace!
|
||||||
attributes: mut fields,
|
// This is designed such that we can throw a compile error but still give autocomplete
|
||||||
children,
|
// ... partial completions mean we do some weird parsing to get the right completions
|
||||||
brace,
|
let mut brace = None;
|
||||||
spreads,
|
let mut block = RsxBlock::default();
|
||||||
diagnostics,
|
|
||||||
} = stream.parse::<RsxBlock>()?;
|
match stream.peek(Brace) {
|
||||||
|
// If the element is followed by a brace, it is complete. Parse the body
|
||||||
|
true => {
|
||||||
|
block = stream.parse::<RsxBlock>()?;
|
||||||
|
brace = Some(block.brace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, it is incomplete. Add a diagnostic
|
||||||
|
false => block.diagnostics.push(
|
||||||
|
name.span()
|
||||||
|
.error("Elements must be followed by braces")
|
||||||
|
.help("Did you forget a brace?"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure these attributes have an el_name set for completions and Template generation
|
// Make sure these attributes have an el_name set for completions and Template generation
|
||||||
for attr in fields.iter_mut() {
|
for attr in block.attributes.iter_mut() {
|
||||||
attr.el_name = Some(name.clone());
|
attr.el_name = Some(name.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assemble the new element from the contents of the block
|
||||||
let mut element = Element {
|
let mut element = Element {
|
||||||
name,
|
|
||||||
raw_attributes: fields,
|
|
||||||
children,
|
|
||||||
brace,
|
brace,
|
||||||
diagnostics,
|
name,
|
||||||
spreads: spreads.clone(),
|
raw_attributes: block.attributes,
|
||||||
|
children: block.children,
|
||||||
|
diagnostics: block.diagnostics,
|
||||||
|
spreads: block.spreads.clone(),
|
||||||
merged_attributes: Vec::new(),
|
merged_attributes: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// And then merge the various attributes together
|
||||||
|
// The original raw_attributes are kept for lossless parsing used by hotreload/autofmt
|
||||||
element.merge_attributes();
|
element.merge_attributes();
|
||||||
|
|
||||||
for spread in spreads.iter() {
|
// And then merge the spreads *after* the attributes are merged. This ensures walking the
|
||||||
|
// merged attributes in path order stops before we hit the spreads, but spreads are still
|
||||||
|
// counted as dynamic attributes
|
||||||
|
for spread in block.spreads.iter() {
|
||||||
element.merged_attributes.push(Attribute {
|
element.merged_attributes.push(Attribute {
|
||||||
name: AttributeName::Spread(spread.dots),
|
name: AttributeName::Spread(spread.dots),
|
||||||
colon: None,
|
colon: None,
|
||||||
@@ -171,10 +190,13 @@ impl ToTokens for Element {
|
|||||||
let ns = ns(quote!(NAME_SPACE));
|
let ns = ns(quote!(NAME_SPACE));
|
||||||
let el_name = el_name.tag_name();
|
let el_name = el_name.tag_name();
|
||||||
let diagnostics = &el.diagnostics;
|
let diagnostics = &el.diagnostics;
|
||||||
|
let completion_hints = &el.completion_hints();
|
||||||
|
|
||||||
// todo: generate less code if there's no diagnostics by not including the curlies
|
// todo: generate less code if there's no diagnostics by not including the curlies
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
{
|
{
|
||||||
|
#completion_hints
|
||||||
|
|
||||||
#diagnostics
|
#diagnostics
|
||||||
|
|
||||||
dioxus_core::TemplateNode::Element {
|
dioxus_core::TemplateNode::Element {
|
||||||
@@ -294,6 +316,29 @@ impl Element {
|
|||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn completion_hints(&self) -> TokenStream2 {
|
||||||
|
// If there is already a brace, we don't need any completion hints
|
||||||
|
if self.brace.is_some() {
|
||||||
|
return quote! {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let ElementName::Ident(name) = &self.name else {
|
||||||
|
return quote! {};
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
mod __completions {
|
||||||
|
fn ignore() {
|
||||||
|
super::dioxus_elements::elements::completions::CompleteWithBraces::#name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use quote::ToTokens;
|
|||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
token::{self, Brace},
|
token::{self},
|
||||||
Ident, LitStr, Result, Token,
|
Ident, LitStr, Result, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -76,16 +76,17 @@ impl Parse for BodyNode {
|
|||||||
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is an Element if path match of:
|
// this is an Element if the path is:
|
||||||
//
|
//
|
||||||
// - one ident
|
// - one ident
|
||||||
// - followed by `{`
|
|
||||||
// - 1st char is lowercase
|
// - 1st char is lowercase
|
||||||
// - no underscores (reserved for components)
|
// - no underscores (reserved for components)
|
||||||
|
// And it is not:
|
||||||
|
// - the start of a path with components
|
||||||
//
|
//
|
||||||
// example:
|
// example:
|
||||||
// div {}
|
// div {}
|
||||||
if stream.peek(Ident) && stream.peek2(Brace) {
|
if stream.peek(Ident) && !stream.peek2(Token![::]) {
|
||||||
let ident = stream.fork().parse::<Ident>().unwrap();
|
let ident = stream.fork().parse::<Ident>().unwrap();
|
||||||
let el_name = ident.to_string();
|
let el_name = ident.to_string();
|
||||||
let first_char = el_name.chars().next().unwrap();
|
let first_char = el_name.chars().next().unwrap();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use proc_macro2::Span;
|
|||||||
use proc_macro2_diagnostics::SpanDiagnosticExt;
|
use proc_macro2_diagnostics::SpanDiagnosticExt;
|
||||||
use syn::{
|
use syn::{
|
||||||
ext::IdentExt,
|
ext::IdentExt,
|
||||||
parse::{Parse, ParseBuffer},
|
parse::{Parse, ParseBuffer, ParseStream},
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
token::{self, Brace},
|
token::{self, Brace},
|
||||||
Expr, Ident, LitStr, Token,
|
Expr, Ident, LitStr, Token,
|
||||||
@@ -31,7 +31,7 @@ use syn::{
|
|||||||
/// The name of the block is expected to be parsed by the parent parser. It will accept items out of
|
/// The name of the block is expected to be parsed by the parent parser. It will accept items out of
|
||||||
/// order if possible and then bubble up diagnostics to the parent. This lets us give better errors
|
/// order if possible and then bubble up diagnostics to the parent. This lets us give better errors
|
||||||
/// and autocomplete
|
/// and autocomplete
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
#[derive(PartialEq, Eq, Clone, Debug, Default)]
|
||||||
pub struct RsxBlock {
|
pub struct RsxBlock {
|
||||||
pub brace: token::Brace,
|
pub brace: token::Brace,
|
||||||
pub attributes: Vec<Attribute>,
|
pub attributes: Vec<Attribute>,
|
||||||
@@ -49,16 +49,37 @@ impl Parse for RsxBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RsxBlock {
|
impl RsxBlock {
|
||||||
|
/// Only parse the children of the block - all others will be rejected
|
||||||
|
pub fn parse_children(content: &ParseBuffer) -> syn::Result<Self> {
|
||||||
|
let mut nodes = vec![];
|
||||||
|
let mut diagnostics = Diagnostics::new();
|
||||||
|
while !content.is_empty() {
|
||||||
|
nodes.push(Self::parse_body_node_with_comma_diagnostics(
|
||||||
|
content,
|
||||||
|
&mut diagnostics,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
children: nodes,
|
||||||
|
diagnostics,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_inner(content: &ParseBuffer, brace: token::Brace) -> syn::Result<Self> {
|
pub fn parse_inner(content: &ParseBuffer, brace: token::Brace) -> syn::Result<Self> {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
let mut diagnostics = Diagnostics::new();
|
let mut diagnostics = Diagnostics::new();
|
||||||
|
|
||||||
|
// If we are after attributes, we can try to provide better completions and diagnostics
|
||||||
|
// by parsing the following nodes as body nodes if they are ambiguous, we can parse them as body nodes
|
||||||
|
let mut after_attributes = false;
|
||||||
|
|
||||||
// Lots of manual parsing but it's important to do it all here to give the best diagnostics possible
|
// Lots of manual parsing but it's important to do it all here to give the best diagnostics possible
|
||||||
// We can do things like lookaheads, peeking, etc. to give better errors and autocomplete
|
// We can do things like lookaheads, peeking, etc. to give better errors and autocomplete
|
||||||
// We allow parsing in any order but complain if its done out of order.
|
// We allow parsing in any order but complain if its done out of order.
|
||||||
// Autofmt will fortunately fix this for us in most cases
|
// Autofmt will fortunately fix this for us in most cases
|
||||||
//
|
//
|
||||||
// Weo do this by parsing the unambiguous cases first and then do some clever lookahead to parse the rest
|
// We do this by parsing the unambiguous cases first and then do some clever lookahead to parse the rest
|
||||||
while !content.is_empty() {
|
while !content.is_empty() {
|
||||||
// Parse spread attributes
|
// Parse spread attributes
|
||||||
if content.peek(Token![..]) {
|
if content.peek(Token![..]) {
|
||||||
@@ -89,6 +110,7 @@ impl RsxBlock {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
items.push(RsxItem::Spread(attr));
|
items.push(RsxItem::Spread(attr));
|
||||||
|
after_attributes = true;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -113,7 +135,7 @@ impl RsxBlock {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eagerly match on children, generally
|
// Eagerly match on completed children, generally
|
||||||
if content.peek(LitStr)
|
if content.peek(LitStr)
|
||||||
| content.peek(Token![for])
|
| content.peek(Token![for])
|
||||||
| content.peek(Token![if])
|
| content.peek(Token![if])
|
||||||
@@ -122,10 +144,13 @@ impl RsxBlock {
|
|||||||
// web components
|
// web components
|
||||||
| (content.peek(Ident::peek_any) && content.peek2(Token![-]))
|
| (content.peek(Ident::peek_any) && content.peek2(Token![-]))
|
||||||
// elements
|
// elements
|
||||||
| (content.peek(Ident::peek_any) && content.peek2(token::Brace))
|
| (content.peek(Ident::peek_any) && (after_attributes || content.peek2(token::Brace)))
|
||||||
// todo: eager parse components?
|
// components
|
||||||
|
| (content.peek(Ident::peek_any) && (after_attributes || content.peek2(token::Brace) || content.peek2(Token![::])))
|
||||||
{
|
{
|
||||||
items.push(RsxItem::Child(content.parse::<BodyNode>()?));
|
items.push(RsxItem::Child(
|
||||||
|
Self::parse_body_node_with_comma_diagnostics(content, &mut diagnostics)?,
|
||||||
|
));
|
||||||
if !content.is_empty() && content.peek(Token![,]) {
|
if !content.is_empty() && content.peek(Token![,]) {
|
||||||
let comma = content.parse::<Token![,]>()?;
|
let comma = content.parse::<Token![,]>()?;
|
||||||
diagnostics.push(
|
diagnostics.push(
|
||||||
@@ -134,30 +159,20 @@ impl RsxBlock {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
after_attributes = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse shorthand attributes
|
// Parse shorthand attributes
|
||||||
// todo: this might cause complications with partial expansion... think more about the cases
|
// todo: this might cause complications with partial expansion... think more about the cases
|
||||||
// where we can imagine expansion and what better diagnostics we can provide
|
// where we can imagine expansion and what better diagnostics we can provide
|
||||||
let looks_like_attribute = match content.fork().parse::<Ident>() {
|
if Self::peek_lowercase_ident(&content)
|
||||||
// If it is an ident, it is only a shorthand attribute if it starts with a lowercase letter
|
&& !content.peek2(Brace)
|
||||||
// Otherwise this is the start of a component
|
&& !content.peek2(Token![:]) // regular attributes / components with generics
|
||||||
Ok(ident) => ident
|
&& !content.peek2(Token![-]) // web components
|
||||||
.to_string()
|
&& !content.peek2(Token![<]) // generics on components
|
||||||
.chars()
|
// generics on components
|
||||||
.next()
|
&& !content.peek2(Token![::])
|
||||||
.unwrap()
|
|
||||||
.is_ascii_lowercase(),
|
|
||||||
Err(_) => false,
|
|
||||||
};
|
|
||||||
if looks_like_attribute
|
|
||||||
&& !content.peek2(Brace)
|
|
||||||
&& !content.peek2(Token![:]) // regular attributes / components with generics
|
|
||||||
&& !content.peek2(Token![-]) // web components
|
|
||||||
&& !content.peek2(Token![<]) // generics on components
|
|
||||||
// generics on components
|
|
||||||
&& !content.peek2(Token![::])
|
|
||||||
{
|
{
|
||||||
let attribute = content.parse::<Attribute>()?;
|
let attribute = content.parse::<Attribute>()?;
|
||||||
|
|
||||||
@@ -176,7 +191,9 @@ impl RsxBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally just attempt a bodynode parse
|
// Finally just attempt a bodynode parse
|
||||||
items.push(RsxItem::Child(content.parse::<BodyNode>()?))
|
items.push(RsxItem::Child(
|
||||||
|
Self::parse_body_node_with_comma_diagnostics(content, &mut diagnostics)?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the order of the items
|
// Validate the order of the items
|
||||||
@@ -204,6 +221,36 @@ impl RsxBlock {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse a body node with diagnostics for unnecessary trailing commas
|
||||||
|
fn parse_body_node_with_comma_diagnostics(
|
||||||
|
content: &ParseBuffer,
|
||||||
|
diagnostics: &mut Diagnostics,
|
||||||
|
) -> syn::Result<BodyNode> {
|
||||||
|
let body_node = content.parse::<BodyNode>()?;
|
||||||
|
if !content.is_empty() && content.peek(Token![,]) {
|
||||||
|
let comma = content.parse::<Token![,]>()?;
|
||||||
|
diagnostics.push(
|
||||||
|
comma
|
||||||
|
.span()
|
||||||
|
.warning("Elements and text nodes do not need to be separated by commas."),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(body_node)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_lowercase_ident(stream: &ParseStream) -> bool {
|
||||||
|
let Ok(ident) = stream.fork().parse::<Ident>() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
ident
|
||||||
|
.to_string()
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.is_ascii_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensure the ordering of the items is correct
|
/// Ensure the ordering of the items is correct
|
||||||
/// - Attributes must come before children
|
/// - Attributes must come before children
|
||||||
/// - Spreads must come before children
|
/// - Spreads must come before children
|
||||||
@@ -551,4 +598,27 @@ mod tests {
|
|||||||
|
|
||||||
let _parsed: RsxBlock = syn::parse2(input).unwrap();
|
let _parsed: RsxBlock = syn::parse2(input).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn incomplete_root_elements() {
|
||||||
|
use syn::parse::Parser;
|
||||||
|
|
||||||
|
let input = quote::quote! {
|
||||||
|
di
|
||||||
|
};
|
||||||
|
|
||||||
|
let parsed = RsxBlock::parse_children.parse2(input).unwrap();
|
||||||
|
let children = parsed.children;
|
||||||
|
|
||||||
|
assert_eq!(children.len(), 1);
|
||||||
|
if let BodyNode::Element(parsed) = &children[0] {
|
||||||
|
assert_eq!(
|
||||||
|
parsed.name,
|
||||||
|
ElementName::Ident(Ident::new("di", Span::call_site()))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!("expected element, got {:?}", children);
|
||||||
|
}
|
||||||
|
assert!(parsed.diagnostics.is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -58,8 +58,6 @@ use self::location::DynIdx;
|
|||||||
use crate::innerlude::Attribute;
|
use crate::innerlude::Attribute;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use proc_macro2_diagnostics::SpanDiagnosticExt;
|
|
||||||
use syn::token::Brace;
|
|
||||||
|
|
||||||
#[cfg(feature = "hot_reload")]
|
#[cfg(feature = "hot_reload")]
|
||||||
use dioxus_core::prelude::Template;
|
use dioxus_core::prelude::Template;
|
||||||
@@ -83,39 +81,19 @@ pub struct TemplateBody {
|
|||||||
pub template_idx: DynIdx,
|
pub template_idx: DynIdx,
|
||||||
pub node_paths: Vec<NodePath>,
|
pub node_paths: Vec<NodePath>,
|
||||||
pub attr_paths: Vec<(AttributePath, usize)>,
|
pub attr_paths: Vec<(AttributePath, usize)>,
|
||||||
pub diagnostic: Diagnostics,
|
pub diagnostics: Diagnostics,
|
||||||
current_path: Vec<u8>,
|
current_path: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for TemplateBody {
|
impl Parse for TemplateBody {
|
||||||
/// Parse the nodes of the callbody as `Body`.
|
/// Parse the nodes of the callbody as `Body`.
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let brace = Brace::default();
|
let children = RsxBlock::parse_children(input)?;
|
||||||
|
let mut myself = Self::new(children.children);
|
||||||
let RsxBlock {
|
myself
|
||||||
brace: _,
|
.diagnostics
|
||||||
attributes,
|
.extend(children.diagnostics.into_diagnostics());
|
||||||
spreads,
|
Ok(myself)
|
||||||
children,
|
|
||||||
diagnostics: _, // we don't care about the diagnostics here - ours might be different
|
|
||||||
} = RsxBlock::parse_inner(input, brace)?;
|
|
||||||
|
|
||||||
let mut template = Self::new(children);
|
|
||||||
for spread in spreads {
|
|
||||||
template.diagnostic.push(
|
|
||||||
spread
|
|
||||||
.span()
|
|
||||||
.error("Spreads are only allowed in elements and components"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for attr in attributes {
|
|
||||||
template.diagnostic.push(
|
|
||||||
attr.span()
|
|
||||||
.error("Attributes are only allowed in elements and components"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(template)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +161,8 @@ impl ToTokens for TemplateBody {
|
|||||||
|
|
||||||
let index = self.template_idx.get();
|
let index = self.template_idx.get();
|
||||||
|
|
||||||
|
let diagnostics = &self.diagnostics;
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
dioxus_core::Element::Ok({
|
dioxus_core::Element::Ok({
|
||||||
#[doc(hidden)] // vscode please stop showing these in symbol search
|
#[doc(hidden)] // vscode please stop showing these in symbol search
|
||||||
@@ -197,6 +177,8 @@ impl ToTokens for TemplateBody {
|
|||||||
attr_paths: &[ #( #attr_paths ),* ],
|
attr_paths: &[ #( #attr_paths ),* ],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#diagnostics
|
||||||
|
|
||||||
{
|
{
|
||||||
// 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)]
|
#[allow(clippy::let_and_return)]
|
||||||
@@ -208,6 +190,7 @@ impl ToTokens for TemplateBody {
|
|||||||
);
|
);
|
||||||
__vnodes
|
__vnodes
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -225,7 +208,7 @@ impl TemplateBody {
|
|||||||
node_paths: Vec::new(),
|
node_paths: Vec::new(),
|
||||||
attr_paths: Vec::new(),
|
attr_paths: Vec::new(),
|
||||||
current_path: Vec::new(),
|
current_path: Vec::new(),
|
||||||
diagnostic: Diagnostics::new(),
|
diagnostics: Diagnostics::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Assign paths to all nodes in the template
|
// Assign paths to all nodes in the template
|
||||||
|
|||||||
Reference in new issue
Block a user