diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d54fa2f..c33adf7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,21 +2,23 @@ name: Rust on: push: - branches: [ "master" ] + branches: [ "main" ] pull_request: - branches: [ "master" ] + branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: build: - + strategy: + matrix: + rust: + - stable + - 1.64.0 runs-on: ubuntu-latest - steps: - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + - run: rustup default ${{ matrix.rust }} + - run: cargo build --verbose + - run: cargo test --verbose diff --git a/Cargo.toml b/Cargo.toml index 774d4ea..7a1d4e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,5 +23,6 @@ [dev-dependencies] - yew = { version = "0.21" } - trybuild = "1" + yew = { version = "0.21" } + trybuild = "1" + rustversion = "1" diff --git a/src/autoprops.rs b/src/autoprops.rs new file mode 100644 index 0000000..b0b3e69 --- /dev/null +++ b/src/autoprops.rs @@ -0,0 +1,258 @@ +use crate::function_component::FunctionComponentName; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned; + +#[derive(Clone)] +pub struct Autoprops { + item_fn: syn::ItemFn, + properties_name: syn::Ident, +} + +impl syn::parse::Parse for Autoprops { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let parsed: syn::Item = input.parse()?; + + let item_fn = match parsed { + syn::Item::Fn(m) => m, + item => { + return Err(syn::Error::new_spanned( + item, + "`autoprops` attribute can only be applied to functions", + )) + } + }; + + let syn::ItemFn { attrs, sig, .. } = &item_fn; + + let mut component_name = item_fn.sig.ident.clone(); + + attrs + .iter() + .find(|attr| { + match &attr.meta { + syn::Meta::Path(path) => { + if let Some(last_segment) = path.segments.last() { + if last_segment.ident == "function_component" { + return true; + } + } + } + syn::Meta::List(syn::MetaList { path, tokens, .. }) => { + if let Some(last_segment) = path.segments.last() { + if last_segment.ident == "function_component" { + if let Ok(attr) = + syn::parse2::(tokens.clone()) + { + if let Some(name) = attr.component_name { + component_name = name; + } + } + return true; + } + } + } + _ => {} + } + false + }) + .ok_or_else(|| { + syn::Error::new_spanned( + sig, + "could not find #[function_component] attribute in function declaration \ + (#[autoprops] must be placed *before* #[function_component])", + ) + })?; + + for input in &sig.inputs { + if let syn::FnArg::Typed(syn::PatType { pat, .. }) = input { + if let syn::Pat::Wild(wild) = pat.as_ref() { + return Err(syn::Error::new_spanned( + wild, + "cannot use `_` as field name", + )); + } + } + } + + let properties_name = syn::Ident::new( + &format!("{}Props", component_name), + proc_macro2::Span::call_site(), + ); + + Ok(Self { + properties_name, + item_fn, + }) + } +} + +impl Autoprops { + pub fn apply_args(&mut self, args: AutopropsArgs) { + if let Some(name) = args.properties_name { + self.properties_name = name; + } + } + + fn print_function_component(&self) -> proc_macro2::TokenStream { + let properties_name = &self.properties_name; + let syn::ItemFn { + attrs, + vis, + sig, + block, + } = &self.item_fn; + + let fn_name = &sig.ident; + let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); + let inputs = if self.needs_a_properties_struct() { + // NOTE: function components currently don't accept receivers, we're just passing the + // information to the next macro to fail and give its own error message + let receivers = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Receiver(receiver) => Some(receiver), + _ => None, + }) + .collect::>(); + let args = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { pat, .. }) => Some(quote! { #pat }), + _ => None, + }) + .collect::>(); + let phantom = sig.generics.type_params().next().is_some().then(|| { + quote! { + , phantom: _ + } + }); + quote! { + #(#receivers,)* #properties_name { + #(#args),* + #phantom + }: &#properties_name #type_generics + } + } else { + quote! {} + }; + let clones = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { pat, ty, .. }) + if !matches!(**ty, syn::Type::Reference(_)) => + { + Some(quote! { let #pat = ::std::clone::Clone::clone(#pat); }) + } + _ => None, + }) + .collect::>(); + + quote! { + #(#attrs)* + #vis fn #fn_name #impl_generics (#inputs) -> ::yew::Html #where_clause { + #(#clones)* + #block + } + } + } + + fn print_properties_struct(&self) -> proc_macro2::TokenStream { + let properties_name = &self.properties_name; + let syn::ItemFn { vis, sig, .. } = &self.item_fn; + + if !self.needs_a_properties_struct() { + return quote! {}; + } + + let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); + let fields = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { attrs, pat, ty, .. }) => match ty.as_ref() { + syn::Type::Reference(syn::TypeReference { elem, .. }) => { + Some(quote! { #(#attrs)* #pat: #elem, }) + } + _ => Some(quote! { #(#attrs)* #pat: #ty, }), + }, + _ => None, + }) + .collect::>(); + let type_params = sig + .generics + .type_params() + .map(|param| ¶m.ident) + .collect::>(); + let phantom = (!type_params.is_empty()).then(|| { + quote! { + #[prop_or_default] + phantom: ::std::marker::PhantomData <( #(#type_params),* )>, + } + }); + let fields_eq = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => Some(quote_spanned! { + ty.span() => self.#pat == rhs.#pat && + }), + _ => None, + }) + .collect::>(); + + quote! { + #[derive(::yew::Properties)] + #vis struct #properties_name #impl_generics #where_clause { + #(#fields)* + #phantom + } + + impl #impl_generics ::std::cmp::PartialEq for #properties_name #type_generics + #where_clause { + fn eq(&self, rhs: &Self) -> ::std::primitive::bool { + #(#fields_eq)* true + } + } + } + } + + fn needs_a_properties_struct(&self) -> bool { + let syn::ItemFn { sig, .. } = &self.item_fn; + !sig.inputs.is_empty() || sig.generics.type_params().next().is_some() + } +} + +impl quote::ToTokens for Autoprops { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let function_component = self.print_function_component(); + let properties_struct = self.print_properties_struct(); + + tokens.extend(quote! { + #function_component + #properties_struct + }) + } +} + +pub struct AutopropsArgs { + pub properties_name: Option, +} + +impl syn::parse::Parse for AutopropsArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if input.is_empty() { + return Ok(Self { + properties_name: None, + }); + } + + let properties_name = input.parse()?; + + Ok(Self { + properties_name: Some(properties_name), + }) + } +} diff --git a/src/function_component.rs b/src/function_component.rs new file mode 100644 index 0000000..eae5172 --- /dev/null +++ b/src/function_component.rs @@ -0,0 +1,24 @@ +// copy-pasted from yew-macro because proc-macro crates cannot export any items + +use syn::parse::{Parse, ParseStream}; +use syn::Ident; + +pub struct FunctionComponentName { + pub component_name: Option, +} + +impl Parse for FunctionComponentName { + fn parse(input: ParseStream) -> syn::Result { + if input.is_empty() { + return Ok(Self { + component_name: None, + }); + } + + let component_name = input.parse()?; + + Ok(Self { + component_name: Some(component_name), + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 0467478..58f086e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,170 +1,17 @@ -//! proc-macro to automatically derive Properties structs from args for Yew components -//! -//! No more extra one-off Props structs! -//! -//! -//! # Examples -//! -//! ``` -//! use yew_autoprops::autoprops_component; -//! use yew::prelude::*; -//! -//! #[autoprops_component] -//! fn CoolComponent(#[prop_or_default] hidden: bool, smth: &AttrValue) -> Html { -//! html! { -//!
-//!

{ smth }

-//!
-//! } -//! } -//! ``` -//! -//! ``` -//! use yew_autoprops::autoprops_component; -//! use yew::prelude::*; -//! -//! #[autoprops_component(CoolComponent)] -//! fn cool_component(#[prop_or_default] hidden: bool, smth: &AttrValue) -> Html { -//! html! { -//!
-//!

{ smth }

-//!
-//! } -//! } -//! ``` -//! -extern crate proc_macro; +mod autoprops; +mod function_component; -use core::panic; - -use proc_macro2::{Span, TokenStream, TokenTree}; -use quote::{quote, ToTokens}; -use syn::{parse_macro_input, FnArg, ItemFn, Pat, PatType, Type}; +use quote::ToTokens; +//#[proc_macro_error::proc_macro_error] #[proc_macro_attribute] -pub fn autoprops_component( - args: proc_macro::TokenStream, - input: proc_macro::TokenStream, +pub fn autoprops( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let args = TokenStream::from(args).into_iter().collect::>(); - - let function = parse_macro_input!(input as ItemFn); - - let fn_name = &function.sig.ident; - let visibility = &function.vis; - let generics = &function.sig.generics; - - let (component_name, struct_name) = match args.len() { - 0 => ( - None, - Some(syn::Ident::new( - &format!("{}Props", fn_name), - Span::call_site().into(), - )), - ), - 1 => { - let TokenTree::Ident(name) = &args[0] else { - panic!("Invalid argument: {}", args[0].to_string()); - }; - - ( - Some(name), - Some(syn::Ident::new( - &format!("{}Props", name), - Span::call_site().into(), - )), - ) - } - 3 => { - let TokenTree::Ident(name) = &args[0] else { - panic!("Invalid argument: {}", args[0].to_string()); - }; - - let TokenTree::Ident(props) = args[2].clone() else { - panic!("Invalid argument: {}", args[2].to_string()); - }; - - (Some(name), Some(props)) - } - _ => panic!("Invalid arguments: {:?}", args), - }; - - let arg_names = function - .sig - .inputs - .iter() - .map(|input| { - if let FnArg::Typed(PatType { pat, .. }) = input { - if let Pat::Ident(ident) = pat.as_ref() { - return &ident.ident; - } - } - panic!("Invalid argument: {}", input.to_token_stream()); - }) - .collect::>(); - - let mut fields = Vec::new(); - let mut arg_types = Vec::new(); - let mut clones = Vec::new(); - let mut partial_eq_constraints = Vec::new(); - for input in function.sig.inputs.iter() { - if let FnArg::Typed(PatType { pat, ty, attrs, .. }) = input { - let mut end_ty = ty; - - if let Type::Reference(ty_ref) = ty.as_ref() { - end_ty = &ty_ref.elem; - } else { - clones.push(quote! { - let #pat = #pat.clone(); - }); - } - - fields.push(quote! { - #(#attrs)* - pub #pat: #end_ty - }); - arg_types.push(ty.clone()); - partial_eq_constraints.push(quote! { #end_ty: PartialEq, }); - } else { - panic!("Invalid argument"); - } - } - - let (impl_generics, ty_generics, _) = generics.split_for_impl(); - let bounds = generics.where_clause.clone(); - - let where_clause = if generics.params.is_empty() { - quote! {} - } else { - quote! { - where - #(#partial_eq_constraints)* - #bounds - } - }; - - let destructure = quote! { - #struct_name { #(#arg_names),* } - }; - - let function_block = function.block; - - let tokens = quote! { - #[derive(::yew::Properties, PartialEq)] - #visibility struct #struct_name #impl_generics #where_clause { - #(#fields),* - } - - #[::yew::function_component(#component_name)] - #[allow(non_snake_case)] - #visibility fn #fn_name #impl_generics (#destructure: &#struct_name #ty_generics) -> ::yew::Html #where_clause { - #(#clones)* - #function_block - } - }; - - // panic!("\n{}", tokens.to_string()); + let mut autoprops = syn::parse_macro_input!(item as autoprops::Autoprops); + let args = syn::parse_macro_input!(attr as autoprops::AutopropsArgs); + autoprops.apply_args(args); - // Return the new tokens - tokens.into() + proc_macro::TokenStream::from(autoprops.into_token_stream()) } diff --git a/tests/cases/allow-refs-pass.rs b/tests/cases/allow-refs-pass.rs deleted file mode 100644 index 6de445c..0000000 --- a/tests/cases/allow-refs-pass.rs +++ /dev/null @@ -1,15 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component] -fn CoolComponent(test: i8, smth: &usize) -> Html { - println!("test: {}", test); - - html! { -
-

{ smth }

-
- } -} - -fn main() {} diff --git a/tests/cases/attrs-pass.rs b/tests/cases/attrs-pass.rs deleted file mode 100644 index 9396f0a..0000000 --- a/tests/cases/attrs-pass.rs +++ /dev/null @@ -1,43 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component] -fn CoolComponent(#[prop_or_default] test: &i8, smth: &usize) -> Html { - println!("test: {}", test); - - html! { -
-

{ smth }

-
- } -} - -#[autoprops_component] -fn CoolComponent2(me: &i8, #[prop_or_default] pls: &usize) -> Html { - println!("pls: {}", pls); - - html! { -
-

{ me }

-
- } -} - -#[autoprops_component] -fn CoolComponent3(me: &i8, #[prop_or(2)] pls: &usize) -> Html { - println!("pls: {}", pls); - - html! { -
-

{ me }

-
- } -} - -fn main() { - fn main() { - let _ = html! { }; - let _ = html! { }; - let _ = html! { }; - } -} diff --git a/tests/cases/generics-pass.rs b/tests/cases/generics-pass.rs deleted file mode 100644 index 727a320..0000000 --- a/tests/cases/generics-pass.rs +++ /dev/null @@ -1,20 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component] -fn GenericComponent(value: &T) -> Html { - html! { -
- { value.to_string() } -
- } -} - -fn main() { - html! { - <> - value=1 /> - value="hi" /> - - }; -} diff --git a/tests/cases/hooks-pass.rs b/tests/cases/hooks-pass.rs deleted file mode 100644 index a49287f..0000000 --- a/tests/cases/hooks-pass.rs +++ /dev/null @@ -1,20 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component(CoolComponent)] -fn cool_component(#[prop_or_default] test: &i8, smth: &usize) -> Html { - println!("test: {}", test); - - let test_hook = use_state(|| 0); - - html! { -
-

{ smth }

-

{ *test_hook }

-
- } -} - -fn main() { - let _ = html! { }; -} diff --git a/tests/cases/name-pass.rs b/tests/cases/name-pass.rs deleted file mode 100644 index e54870b..0000000 --- a/tests/cases/name-pass.rs +++ /dev/null @@ -1,17 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component(CoolComponent)] -fn cool_component(#[prop_or_default] test: &i8, smth: &usize) -> Html { - println!("test: {}", test); - - html! { -
-

{ smth }

-
- } -} - -fn main() { - let _ = html! { }; -} diff --git a/tests/cases/partialeq-fail.rs b/tests/cases/partialeq-fail.rs deleted file mode 100644 index d00603b..0000000 --- a/tests/cases/partialeq-fail.rs +++ /dev/null @@ -1,29 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -struct TestPEqStruct { - t: T, -} - -impl PartialEq for TestPEqStruct { - fn eq(&self, other: &Self) -> bool { - self.t == other.t - } -} - -struct NotPartialEq(); - -#[autoprops_component] -fn TestComponent(test_struct: &TestPEqStruct) -> Html { - html! { -
- { "TestComponent" } -
- } -} - -fn main() { - html! { - - }; -} diff --git a/tests/cases/partialeq-fail.stderr b/tests/cases/partialeq-fail.stderr deleted file mode 100644 index f35afa2..0000000 --- a/tests/cases/partialeq-fail.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0369]: binary operation `==` cannot be applied to type `TestPEqStruct` - --> tests/cases/partialeq-fail.rs:16:1 - | -16 | #[autoprops_component] - | ^^^^^^^^^^^^^^^^^^^^^^ - | -note: an implementation of `PartialEq` might be missing for `NotPartialEq` - --> tests/cases/partialeq-fail.rs:14:1 - | -14 | struct NotPartialEq(); - | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq` - = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` - | -14 + #[derive(PartialEq)] -15 | struct NotPartialEq(); - | diff --git a/tests/cases/partialeq-pass.rs b/tests/cases/partialeq-pass.rs deleted file mode 100644 index a873158..0000000 --- a/tests/cases/partialeq-pass.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::marker::PhantomData; -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -struct TestPEqStruct { - _phantom: PhantomData, -} - -impl PartialEq for TestPEqStruct { - fn eq(&self, _other: &Self) -> bool { - true - } -} - -struct NotPartialEq(); - -#[autoprops_component] -fn TestComponent(test_struct: &TestPEqStruct) -> Html { - assert!(test_struct == test_struct); - html! { -
- { "TestComponent" } -
- } -} - -fn main() { - html! { - - }; -} diff --git a/tests/function_attr_test.rs b/tests/function_attr_test.rs new file mode 100644 index 0000000..ed3cf9d --- /dev/null +++ b/tests/function_attr_test.rs @@ -0,0 +1,7 @@ +#[allow(dead_code)] +#[rustversion::attr(stable(1.64), test)] +fn tests() { + let t = trybuild::TestCases::new(); + t.pass("tests/function_component_attr/*-pass.rs"); + t.compile_fail("tests/function_component_attr/*-fail.rs"); +} diff --git a/tests/function_component_attr/autoprops-fail.rs b/tests/function_component_attr/autoprops-fail.rs new file mode 100644 index 0000000..36f1f70 --- /dev/null +++ b/tests/function_component_attr/autoprops-fail.rs @@ -0,0 +1,67 @@ +use yew::prelude::*; +use yew_autoprops::*; + +#[autoprops] +#[function_component] +fn CantAcceptReceiver(&self, b: bool) -> Html { + html! { +

{b}

+ } +} + +#[autoprops] +fn not_a_function_component(b: bool) -> Html { + html! { +

{b}

+ } +} + +#[function_component(WrongAttrsOrder)] +#[autoprops] +fn wrong_attrs_order(b: bool) -> Html { + html! { +

{b}

+ } +} + +#[autoprops] +#[function_component(let)] +fn BadFunctionComponent(b: bool) -> Html { + html! { +

{b}

+ } +} + +#[derive(PartialEq)] +struct NotClonable(u32); + +#[autoprops] +#[function_component] +fn TypeIsNotClone(stuff: NotClonable) -> Html { + drop(stuff); + html! { +

+ } +} + +#[derive(Clone)] +struct NotPartialEq(u32); + +#[autoprops] +#[function_component] +fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { + drop(stuff); + html! { +

+ } +} + +#[autoprops] +#[function_component] +fn InvalidFieldName(_: u32) -> Html { + html! { +

+ } +} + +fn main() {} diff --git a/tests/function_component_attr/autoprops-fail.stderr b/tests/function_component_attr/autoprops-fail.stderr new file mode 100644 index 0000000..d68f091 --- /dev/null +++ b/tests/function_component_attr/autoprops-fail.stderr @@ -0,0 +1,59 @@ +error: function components can't accept a receiver + --> tests/function_component_attr/autoprops-fail.rs:6:23 + | +6 | fn CantAcceptReceiver(&self, b: bool) -> Html { + | ^^^^^ + +error: could not find #[function_component] attribute in function declaration (#[autoprops] must be placed *before* #[function_component]) + --> tests/function_component_attr/autoprops-fail.rs:13:1 + | +13 | fn not_a_function_component(b: bool) -> Html { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected a reference to a `Properties` type (try: `&bool`) + --> tests/function_component_attr/autoprops-fail.rs:21:25 + | +21 | fn wrong_attrs_order(b: bool) -> Html { + | ^^^^ + +error: expected identifier, found keyword `let` + --> tests/function_component_attr/autoprops-fail.rs:28:22 + | +28 | #[function_component(let)] + | ^^^ + +error: cannot use `_` as field name + --> tests/function_component_attr/autoprops-fail.rs:61:21 + | +61 | fn InvalidFieldName(_: u32) -> Html { + | ^ + +error[E0277]: the trait bound `NotClonable: Clone` is not satisfied + --> tests/function_component_attr/autoprops-fail.rs:40:19 + | +38 | #[autoprops] + | ------------ required by a bound introduced by this call +39 | #[function_component] +40 | fn TypeIsNotClone(stuff: NotClonable) -> Html { + | ^^^^^ the trait `Clone` is not implemented for `NotClonable` + | +help: consider annotating `NotClonable` with `#[derive(Clone)]` + | +36 | #[derive(Clone)] + | + +error[E0369]: binary operation `==` cannot be applied to type `NotPartialEq` + --> tests/function_component_attr/autoprops-fail.rs:52:30 + | +52 | fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { + | ^^^^^^^^^^^^ + | +note: an implementation of `PartialEq<_>` might be missing for `NotPartialEq` + --> tests/function_component_attr/autoprops-fail.rs:48:1 + | +48 | struct NotPartialEq(u32); + | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq<_>` +help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` + | +48 | #[derive(PartialEq)] + | diff --git a/tests/function_component_attr/autoprops-pass.rs b/tests/function_component_attr/autoprops-pass.rs new file mode 100644 index 0000000..50ec504 --- /dev/null +++ b/tests/function_component_attr/autoprops-pass.rs @@ -0,0 +1,112 @@ +#![no_implicit_prelude] + +// Shadow primitives +#[allow(non_camel_case_types)] +pub struct bool; +#[allow(non_camel_case_types)] +pub struct char; +#[allow(non_camel_case_types)] +pub struct f32; +#[allow(non_camel_case_types)] +pub struct f64; +#[allow(non_camel_case_types)] +pub struct i128; +#[allow(non_camel_case_types)] +pub struct i16; +#[allow(non_camel_case_types)] +pub struct i32; +#[allow(non_camel_case_types)] +pub struct i64; +#[allow(non_camel_case_types)] +pub struct i8; +#[allow(non_camel_case_types)] +pub struct isize; +#[allow(non_camel_case_types)] +pub struct str; +#[allow(non_camel_case_types)] +pub struct u128; +#[allow(non_camel_case_types)] +pub struct u16; +#[allow(non_camel_case_types)] +pub struct u32; +#[allow(non_camel_case_types)] +pub struct u64; +#[allow(non_camel_case_types)] +pub struct u8; +#[allow(non_camel_case_types)] +pub struct usize; + +#[::yew_autoprops::autoprops] +#[::yew::function_component] +fn CompUseFnName() -> ::yew::Html +{ + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(CompNoProperties)] +fn comp_no_properties() -> ::yew::Html +{ + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(CompNoGenerics)] +fn comp_no_generics(#[prop_or_default] b: ::std::primitive::bool, a: &::yew::AttrValue) -> ::yew::Html +{ + let _: ::std::primitive::bool = b; + let _: &::yew::AttrValue = a; + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(CompGenerics)] +fn comp_generics(b: T1, a: &T2) -> ::yew::Html +where + T1: ::std::cmp::PartialEq + ::std::clone::Clone, + T2: ::std::cmp::PartialEq, +{ + let _: T1 = b; + let _: &T2 = a; + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(CompGenericsWithoutField)] +fn comp_generics_without_field(b: ::std::primitive::bool) -> ::yew::Html { + let _ = b; + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(ConstGenerics)] +fn const_generics(xs: [::std::primitive::u32; N]) -> ::yew::Html { + let _: [::std::primitive::u32; N] = xs; + ::yew::html! { +
+ { N } +
+ } +} + +fn compile_pass() { + let _ = ::yew::html! { }; + let _ = ::yew::html! { }; + let _ = ::yew::html! { }; + let _ = ::yew::html! { }; + let _ = ::yew::html! { b=true a="foo" /> }; + + let _ = ::yew::html! { xs={[1_u32, 2_u32]} /> }; +} + +fn main() {} diff --git a/tests/test.rs b/tests/test.rs deleted file mode 100644 index bc7edaa..0000000 --- a/tests/test.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[test] -fn tests() { - let t = trybuild::TestCases::new(); - t.pass("tests/cases/*-pass.rs"); - t.compile_fail("tests/cases/*-fail.rs"); -}