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:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2024-05-22 07:24:50 -05:00
1 parent 245003a5d4
commit 2523581cf4
21 files changed
+788 -217

No files matched your search

+123 -19
View File
@@ -11,18 +11,18 @@
//! - [ ] Keys
//! - [ ] Properties spreading with with `..` syntax
use self::location::CallerLocation;
use self::{location::CallerLocation, util::try_parse_braces};
use super::*;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use quote::{quote, quote_spanned};
use syn::{
ext::IdentExt, parse::ParseBuffer, spanned::Spanned, token::Brace,
AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Token,
ext::IdentExt, spanned::Spanned, token::Brace, AngleBracketedGenericArguments, Error, Expr,
Ident, LitStr, PathArguments, Token,
};
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
#[derive(Clone, Debug)]
pub struct Component {
pub name: syn::Path,
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
@@ -30,8 +30,35 @@ pub struct Component {
pub fields: Vec<ComponentField>,
pub children: Vec<BodyNode>,
pub manual_props: Option<Expr>,
pub brace: syn::token::Brace,
pub brace: Option<syn::token::Brace>,
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 {
@@ -42,8 +69,10 @@ impl Parse for Component {
// extract the path arguments from the path into prop_gen_args
let prop_gen_args = normalize_path(&mut name);
let content: ParseBuffer;
let brace = syn::braced!(content in stream);
let Ok((brace, content)) = try_parse_braces(stream) else {
// 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 children = Vec::new();
@@ -65,7 +94,7 @@ impl Parse for Component {
&& !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" {
_ = content.parse::<Ident>()?;
_ = content.parse::<Token![:]>()?;
@@ -94,8 +123,9 @@ impl Parse for Component {
fields,
children,
manual_props,
brace,
brace: Some(brace),
key,
errors: Vec::new(),
})
}
}
@@ -116,19 +146,78 @@ impl ToTokens for Component {
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({
#[allow(unused_imports)]
use dioxus_core::prelude::Properties;
(#builder).into_vcomponent(
#name #prop_gen_args,
#fn_name
)
})
})
};
let component = if errors.is_empty() {
component_node
} else {
quote_spanned! {
name.span() => {
#errors
#component_node
}
}
};
tokens.append_all(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<()> {
// ensure path segments doesn't have PathArguments, only the last
// segment is allowed to have one.
@@ -157,15 +246,18 @@ impl Component {
}
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 {
if field.name == "key" {
continue;
}
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 }}
}
@@ -173,23 +265,35 @@ impl Component {
let name = &self.name;
let mut toks = match &self.prop_gen_args {
Some(gen_args) => quote! { fc_to_builder(#name #gen_args) },
None => quote! { fc_to_builder(#name) },
Some(gen_args) => quote_spanned! { name.span() => fc_to_builder(#name #gen_args) },
None => quote_spanned! { name.span() => fc_to_builder(#name) },
};
for field in &self.fields {
toks.append_all(quote! {#field})
}
if !self.children.is_empty() {
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
}
fn fn_name(&self) -> 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