Skip to content

Commit

Permalink
fix: improved rust-analyzer support in #[component] macro (#2075)
Browse files Browse the repository at this point in the history
  • Loading branch information
gbj authored Nov 28, 2023
1 parent 4e8c3ac commit 18a92bb
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 99 deletions.
33 changes: 24 additions & 9 deletions leptos_macro/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,6 @@ impl ToTokens for Model {

let no_props = props.is_empty();

let mut body = body.to_owned();

// check for components that end ;
if !is_transparent {
let ends_semi =
Expand All @@ -139,7 +137,6 @@ impl ToTokens for Model {
}
}

body.sig.ident = format_ident!("__{}", body.sig.ident);
#[allow(clippy::redundant_clone)] // false positive
let body_name = body.sig.ident.clone();

Expand Down Expand Up @@ -234,6 +231,7 @@ impl ToTokens for Model {
quote! {}
};

let body_name = unmodified_fn_name_from_fn_name(&body_name);
let body_expr = if *is_island {
quote! {
::leptos::SharedContext::with_hydration(move || {
Expand Down Expand Up @@ -367,7 +365,6 @@ impl ToTokens for Model {
.collect::<TokenStream>();

let body = quote! {
#body
#destructure_props
#tracing_span_expr
#component
Expand Down Expand Up @@ -547,10 +544,10 @@ impl Model {
/// used to improve IDEs and rust-analyzer's auto-completion behavior in case
/// of a syntax error.
pub struct DummyModel {
attrs: Vec<Attribute>,
vis: Visibility,
sig: Signature,
body: TokenStream,
pub attrs: Vec<Attribute>,
pub vis: Visibility,
pub sig: Signature,
pub body: TokenStream,
}

impl Parse for DummyModel {
Expand Down Expand Up @@ -588,7 +585,21 @@ impl ToTokens for DummyModel {
let mut sig = sig.clone();
sig.inputs.iter_mut().for_each(|arg| {
if let FnArg::Typed(ty) = arg {
ty.attrs.clear();
ty.attrs.retain(|attr| match &attr.meta {
Meta::List(list) => list
.path
.segments
.first()
.map(|n| n.ident != "prop")
.unwrap_or(true),
Meta::NameValue(name_value) => name_value
.path
.segments
.first()
.map(|n| n.ident != "doc")
.unwrap_or(true),
_ => true,
});
}
});
sig
Expand Down Expand Up @@ -1162,3 +1173,7 @@ fn is_valid_into_view_return_type(ty: &ReturnType) -> bool {
.iter()
.any(|test| ty == test)
}

pub fn unmodified_fn_name_from_fn_name(ident: &Ident) -> Ident {
Ident::new(&format!("__{ident}"), ident.span())
}
83 changes: 51 additions & 32 deletions leptos_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
#[macro_use]
extern crate proc_macro_error;

use component::DummyModel;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenTree};
use quote::ToTokens;
use rstml::{node::KeyedAttribute, parse};
use syn::parse_macro_input;
use syn::{parse_macro_input, spanned::Spanned, token::Pub, Visibility};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Mode {
Expand All @@ -31,6 +32,7 @@ impl Default for Mode {

mod params;
mod view;
use crate::component::unmodified_fn_name_from_fn_name;
use view::{client_template::render_template, render_view};
mod component;
mod server;
Expand Down Expand Up @@ -598,21 +600,30 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
false
};

let parse_result = syn::parse::<component::Model>(s.clone());
let mut dummy = syn::parse::<DummyModel>(s.clone());
let parse_result = syn::parse::<component::Model>(s);

if let Ok(model) = parse_result {
model
.is_transparent(is_transparent)
.into_token_stream()
.into()
if let (Ok(ref mut unexpanded), Ok(model)) = (&mut dummy, parse_result) {
let expanded = model.is_transparent(is_transparent).into_token_stream();
unexpanded.sig.ident =
unmodified_fn_name_from_fn_name(&unexpanded.sig.ident);
quote! {
#expanded
#[doc(hidden)]
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
#unexpanded
}
} else if let Ok(mut dummy) = dummy {
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
quote! {
#[doc(hidden)]
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
#dummy
}
} else {
// When the input syntax is invalid, e.g. while typing, we let
// the dummy model output tokens similar to the input, which improves
// IDEs and rust-analyzer's auto-complete capabilities.
parse_macro_input!(s as component::DummyModel)
.into_token_stream()
.into()
quote! {}
}
.into()
}

/// Defines a component as an interactive island when you are using the
Expand Down Expand Up @@ -688,28 +699,36 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// ```
#[proc_macro_error::proc_macro_error]
#[proc_macro_attribute]
pub fn island(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
let is_transparent = if !args.is_empty() {
let transparent = parse_macro_input!(args as syn::Ident);
pub fn island(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
let mut dummy = syn::parse::<DummyModel>(s.clone());
let parse_result = syn::parse::<component::Model>(s);

if transparent != "transparent" {
abort!(
transparent,
"only `transparent` is supported";
help = "try `#[island(transparent)]` or `#[island]`"
);
if let (Ok(ref mut unexpanded), Ok(model)) = (&mut dummy, parse_result) {
let expanded = model.is_island().into_token_stream();
if !matches!(unexpanded.vis, Visibility::Public(_)) {
unexpanded.vis = Visibility::Public(Pub {
span: unexpanded.vis.span(),
})
}
unexpanded.sig.ident =
unmodified_fn_name_from_fn_name(&unexpanded.sig.ident);
quote! {
#expanded
#[doc(hidden)]
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
#unexpanded
}
} else if let Ok(mut dummy) = dummy {
dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
quote! {
#[doc(hidden)]
#[allow(non_snake_case, dead_code, clippy::too_many_arguments)]
#dummy
}

true
} else {
false
};

parse_macro_input!(s as component::Model)
.is_transparent(is_transparent)
.is_island()
.into_token_stream()
.into()
quote! {}
}
.into()
}

/// Annotates a struct so that it can be used with your Component as a `slot`.
Expand Down
9 changes: 1 addition & 8 deletions leptos_macro/tests/ui/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,32 @@ fn unknown_prop_option(#[prop(hello)] test: bool) -> impl IntoView {

#[component]
fn optional_and_optional_no_strip(
,
#[prop(optional, optional_no_strip)] conflicting: bool,
) -> impl IntoView {
_ = conflicting;
}

#[component]
fn optional_and_strip_option(
,
#[prop(optional, strip_option)] conflicting: bool,
) -> impl IntoView {
_ = conflicting;
}

#[component]
fn optional_no_strip_and_strip_option(
,
#[prop(optional_no_strip, strip_option)] conflicting: bool,
) -> impl IntoView {
_ = conflicting;
}

#[component]
fn default_without_value(
,
#[prop(default)] default: bool,
) -> impl IntoView {
fn default_without_value(#[prop(default)] default: bool) -> impl IntoView {
_ = default;
}

#[component]
fn default_with_invalid_value(
,
#[prop(default= |)] default: bool,
) -> impl IntoView {
_ = default;
Expand Down
72 changes: 22 additions & 50 deletions leptos_macro/tests/ui/component.stderr
Original file line number Diff line number Diff line change
@@ -1,33 +1,3 @@
error: expected parameter name, found `,`
--> tests/ui/component.rs:16:5
|
16 | ,
| ^ expected parameter name

error: expected parameter name, found `,`
--> tests/ui/component.rs:24:5
|
24 | ,
| ^ expected parameter name

error: expected parameter name, found `,`
--> tests/ui/component.rs:32:5
|
32 | ,
| ^ expected parameter name

error: expected parameter name, found `,`
--> tests/ui/component.rs:40:5
|
40 | ,
| ^ expected parameter name

error: expected parameter name, found `,`
--> tests/ui/component.rs:48:5
|
48 | ,
| ^ expected parameter name

error: return type is incorrect
--> tests/ui/component.rs:4:1
|
Expand All @@ -50,32 +20,34 @@ error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `de
10 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl IntoView {
| ^^^^^

error: expected one of: identifier, `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
--> tests/ui/component.rs:16:5
error: `optional` conflicts with mutually exclusive `optional_no_strip`
--> tests/ui/component.rs:16:12
|
16 | ,
| ^
16 | #[prop(optional, optional_no_strip)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: expected one of: identifier, `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
--> tests/ui/component.rs:24:5
error: `optional` conflicts with mutually exclusive `strip_option`
--> tests/ui/component.rs:23:12
|
24 | ,
| ^
23 | #[prop(optional, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^

error: expected one of: identifier, `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
--> tests/ui/component.rs:32:5
error: `optional_no_strip` conflicts with mutually exclusive `strip_option`
--> tests/ui/component.rs:30:12
|
32 | ,
| ^
30 | #[prop(optional_no_strip, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: expected one of: identifier, `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
--> tests/ui/component.rs:40:5
error: unexpected end of input, expected assignment `=`
--> tests/ui/component.rs:36:40
|
40 | ,
| ^
36 | fn default_without_value(#[prop(default)] default: bool) -> impl IntoView {
| ^

error: unexpected end of input, expected one of: identifier, `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`

error: expected one of: identifier, `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
--> tests/ui/component.rs:48:5
= help: try `#[prop(default=5 * 10)]`
--> tests/ui/component.rs:42:22
|
48 | ,
| ^
42 | #[prop(default= |)] default: bool,
| ^

0 comments on commit 18a92bb

Please sign in to comment.