feat: allow component declaration without `use leptos::Scope` in scope (#748)

This commit is contained in:
Lukas Potthast 2023-03-29 13:59:08 +02:00 committed by GitHub
parent e6b1298915
commit e9ff26abb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 138 additions and 6 deletions

View File

@ -16,6 +16,7 @@ attribute-derive = { version = "0.5", features = ["syn-full"] }
cfg-if = "1"
html-escape = "0.2"
itertools = "0.10"
once_cell = "1"
prettyplease = "0.1"
proc-macro-error = "1"
proc-macro2 = "1"

View File

@ -4,6 +4,7 @@ use convert_case::{
Casing,
};
use itertools::Itertools;
use once_cell::unsync::Lazy;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, ToTokens, TokenStreamExt};
use syn::{
@ -43,7 +44,7 @@ impl Parse for Model {
"this method requires a `Scope` parameter";
help = "try `fn {}(cx: Scope, /* ... */)`", item.sig.ident
);
} else if props[0].ty != parse_quote!(Scope) {
} else if !is_valid_scope_type(&props[0].ty) {
abort!(
item.sig.inputs,
"this method requires a `Scope` parameter";
@ -68,7 +69,7 @@ impl Parse for Model {
});
// Make sure return type is correct
if item.sig.output != parse_quote!(-> impl IntoView) {
if !is_valid_into_view_return_type(&item.sig.output) {
abort!(
item.sig,
"return type is incorrect";
@ -206,7 +207,7 @@ impl ToTokens for Model {
#tracing_instrument_attr
#vis fn #name #generics (
#[allow(unused_variables)]
#scope_name: Scope,
#scope_name: ::leptos::Scope,
props: #props_name #generics
) #ret #(+ #lifetimes)*
#where_clause
@ -436,7 +437,7 @@ impl ToTokens for TypedBuilderOpts {
fn prop_builder_fields(vis: &Visibility, props: &[Prop]) -> TokenStream {
props
.iter()
.filter(|Prop { ty, .. }| *ty != parse_quote!(Scope))
.filter(|Prop { ty, .. }| !is_valid_scope_type(ty))
.map(|prop| {
let Prop {
docs,
@ -463,7 +464,7 @@ fn prop_builder_fields(vis: &Visibility, props: &[Prop]) -> TokenStream {
fn prop_names(props: &[Prop]) -> TokenStream {
props
.iter()
.filter(|Prop { ty, .. }| *ty != parse_quote!(Scope))
.filter(|Prop { ty, .. }| !is_valid_scope_type(ty))
.map(|Prop { name, .. }| quote! { #name, })
.collect()
}
@ -642,3 +643,27 @@ fn prop_to_doc(
}
}
}
const VALID_SCOPE_TYPES: Lazy<Vec<Type>> = Lazy::new(|| {
vec![
parse_quote!(Scope),
parse_quote!(leptos::Scope),
parse_quote!(::leptos::Scope),
]
});
fn is_valid_scope_type(ty: &Type) -> bool {
VALID_SCOPE_TYPES.iter().any(|test| ty == test)
}
const VALID_INTO_VIEW_RETURN_TYPES: Lazy<Vec<ReturnType>> = Lazy::new(|| {
vec![
parse_quote!(-> impl IntoView),
parse_quote!(-> impl leptos::IntoView),
parse_quote!(-> impl ::leptos::IntoView),
]
});
fn is_valid_into_view_return_type(ty: &ReturnType) -> bool {
VALID_INTO_VIEW_RETURN_TYPES.iter().any(|test| ty == test)
}

View File

@ -1,5 +1,6 @@
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
t.compile_fail("tests/ui/component.rs");
t.compile_fail("tests/ui/component_absolute.rs");
}

View File

@ -0,0 +1,52 @@
#[::leptos::component]
fn missing_scope() {}
#[::leptos::component]
fn missing_return_type(cx: ::leptos::Scope) {}
#[::leptos::component]
fn unknown_prop_option(cx: ::leptos::Scope, #[prop(hello)] test: bool) -> impl ::leptos::IntoView {}
#[::leptos::component]
fn optional_and_optional_no_strip(
cx: Scope,
#[prop(optional, optional_no_strip)] conflicting: bool,
) -> impl IntoView {
}
#[::leptos::component]
fn optional_and_strip_option(
cx: ::leptos::Scope,
#[prop(optional, strip_option)] conflicting: bool,
) -> impl ::leptos::IntoView {
}
#[::leptos::component]
fn optional_no_strip_and_strip_option(
cx: ::leptos::Scope,
#[prop(optional_no_strip, strip_option)] conflicting: bool,
) -> impl ::leptos::IntoView {
}
#[::leptos::component]
fn default_without_value(
cx: ::leptos::Scope,
#[prop(default)] default: bool,
) -> impl ::leptos::IntoView {
}
#[::leptos::component]
fn default_with_invalid_value(
cx: ::leptos::Scope,
#[prop(default= |)] default: bool,
) -> impl ::leptos::IntoView {
}
#[::leptos::component]
pub fn using_the_view_macro(cx: ::leptos::Scope) -> impl ::leptos::IntoView {
::leptos::view! { cx,
"ok"
}
}
fn main() {}

View File

@ -0,0 +1,53 @@
error: this method requires a `Scope` parameter
--> tests/ui/component_absolute.rs:2:1
|
2 | fn missing_scope() {}
| ^^^^^^^^^^^^^^^^^^
|
= help: try `fn missing_scope(cx: Scope, /* ... */)`
error: return type is incorrect
--> tests/ui/component_absolute.rs:5:1
|
5 | fn missing_return_type(cx: ::leptos::Scope) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: return signature must be `-> impl IntoView`
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default` and `into`
--> tests/ui/component_absolute.rs:8:52
|
8 | fn unknown_prop_option(cx: ::leptos::Scope, #[prop(hello)] test: bool) -> impl ::leptos::IntoView {}
| ^^^^^
error: `optional` conflicts with mutually exclusive `optional_no_strip`
--> tests/ui/component_absolute.rs:13:12
|
13 | #[prop(optional, optional_no_strip)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `optional` conflicts with mutually exclusive `strip_option`
--> tests/ui/component_absolute.rs:20:12
|
20 | #[prop(optional, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^
error: `optional_no_strip` conflicts with mutually exclusive `strip_option`
--> tests/ui/component_absolute.rs:27:12
|
27 | #[prop(optional_no_strip, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unexpected end of input, expected assignment `=`
--> tests/ui/component_absolute.rs:34:19
|
34 | #[prop(default)] default: bool,
| ^
error: unexpected end of input, expected one of: `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
= help: try `#[prop(default=5 * 10)]`
--> tests/ui/component_absolute.rs:41:22
|
41 | #[prop(default= |)] default: bool,
| ^