From 1c77538faf4b3d9bf8ae9337c46a7fda18b53168 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Mon, 4 Mar 2024 17:51:31 -0300 Subject: [PATCH 01/10] Create attribute macro and start creating parse logic --- macros/src/attr/fn.rs | 37 +++++++++++++++++++++++++++++++++ macros/src/attr/mod.rs | 2 ++ macros/src/lib.rs | 23 ++++++++++++++++++++- macros/src/types/fn.rs | 46 +++++++++++++++++++++++++++++++++++++++++ macros/src/types/mod.rs | 2 ++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 macros/src/attr/fn.rs create mode 100644 macros/src/types/fn.rs diff --git a/macros/src/attr/fn.rs b/macros/src/attr/fn.rs new file mode 100644 index 000000000..42efdb82b --- /dev/null +++ b/macros/src/attr/fn.rs @@ -0,0 +1,37 @@ +use syn::{Ident, Result}; + +use super::{parse_assign_str, Inflection, parse_assign_inflection}; + +#[derive(Default)] +pub struct FnAttr { + pub args: Args, + pub rename: Option, + pub rename_all: Option, +} + +#[derive(Default)] +pub enum Args { + #[default] + Positional, + Named, +} + +impl TryFrom for Args { + type Error = syn::Error; + + fn try_from(s: String) -> Result { + match s.as_str() { + "named" => Ok(Self::Named), + "positional" => Ok(Self::Positional), + x => syn_err!(r#"Expected "named" or "positional", found "{}""#, x) + } + } +} + +impl_parse! { + FnAttr(input, output) { + "args" => output.args = parse_assign_str(input)?.try_into()?, + "rename" => output.rename = Some(parse_assign_str(input)?), + "rename_all" => output.rename_all = Some(parse_assign_inflection(input)?), + } +} diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs index 6bb7b34f1..df2d784cd 100644 --- a/macros/src/attr/mod.rs +++ b/macros/src/attr/mod.rs @@ -8,11 +8,13 @@ use syn::{ Error, Lit, Result, Token, }; pub use variant::*; +pub use r#fn::*; mod r#enum; mod field; mod r#struct; mod variant; +mod r#fn; #[derive(Copy, Clone, Debug)] pub enum Inflection { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index afb24c210..12083f891 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,11 +1,12 @@ #![macro_use] #![deny(unused)] +use attr::FnAttr; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{ parse_quote, spanned::Spanned, ConstParam, GenericParam, Generics, Item, LifetimeParam, Result, - TypeParam, WhereClause, + TypeParam, WhereClause, ItemFn, }; use crate::{deps::Dependencies, utils::format_generics}; @@ -338,3 +339,23 @@ fn entry(input: proc_macro::TokenStream) -> Result { Ok(ts.into_impl(ident, generics)) } + +#[proc_macro_attribute] +pub fn ts_rs_fn(attr: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream { + entry_fn(attr.into(), input.into()).map_or_else( + |e| e.into_compile_error().into(), + Into::into + ) +} + + + +fn entry_fn(attr: TokenStream, input: TokenStream) -> Result { + let input = syn::parse2::(input)?; + let attr = syn::parse2::(attr)?; + + let _foo = types::fn_def(&input, attr)?; + Ok(quote!( + #input + )) +} diff --git a/macros/src/types/fn.rs b/macros/src/types/fn.rs new file mode 100644 index 000000000..d5f8cced9 --- /dev/null +++ b/macros/src/types/fn.rs @@ -0,0 +1,46 @@ +use quote::ToTokens; +use syn::{ItemFn, Result, Error, PatType, spanned::Spanned, punctuated::Punctuated, token::{Comma, Paren, Brace}, FieldsUnnamed, FieldsNamed, Fields}; + +use crate::{attr::{FnAttr, Args}, DerivedTS}; + +pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { + let fields = input + .sig + .inputs + .iter() + .map(|x| match x { + syn::FnArg::Receiver(_) => Err( + Error::new(x.span(), "self parameter is not allowed") + ), + syn::FnArg::Typed(PatType { ty, attrs, pat, .. }) => { + Ok(syn::Field { + attrs: attrs.to_vec(), + vis: syn::Visibility::Inherited, + mutability: syn::FieldMutability::None, + ident: match fn_attr.args { + Args::Named => Some(syn::parse2(pat.to_token_stream())?), + Args::Positional => None, + }, + colon_token: None, + ty: *ty.clone(), + }) + }, + }) + .collect::>>()?; + + let fields = match (fields.is_empty(), fn_attr.args) { + (true, _) => Fields::Unit, + (_, Args::Positional) => Fields::Unnamed(FieldsUnnamed { + paren_token: Paren::default(), + unnamed: fields, + }), + (_, Args::Named) => Fields::Named(FieldsNamed { + brace_token: Brace::default(), + named: fields, + }), + }; + + Ok(DerivedTS { + ..todo!() + }) +} diff --git a/macros/src/types/mod.rs b/macros/src/types/mod.rs index 1bdd276d5..ced35be36 100644 --- a/macros/src/types/mod.rs +++ b/macros/src/types/mod.rs @@ -7,7 +7,9 @@ mod named; mod newtype; mod tuple; mod unit; +mod r#fn; +pub(crate) use r#fn::fn_def; pub(crate) use r#enum::r#enum_def; pub(crate) fn struct_def(s: &ItemStruct) -> Result { From bc7479b9464f8b0cb1037dd93123b62736eb8bc9 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 5 Mar 2024 10:50:33 -0300 Subject: [PATCH 02/10] Finish implementation of new attribute --- macros/src/attr/fn.rs | 14 +++-- macros/src/attr/mod.rs | 16 +++++- macros/src/lib.rs | 31 ++++++---- macros/src/types/fn.rs | 118 +++++++++++++++++++++++++++++--------- macros/src/types/mod.rs | 4 +- ts-rs/src/lib.rs | 11 +--- ts-rs/tests/fn.rs | 89 ++++++++++++++++++++++++++++ ts-rs/tests/struct_tag.rs | 1 - 8 files changed, 228 insertions(+), 56 deletions(-) create mode 100644 ts-rs/tests/fn.rs diff --git a/macros/src/attr/fn.rs b/macros/src/attr/fn.rs index 42efdb82b..a56a30d18 100644 --- a/macros/src/attr/fn.rs +++ b/macros/src/attr/fn.rs @@ -1,10 +1,11 @@ use syn::{Ident, Result}; -use super::{parse_assign_str, Inflection, parse_assign_inflection}; +use super::{parse_assign_inflection, parse_assign_str, Inflection}; #[derive(Default)] pub struct FnAttr { pub args: Args, + pub export_to: Option, pub rename: Option, pub rename_all: Option, } @@ -12,8 +13,8 @@ pub struct FnAttr { #[derive(Default)] pub enum Args { #[default] - Positional, - Named, + Flattened, + Inlined, } impl TryFrom for Args { @@ -21,9 +22,9 @@ impl TryFrom for Args { fn try_from(s: String) -> Result { match s.as_str() { - "named" => Ok(Self::Named), - "positional" => Ok(Self::Positional), - x => syn_err!(r#"Expected "named" or "positional", found "{}""#, x) + "inlined" => Ok(Self::Inlined), + "flattened" => Ok(Self::Flattened), + x => syn_err!(r#"Expected "inlined" or "flattened", found "{x}""#), } } } @@ -31,6 +32,7 @@ impl TryFrom for Args { impl_parse! { FnAttr(input, output) { "args" => output.args = parse_assign_str(input)?.try_into()?, + "export_to" => output.export_to = Some(parse_assign_str(input)?), "rename" => output.rename = Some(parse_assign_str(input)?), "rename_all" => output.rename_all = Some(parse_assign_inflection(input)?), } diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs index df2d784cd..e524281a5 100644 --- a/macros/src/attr/mod.rs +++ b/macros/src/attr/mod.rs @@ -2,19 +2,19 @@ use std::convert::TryFrom; pub use field::*; pub use r#enum::*; +pub use r#fn::*; pub use r#struct::*; use syn::{ parse::{Parse, ParseStream}, Error, Lit, Result, Token, }; pub use variant::*; -pub use r#fn::*; mod r#enum; mod field; +mod r#fn; mod r#struct; mod variant; -mod r#fn; #[derive(Copy, Clone, Debug)] pub enum Inflection { @@ -61,6 +61,18 @@ impl Inflection { Inflection::Kebab => string.to_kebab_case(), } } + + pub fn as_str(&self) -> &str { + match self { + Self::Lower => "lowercase", + Self::Upper => "UPPERCASE", + Self::Kebab => "kebab-case", + Self::Camel => "camelCase", + Self::Snake => "snake_case", + Self::Pascal => "PascalCase", + Self::ScreamingSnake => "SCREAMING_SNAKE_CASE", + } + } } impl TryFrom for Inflection { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 12083f891..c168bb9bf 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -2,12 +2,13 @@ #![deny(unused)] use attr::FnAttr; +use inflector::Inflector; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{ - parse_quote, spanned::Spanned, ConstParam, GenericParam, Generics, Item, LifetimeParam, Result, - TypeParam, WhereClause, ItemFn, + parse_quote, spanned::Spanned, ConstParam, GenericParam, Generics, Item, ItemFn, LifetimeParam, Result, TypeParam, WhereClause }; +use types::ParsedFn; use crate::{deps::Dependencies, utils::format_generics}; @@ -341,21 +342,31 @@ fn entry(input: proc_macro::TokenStream) -> Result { } #[proc_macro_attribute] -pub fn ts_rs_fn(attr: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream { - entry_fn(attr.into(), input.into()).map_or_else( - |e| e.into_compile_error().into(), - Into::into - ) +pub fn ts_rs_fn( + attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + entry_fn(attr.into(), input.into()).map_or_else(|e| e.into_compile_error().into(), Into::into) } - - fn entry_fn(attr: TokenStream, input: TokenStream) -> Result { let input = syn::parse2::(input)?; let attr = syn::parse2::(attr)?; - let _foo = types::fn_def(&input, attr)?; + let ident = format_ident!("{}Fn", input.sig.ident.to_string().to_pascal_case()); + + let ParsedFn { + args_struct, + derived_fn, + } = types::fn_def(&input, attr)?; + + let struct_impl = derived_fn.into_impl(ident.clone(), input.sig.generics.clone()); Ok(quote!( #input + + struct #ident; + #struct_impl + + #args_struct )) } diff --git a/macros/src/types/fn.rs b/macros/src/types/fn.rs index d5f8cced9..b9e4b55e0 100644 --- a/macros/src/types/fn.rs +++ b/macros/src/types/fn.rs @@ -1,46 +1,110 @@ -use quote::ToTokens; -use syn::{ItemFn, Result, Error, PatType, spanned::Spanned, punctuated::Punctuated, token::{Comma, Paren, Brace}, FieldsUnnamed, FieldsNamed, Fields}; +use inflector::Inflector; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Error, Field, FnArg, ItemFn, PatType, Result, TypeReference +}; -use crate::{attr::{FnAttr, Args}, DerivedTS}; +use crate::{ + attr::{Args, FnAttr}, + deps::Dependencies, + utils::{parse_docs, to_ts_ident}, + DerivedTS, +}; -pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { +pub struct ParsedFn { + pub args_struct: Option, + pub derived_fn: DerivedTS, +} + +pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { + let mut dependencies = Dependencies::default(); + + let ident = &input.sig.ident; + let generics = &input.sig.generics; + let (_, ty_generics, where_clause) = generics.split_for_impl(); + + let struct_ident = format_ident!("{}Args", ident.to_string().to_pascal_case()); let fields = input .sig .inputs .iter() .map(|x| match x { - syn::FnArg::Receiver(_) => Err( - Error::new(x.span(), "self parameter is not allowed") - ), - syn::FnArg::Typed(PatType { ty, attrs, pat, .. }) => { - Ok(syn::Field { + FnArg::Receiver(_) => Err(Error::new(x.span(), "self parameter is not allowed")), + FnArg::Typed(PatType { ty, attrs, pat, .. }) => { + dependencies.push(&ty); + Ok(Field { attrs: attrs.to_vec(), vis: syn::Visibility::Inherited, mutability: syn::FieldMutability::None, - ident: match fn_attr.args { - Args::Named => Some(syn::parse2(pat.to_token_stream())?), - Args::Positional => None, - }, + ident: Some(syn::parse2(pat.to_token_stream())?), colon_token: None, - ty: *ty.clone(), + ty: match ty.as_ref() { + syn::Type::Reference(TypeReference { elem, .. }) => parse_quote!(Box<#elem>), + x => x.clone(), + }, }) - }, + } }) .collect::>>()?; - let fields = match (fields.is_empty(), fn_attr.args) { - (true, _) => Fields::Unit, - (_, Args::Positional) => Fields::Unnamed(FieldsUnnamed { - paren_token: Paren::default(), - unnamed: fields, - }), - (_, Args::Named) => Fields::Named(FieldsNamed { - brace_token: Brace::default(), - named: fields, - }), + let FnAttr { + rename_all, + rename, + args, + export_to, + } = fn_attr; + let struct_attr = rename_all.map(|rename_all| { + let rename_all = rename_all.as_str(); + Some(quote!(#[ts(rename_all = #rename_all)])) + }); + + let args_struct = if fields.is_empty() { + None + } else { + Some(quote!( + #[derive(ts_rs::TS)] + #struct_attr + struct #struct_ident #ty_generics #where_clause { + #fields + } + )) + }; + + let docs = parse_docs(&input.attrs)?; + let ts_name = rename.clone().unwrap_or_else(|| to_ts_ident(ident)); + let return_ty = match input.sig.output { + syn::ReturnType::Default => quote!("void"), + syn::ReturnType::Type(_, ref ty) => { + dependencies.push(ty); + quote!(<#ty as ts_rs::TS>::name()) + } + }; + + let inline = match (&args_struct, args) { + (Some(_), Args::Inlined) => quote!(format!( + "(args: {}) => {}", + <#struct_ident as ts_rs::TS>::inline(), + #return_ty, + )), + (Some(_), Args::Flattened) => quote!(format!("({}) => {}", + <#struct_ident as ts_rs::TS>::inline_flattened().trim_matches(['{', '}', ' ']), + #return_ty + )), + (None, _) => quote!(format!("() => {}", #return_ty)), }; - Ok(DerivedTS { - ..todo!() + Ok(ParsedFn { + args_struct, + derived_fn: DerivedTS { + generics: generics.clone(), + ts_name, + docs, + inline, + inline_flattened: None, + dependencies, + export: true, + export_to, + }, }) } diff --git a/macros/src/types/mod.rs b/macros/src/types/mod.rs index ced35be36..f68cfa499 100644 --- a/macros/src/types/mod.rs +++ b/macros/src/types/mod.rs @@ -3,14 +3,14 @@ use syn::{Fields, Generics, Ident, ItemStruct, Result}; use crate::{attr::StructAttr, utils::to_ts_ident, DerivedTS}; mod r#enum; +mod r#fn; mod named; mod newtype; mod tuple; mod unit; -mod r#fn; -pub(crate) use r#fn::fn_def; pub(crate) use r#enum::r#enum_def; +pub(crate) use r#fn::*; pub(crate) fn struct_def(s: &ItemStruct) -> Result { let attr = StructAttr::from_attrs(&s.attrs)?; diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 2540d6204..692b3f93c 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -162,6 +162,7 @@ use std::{ path::{Path, PathBuf}, }; +pub use ts_rs_macros::ts_rs_fn; pub use ts_rs_macros::TS; pub use crate::export::ExportError; @@ -562,10 +563,7 @@ impl TS for Result { where Self: 'static, { - T::generics() - .push::() - .extend(E::generics()) - .push::() + T::generics().push::().extend(E::generics()).push::() } } @@ -658,10 +656,7 @@ impl TS for HashMap { where Self: 'static, { - K::generics() - .push::() - .extend(V::generics()) - .push::() + K::generics().push::().extend(V::generics()).push::() } } diff --git a/ts-rs/tests/fn.rs b/ts-rs/tests/fn.rs new file mode 100644 index 000000000..701a046b9 --- /dev/null +++ b/ts-rs/tests/fn.rs @@ -0,0 +1,89 @@ +#![allow(dead_code, unused)] +use ts_rs::{ts_rs_fn, TS}; + +#[ts_rs_fn(args = "inlined", export_to = "tests-out/fn/")] +fn my_void_function() {} + +#[ts_rs_fn(args = "inlined", export_to = "tests-out/fn/")] +fn my_non_void_function() -> String { + String::from("Hello world") +} + +#[ts_rs_fn(args = "inlined", export_to = "tests-out/fn/")] +fn my_void_function_with_inlined_args(str_arg: &str, int_arg: u32) {} + +#[ts_rs_fn(args = "flattened", export_to = "tests-out/fn/")] +fn my_void_function_with_flattened_args(str_arg: &str, int_arg: u32) {} + +#[ts_rs_fn(args = "inlined", export_to = "tests-out/fn/")] +fn my_non_void_function_with_inlined_args(str_arg: &str, int_arg: u32) -> String { + String::from("Hello world") +} + +#[ts_rs_fn(args = "flattened", export_to = "tests-out/fn/")] +fn my_non_void_function_with_flattened_args(str_arg: &str, int_arg: u32) -> String { + String::from("Hello world") +} + + +#[derive(TS)] +#[ts(export, export_to = "tests-out/fn/")] +struct Foo { + foo: u32, +} + +#[ts_rs_fn(export_to = "tests-out/fn/")] +fn function_with_imported_return() -> Foo { + Foo { foo: 0 } +} + +#[ts_rs_fn(export_to = "tests-out/fn/")] +fn function_with_imported_flattened_args(foo: Foo) {} + +#[ts_rs_fn(args = "inlined", export_to = "tests-out/fn/")] +fn function_with_imported_inlined_args(foo: Foo) {} + +#[test] +fn void_fn() { + assert_eq!(MyVoidFunctionFn::inline(), "() => void") +} + +#[test] +fn non_void_fn() { + assert_eq!(MyNonVoidFunctionFn::inline(), "() => string") +} + +#[test] +fn void_fn_inlined_args() { + assert_eq!(MyVoidFunctionWithInlinedArgsFn::inline(), "(args: { str_arg: string, int_arg: number, }) => void") +} + +#[test] +fn void_fn_flattened_args() { + assert_eq!(MyVoidFunctionWithFlattenedArgsFn::inline(), "(str_arg: string, int_arg: number,) => void") +} + +#[test] +fn non_void_fn_inlined_args() { + assert_eq!(MyNonVoidFunctionWithInlinedArgsFn::inline(), "(args: { str_arg: string, int_arg: number, }) => string") +} + +#[test] +fn non_void_fn_flattened_args() { + assert_eq!(MyNonVoidFunctionWithFlattenedArgsFn::inline(), "(str_arg: string, int_arg: number,) => string") +} + +#[test] +fn fn_with_imported_return() { + assert_eq!(FunctionWithImportedReturnFn::inline(), "() => Foo") +} + +#[test] +fn fn_with_imported_inlined_args() { + assert_eq!(FunctionWithImportedInlinedArgsFn::inline(), "(args: { foo: Foo, }) => void") +} + +#[test] +fn fn_with_imported_flattened_args() { + assert_eq!(FunctionWithImportedFlattenedArgsFn::inline(), "(foo: Foo,) => void") +} diff --git a/ts-rs/tests/struct_tag.rs b/ts-rs/tests/struct_tag.rs index 752a325f0..a1d8dd23d 100644 --- a/ts-rs/tests/struct_tag.rs +++ b/ts-rs/tests/struct_tag.rs @@ -20,4 +20,3 @@ fn test() { "{ type: \"TaggedType\", a: number, b: number, }" ) } - From dca62c094c9d9b91c802b330d53a16a2db8ad276 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 5 Mar 2024 11:19:15 -0300 Subject: [PATCH 03/10] Return Promise from async functions --- macros/src/types/fn.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/macros/src/types/fn.rs b/macros/src/types/fn.rs index b9e4b55e0..02f30759f 100644 --- a/macros/src/types/fn.rs +++ b/macros/src/types/fn.rs @@ -73,12 +73,18 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { let docs = parse_docs(&input.attrs)?; let ts_name = rename.clone().unwrap_or_else(|| to_ts_ident(ident)); - let return_ty = match input.sig.output { - syn::ReturnType::Default => quote!("void"), - syn::ReturnType::Type(_, ref ty) => { + let is_async = input.sig.asyncness.is_some(); + let return_ty = match (is_async, input.sig.output.clone()) { + (false, syn::ReturnType::Default) => quote!("void"), + (true, syn::ReturnType::Default) => quote!("Promise"), + (false, syn::ReturnType::Type(_, ref ty)) => { dependencies.push(ty); quote!(<#ty as ts_rs::TS>::name()) - } + }, + (true, syn::ReturnType::Type(_, ref ty)) => { + dependencies.push(ty); + quote!(format!("Promise<{}>", <#ty as ts_rs::TS>::name())) + }, }; let inline = match (&args_struct, args) { From 9b091112cdac69560a308254eda70d6703828263 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 5 Mar 2024 11:38:51 -0300 Subject: [PATCH 04/10] nicer if None else Some + make clippy happy --- macros/src/types/fn.rs | 14 +++++++------- ts-rs/tests/fn.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/macros/src/types/fn.rs b/macros/src/types/fn.rs index 02f30759f..8d1bc017b 100644 --- a/macros/src/types/fn.rs +++ b/macros/src/types/fn.rs @@ -1,3 +1,5 @@ +use std::ops::Not; + use inflector::Inflector; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; @@ -32,7 +34,7 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { .map(|x| match x { FnArg::Receiver(_) => Err(Error::new(x.span(), "self parameter is not allowed")), FnArg::Typed(PatType { ty, attrs, pat, .. }) => { - dependencies.push(&ty); + dependencies.push(ty); Ok(Field { attrs: attrs.to_vec(), vis: syn::Visibility::Inherited, @@ -59,17 +61,15 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { Some(quote!(#[ts(rename_all = #rename_all)])) }); - let args_struct = if fields.is_empty() { - None - } else { - Some(quote!( + let args_struct = fields.is_empty().not().then_some( + quote!( #[derive(ts_rs::TS)] #struct_attr struct #struct_ident #ty_generics #where_clause { #fields } - )) - }; + ) + ); let docs = parse_docs(&input.attrs)?; let ts_name = rename.clone().unwrap_or_else(|| to_ts_ident(ident)); diff --git a/ts-rs/tests/fn.rs b/ts-rs/tests/fn.rs index 701a046b9..1017c4c3a 100644 --- a/ts-rs/tests/fn.rs +++ b/ts-rs/tests/fn.rs @@ -1,4 +1,4 @@ -#![allow(dead_code, unused)] +#![allow(dead_code, unused, clippy::disallowed_names)] use ts_rs::{ts_rs_fn, TS}; #[ts_rs_fn(args = "inlined", export_to = "tests-out/fn/")] From 0f39fe3249db9243907d6a7c4a0adb0c59e59c76 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 10:38:02 -0300 Subject: [PATCH 05/10] Handle empty attribute --- macros/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c168bb9bf..29b34f585 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -351,7 +351,11 @@ pub fn ts_rs_fn( fn entry_fn(attr: TokenStream, input: TokenStream) -> Result { let input = syn::parse2::(input)?; - let attr = syn::parse2::(attr)?; + let attr = if !attr.is_empty() { + syn::parse2::(attr)? + } else { + FnAttr::default() + }; let ident = format_ident!("{}Fn", input.sig.ident.to_string().to_pascal_case()); From 71c0f40bc326b6689402b9824aa5e2a6db7e6945 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 10:40:38 -0300 Subject: [PATCH 06/10] Make function types PascalCase --- macros/src/types/fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/src/types/fn.rs b/macros/src/types/fn.rs index 8d1bc017b..2749b3f38 100644 --- a/macros/src/types/fn.rs +++ b/macros/src/types/fn.rs @@ -72,7 +72,7 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { ); let docs = parse_docs(&input.attrs)?; - let ts_name = rename.clone().unwrap_or_else(|| to_ts_ident(ident)); + let ts_name = rename.clone().unwrap_or_else(|| to_ts_ident(ident)).to_pascal_case(); let is_async = input.sig.asyncness.is_some(); let return_ty = match (is_async, input.sig.output.clone()) { (false, syn::ReturnType::Default) => quote!("void"), From e05e7e182734cd67cc9c6cbeeab2defcde36a00d Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 3 Apr 2024 14:22:14 -0300 Subject: [PATCH 07/10] Fix compiler error and add crate_rename --- macros/src/attr/fn.rs | 12 ++++++++++-- macros/src/types/fn.rs | 15 +++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/macros/src/attr/fn.rs b/macros/src/attr/fn.rs index a56a30d18..b7a11e8da 100644 --- a/macros/src/attr/fn.rs +++ b/macros/src/attr/fn.rs @@ -1,15 +1,22 @@ -use syn::{Ident, Result}; +use syn::{Ident, Result, Path, parse_quote}; -use super::{parse_assign_inflection, parse_assign_str, Inflection}; +use super::{parse_assign_inflection, parse_assign_str, Inflection, parse_assign_from_str}; #[derive(Default)] pub struct FnAttr { + crate_rename: Option, pub args: Args, pub export_to: Option, pub rename: Option, pub rename_all: Option, } +impl FnAttr { + pub fn crate_rename(&self) -> Path { + self.crate_rename.clone().unwrap_or_else(|| parse_quote!(::ts_rs)) + } +} + #[derive(Default)] pub enum Args { #[default] @@ -31,6 +38,7 @@ impl TryFrom for Args { impl_parse! { FnAttr(input, output) { + "crate" => output.crate_rename = Some(parse_assign_from_str(input)?), "args" => output.args = parse_assign_str(input)?.try_into()?, "export_to" => output.export_to = Some(parse_assign_str(input)?), "rename" => output.rename = Some(parse_assign_str(input)?), diff --git a/macros/src/types/fn.rs b/macros/src/types/fn.rs index 42678be4e..0b6814695 100644 --- a/macros/src/types/fn.rs +++ b/macros/src/types/fn.rs @@ -21,7 +21,7 @@ pub struct ParsedFn { } pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { - let mut dependencies = Dependencies::default(); + let mut dependencies = Dependencies::new(fn_attr.crate_rename()); let ident = &input.sig.ident; let generics = &input.sig.generics; @@ -53,11 +53,13 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { }) .collect::>>()?; + let crate_rename = fn_attr.crate_rename(); let FnAttr { rename_all, rename, args, export_to, + .. } = fn_attr; let struct_attr = rename_all.map(|rename_all| { let rename_all = rename_all.as_str(); @@ -65,7 +67,7 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { }); let args_struct = fields.is_empty().not().then_some(quote!( - #[derive(ts_rs::TS)] + #[derive(#crate_rename::TS)] #struct_attr struct #struct_ident #ty_generics #where_clause { #fields @@ -83,22 +85,22 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { (true, syn::ReturnType::Default) => quote!("Promise"), (false, syn::ReturnType::Type(_, ref ty)) => { dependencies.push(ty); - quote!(<#ty as ts_rs::TS>::name()) + quote!(<#ty as #crate_rename::TS>::name()) } (true, syn::ReturnType::Type(_, ref ty)) => { dependencies.push(ty); - quote!(format!("Promise<{}>", <#ty as ts_rs::TS>::name())) + quote!(format!("Promise<{}>", <#ty as #crate_rename::TS>::name())) } }; let inline = match (&args_struct, args) { (Some(_), Args::Inlined) => quote!(format!( "(args: {}) => {}", - <#struct_ident as ts_rs::TS>::inline(), + <#struct_ident as #crate_rename::TS>::inline(), #return_ty, )), (Some(_), Args::Flattened) => quote!(format!("({}) => {}", - <#struct_ident as ts_rs::TS>::inline_flattened().trim_matches(['{', '}', ' ']), + <#struct_ident as #crate_rename::TS>::inline_flattened().trim_matches(['{', '}', ' ']), #return_ty )), (None, _) => quote!(format!("() => {}", #return_ty)), @@ -107,6 +109,7 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { Ok(ParsedFn { args_struct, derived_fn: DerivedTS { + crate_rename, ts_name, docs, inline, From 5a96177bd2afe77c1c450eaf805ef2a7b24b08ad Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 3 Apr 2024 14:22:14 -0300 Subject: [PATCH 08/10] Fix compiler error and add crate_rename --- macros/src/attr/fn.rs | 12 ++++++++++-- macros/src/types/fn.rs | 15 +++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/macros/src/attr/fn.rs b/macros/src/attr/fn.rs index a56a30d18..b7a11e8da 100644 --- a/macros/src/attr/fn.rs +++ b/macros/src/attr/fn.rs @@ -1,15 +1,22 @@ -use syn::{Ident, Result}; +use syn::{Ident, Result, Path, parse_quote}; -use super::{parse_assign_inflection, parse_assign_str, Inflection}; +use super::{parse_assign_inflection, parse_assign_str, Inflection, parse_assign_from_str}; #[derive(Default)] pub struct FnAttr { + crate_rename: Option, pub args: Args, pub export_to: Option, pub rename: Option, pub rename_all: Option, } +impl FnAttr { + pub fn crate_rename(&self) -> Path { + self.crate_rename.clone().unwrap_or_else(|| parse_quote!(::ts_rs)) + } +} + #[derive(Default)] pub enum Args { #[default] @@ -31,6 +38,7 @@ impl TryFrom for Args { impl_parse! { FnAttr(input, output) { + "crate" => output.crate_rename = Some(parse_assign_from_str(input)?), "args" => output.args = parse_assign_str(input)?.try_into()?, "export_to" => output.export_to = Some(parse_assign_str(input)?), "rename" => output.rename = Some(parse_assign_str(input)?), diff --git a/macros/src/types/fn.rs b/macros/src/types/fn.rs index 42678be4e..0b6814695 100644 --- a/macros/src/types/fn.rs +++ b/macros/src/types/fn.rs @@ -21,7 +21,7 @@ pub struct ParsedFn { } pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { - let mut dependencies = Dependencies::default(); + let mut dependencies = Dependencies::new(fn_attr.crate_rename()); let ident = &input.sig.ident; let generics = &input.sig.generics; @@ -53,11 +53,13 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { }) .collect::>>()?; + let crate_rename = fn_attr.crate_rename(); let FnAttr { rename_all, rename, args, export_to, + .. } = fn_attr; let struct_attr = rename_all.map(|rename_all| { let rename_all = rename_all.as_str(); @@ -65,7 +67,7 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { }); let args_struct = fields.is_empty().not().then_some(quote!( - #[derive(ts_rs::TS)] + #[derive(#crate_rename::TS)] #struct_attr struct #struct_ident #ty_generics #where_clause { #fields @@ -83,22 +85,22 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { (true, syn::ReturnType::Default) => quote!("Promise"), (false, syn::ReturnType::Type(_, ref ty)) => { dependencies.push(ty); - quote!(<#ty as ts_rs::TS>::name()) + quote!(<#ty as #crate_rename::TS>::name()) } (true, syn::ReturnType::Type(_, ref ty)) => { dependencies.push(ty); - quote!(format!("Promise<{}>", <#ty as ts_rs::TS>::name())) + quote!(format!("Promise<{}>", <#ty as #crate_rename::TS>::name())) } }; let inline = match (&args_struct, args) { (Some(_), Args::Inlined) => quote!(format!( "(args: {}) => {}", - <#struct_ident as ts_rs::TS>::inline(), + <#struct_ident as #crate_rename::TS>::inline(), #return_ty, )), (Some(_), Args::Flattened) => quote!(format!("({}) => {}", - <#struct_ident as ts_rs::TS>::inline_flattened().trim_matches(['{', '}', ' ']), + <#struct_ident as #crate_rename::TS>::inline_flattened().trim_matches(['{', '}', ' ']), #return_ty )), (None, _) => quote!(format!("() => {}", #return_ty)), @@ -107,6 +109,7 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { Ok(ParsedFn { args_struct, derived_fn: DerivedTS { + crate_rename, ts_name, docs, inline, From 97f6e04a4a4cbdb47d0b4c614c4e66dad785b0fd Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 9 Apr 2024 14:15:20 -0300 Subject: [PATCH 09/10] Remove use of inflector --- macros/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index be1f17890..a10b75db5 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -3,8 +3,7 @@ use std::collections::{HashMap, HashSet}; -use attr::FnAttr; -use inflector::Inflector; +use attr::{FnAttr, Inflection}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{ @@ -463,7 +462,10 @@ fn entry_fn(attr: TokenStream, input: TokenStream) -> Result { FnAttr::default() }; - let ident = format_ident!("{}Fn", input.sig.ident.to_string().to_pascal_case()); + let ident = format_ident!( + "{}Fn", + Inflection::Pascal.apply(&input.sig.ident.to_string()) + ); let ParsedFn { args_struct, From d2e94d4ae724f216ada6fd2f169e9d371bd337c3 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Tue, 9 Apr 2024 14:19:27 -0300 Subject: [PATCH 10/10] Remove use of inflector --- macros/src/attr/mod.rs | 1 + macros/src/types/fn.rs | 10 +++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs index f7285cf37..7e2bc38ee 100644 --- a/macros/src/attr/mod.rs +++ b/macros/src/attr/mod.rs @@ -110,6 +110,7 @@ impl Inflection { Self::Snake => "snake_case", Self::Pascal => "PascalCase", Self::ScreamingSnake => "SCREAMING_SNAKE_CASE", + Self::ScreamingKebab => "SCREAMING-KEBAB-CASE", } } } diff --git a/macros/src/types/fn.rs b/macros/src/types/fn.rs index 0b6814695..7094b98a7 100644 --- a/macros/src/types/fn.rs +++ b/macros/src/types/fn.rs @@ -1,6 +1,5 @@ use std::{collections::HashMap, ops::Not}; -use inflector::Inflector; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ @@ -9,7 +8,7 @@ use syn::{ }; use crate::{ - attr::{Args, FnAttr}, + attr::{Args, FnAttr, Inflection}, deps::Dependencies, utils::{parse_docs, to_ts_ident}, DerivedTS, @@ -27,7 +26,7 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { let generics = &input.sig.generics; let (_, ty_generics, where_clause) = generics.split_for_impl(); - let struct_ident = format_ident!("{}Args", ident.to_string().to_pascal_case()); + let struct_ident = format_ident!("{}Args", Inflection::Pascal.apply(&ident.to_string())); let fields = input .sig .inputs @@ -75,10 +74,7 @@ pub fn fn_def(input: &ItemFn, fn_attr: FnAttr) -> Result { )); let docs = parse_docs(&input.attrs)?; - let ts_name = rename - .clone() - .unwrap_or_else(|| to_ts_ident(ident)) - .to_pascal_case(); + let ts_name = Inflection::Pascal.apply(&rename.clone().unwrap_or_else(|| to_ts_ident(ident))); let is_async = input.sig.asyncness.is_some(); let return_ty = match (is_async, input.sig.output.clone()) { (false, syn::ReturnType::Default) => quote!("void"),