1 | use convert_case::{Case, Casing}; |
2 | use quote::ToTokens; |
3 | use std::fmt::{Display, Formatter}; |
4 | use syn::{Pat, PathArguments, PathSegment}; |
5 | |
6 | use super::{ty_to_ts_type, ToTypeDef, TypeDef}; |
7 | use crate::{js_doc_from_comments, CallbackArg, FnKind, NapiFn}; |
8 | |
9 | pub(crate) struct FnArg { |
10 | pub(crate) arg: String, |
11 | pub(crate) ts_type: String, |
12 | pub(crate) is_optional: bool, |
13 | } |
14 | |
15 | pub(crate) struct FnArgList { |
16 | this: Option<FnArg>, |
17 | args: Vec<FnArg>, |
18 | last_required: Option<usize>, |
19 | } |
20 | |
21 | impl Display for FnArgList { |
22 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
23 | if let Some(this: &FnArg) = &self.this { |
24 | write!(f, "this: {}" , this.ts_type)?; |
25 | } |
26 | for (i: usize, arg: &FnArg) in self.args.iter().enumerate() { |
27 | if i != 0 || self.this.is_some() { |
28 | write!(f, ", " )?; |
29 | } |
30 | let is_optional: bool = arg.is_optional |
31 | && self |
32 | .last_required |
33 | .map_or(default:true, |last_required: usize| i > last_required); |
34 | if is_optional { |
35 | write!(f, " {}?: {}" , arg.arg, arg.ts_type)?; |
36 | } else { |
37 | write!(f, " {}: {}" , arg.arg, arg.ts_type)?; |
38 | } |
39 | } |
40 | Ok(()) |
41 | } |
42 | } |
43 | |
44 | impl FromIterator<FnArg> for FnArgList { |
45 | fn from_iter<T: IntoIterator<Item = FnArg>>(iter: T) -> Self { |
46 | let mut args: Vec = Vec::new(); |
47 | let mut this: Option = None; |
48 | for arg: FnArg in iter.into_iter() { |
49 | if arg.arg != "this" { |
50 | args.push(arg); |
51 | } else { |
52 | this = Some(arg); |
53 | } |
54 | } |
55 | let last_required: Option = argsOption<(usize, &FnArg)> |
56 | .iter() |
57 | .enumerate() |
58 | .rfind(|(_, arg: &&FnArg)| !arg.is_optional) |
59 | .map(|(i: usize, _)| i); |
60 | FnArgList { |
61 | this, |
62 | args, |
63 | last_required, |
64 | } |
65 | } |
66 | } |
67 | |
68 | impl ToTypeDef for NapiFn { |
69 | fn to_type_def(&self) -> Option<TypeDef> { |
70 | if self.skip_typescript { |
71 | return None; |
72 | } |
73 | |
74 | let def = format!( |
75 | r#" {prefix} {name}{generic}( {args}) {ret}"# , |
76 | prefix = self.gen_ts_func_prefix(), |
77 | name = &self.js_name, |
78 | generic = &self |
79 | .ts_generic_types |
80 | .as_ref() |
81 | .map(|g| format!("< {}>" , g)) |
82 | .unwrap_or_default(), |
83 | args = self |
84 | .ts_args_type |
85 | .clone() |
86 | .unwrap_or_else(|| self.gen_ts_func_args()), |
87 | ret = self |
88 | .ts_return_type |
89 | .clone() |
90 | .map(|t| format!(": {}" , t)) |
91 | .unwrap_or_else(|| self.gen_ts_func_ret()), |
92 | ); |
93 | |
94 | Some(TypeDef { |
95 | kind: "fn" .to_owned(), |
96 | name: self.js_name.clone(), |
97 | original_name: None, |
98 | def, |
99 | js_mod: self.js_mod.to_owned(), |
100 | js_doc: js_doc_from_comments(&self.comments), |
101 | }) |
102 | } |
103 | } |
104 | |
105 | fn gen_callback_type(callback: &CallbackArg) -> String { |
106 | format!( |
107 | "( {args}) => {ret}" , |
108 | args = &callback |
109 | .args |
110 | .iter() |
111 | .enumerate() |
112 | .map(|(i, arg)| { |
113 | let (ts_type, is_optional) = ty_to_ts_type(arg, false, false, false); |
114 | FnArg { |
115 | arg: format!("arg {}" , i), |
116 | ts_type, |
117 | is_optional, |
118 | } |
119 | }) |
120 | .collect::<FnArgList>(), |
121 | ret = match &callback.ret { |
122 | Some(ty) => ty_to_ts_type(ty, true, false, false).0, |
123 | None => "void" .to_owned(), |
124 | } |
125 | ) |
126 | } |
127 | |
128 | impl NapiFn { |
129 | fn gen_ts_func_args(&self) -> String { |
130 | format!( |
131 | " {}" , |
132 | self |
133 | .args |
134 | .iter() |
135 | .filter_map(|arg| match &arg.kind { |
136 | crate::NapiFnArgKind::PatType(path) => { |
137 | let ty_string = path.ty.to_token_stream().to_string(); |
138 | if ty_string == "Env" { |
139 | return None; |
140 | } |
141 | if let syn::Type::Path(path) = path.ty.as_ref() { |
142 | if let Some(PathSegment { ident, arguments }) = path.path.segments.last() { |
143 | if ident == "Reference" || ident == "WeakReference" { |
144 | if let Some(parent) = &self.parent { |
145 | if let PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { |
146 | args: angle_bracketed_args, |
147 | .. |
148 | }) = arguments |
149 | { |
150 | if let Some(syn::GenericArgument::Type(syn::Type::Path(syn::TypePath { |
151 | path, |
152 | .. |
153 | }))) = angle_bracketed_args.first() |
154 | { |
155 | if let Some(segment) = path.segments.first() { |
156 | if *parent == segment.ident { |
157 | // If we have a Reference<A> in an impl A block, it shouldn't be an arg |
158 | return None; |
159 | } |
160 | } |
161 | } |
162 | } |
163 | } |
164 | } |
165 | if ident == "This" || ident == "this" { |
166 | if self.kind != FnKind::Normal { |
167 | return None; |
168 | } |
169 | if let PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { |
170 | args: angle_bracketed_args, |
171 | .. |
172 | }) = arguments |
173 | { |
174 | if let Some(syn::GenericArgument::Type(ty)) = angle_bracketed_args.first() { |
175 | let (ts_type, _) = ty_to_ts_type(ty, false, false, false); |
176 | return Some(FnArg { |
177 | arg: "this" .to_owned(), |
178 | ts_type, |
179 | is_optional: false, |
180 | }); |
181 | } |
182 | } else { |
183 | return Some(FnArg { |
184 | arg: "this" .to_owned(), |
185 | ts_type: "this" .to_owned(), |
186 | is_optional: false, |
187 | }); |
188 | } |
189 | return None; |
190 | } |
191 | } |
192 | } |
193 | |
194 | let mut path = path.clone(); |
195 | // remove mutability from PatIdent |
196 | if let Pat::Ident(i) = path.pat.as_mut() { |
197 | i.mutability = None; |
198 | } |
199 | |
200 | let (ts_type, is_optional) = ty_to_ts_type(&path.ty, false, false, false); |
201 | let ts_type = arg.use_overridden_type_or(|| ts_type); |
202 | let arg = path.pat.to_token_stream().to_string().to_case(Case::Camel); |
203 | |
204 | Some(FnArg { |
205 | arg, |
206 | ts_type, |
207 | is_optional, |
208 | }) |
209 | } |
210 | crate::NapiFnArgKind::Callback(cb) => { |
211 | let ts_type = arg.use_overridden_type_or(|| gen_callback_type(cb)); |
212 | let arg = cb.pat.to_token_stream().to_string().to_case(Case::Camel); |
213 | |
214 | Some(FnArg { |
215 | arg, |
216 | ts_type, |
217 | is_optional: false, |
218 | }) |
219 | } |
220 | }) |
221 | .collect::<FnArgList>() |
222 | ) |
223 | } |
224 | |
225 | fn gen_ts_func_prefix(&self) -> &'static str { |
226 | if self.parent.is_some() { |
227 | match self.kind { |
228 | crate::FnKind::Normal => match self.fn_self { |
229 | Some(_) => "" , |
230 | None => "static" , |
231 | }, |
232 | crate::FnKind::Factory => "static" , |
233 | crate::FnKind::Constructor => "" , |
234 | crate::FnKind::Getter => "get" , |
235 | crate::FnKind::Setter => "set" , |
236 | } |
237 | } else { |
238 | "export function" |
239 | } |
240 | } |
241 | |
242 | fn gen_ts_func_ret(&self) -> String { |
243 | match self.kind { |
244 | FnKind::Constructor | FnKind::Setter => "" .to_owned(), |
245 | FnKind::Factory => self |
246 | .parent |
247 | .clone() |
248 | .map(|i| { |
249 | let parent = i.to_string().to_case(Case::Pascal); |
250 | if self.is_async { |
251 | format!(": Promise< {}>" , parent) |
252 | } else { |
253 | format!(": {}" , parent) |
254 | } |
255 | }) |
256 | .unwrap_or_else(|| "" .to_owned()), |
257 | _ => { |
258 | let ret = if let Some(ret) = &self.ret { |
259 | let (ts_type, _) = ty_to_ts_type(ret, true, false, false); |
260 | if ts_type == "undefined" { |
261 | "void" .to_owned() |
262 | } else if ts_type == "Self" { |
263 | "this" .to_owned() |
264 | } else { |
265 | ts_type |
266 | } |
267 | } else { |
268 | "void" .to_owned() |
269 | }; |
270 | |
271 | if self.is_async { |
272 | format!(": Promise< {}>" , ret) |
273 | } else { |
274 | format!(": {}" , ret) |
275 | } |
276 | } |
277 | } |
278 | } |
279 | } |
280 | |