1 | use convert_case::{Case, Casing}; |
2 | use quote::ToTokens; |
3 | use std::fmt::{Display, Formatter}; |
4 | use syn::{Member, 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 | fn gen_ts_func_arg(pat: &Pat) -> String { |
129 | match pat { |
130 | Pat::Struct(s) => format!( |
131 | " {{ {} }}" , |
132 | s.fields |
133 | .iter() |
134 | .map(|field| { |
135 | let member_str = match &field.member { |
136 | Member::Named(ident) => ident.to_string(), |
137 | Member::Unnamed(index) => format!("field {}" , index.index), |
138 | }; |
139 | let nested_str = gen_ts_func_arg(&field.pat); |
140 | if member_str == nested_str { |
141 | member_str.to_case(Case::Camel) |
142 | } else { |
143 | format!(" {}: {}" , member_str.to_case(Case::Camel), nested_str) |
144 | } |
145 | }) |
146 | .collect::<Vec<_>>() |
147 | .join(", " ) |
148 | .as_str() |
149 | ), |
150 | Pat::TupleStruct(ts) => format!( |
151 | " {{ {} }}" , |
152 | ts.elems |
153 | .iter() |
154 | .enumerate() |
155 | .map(|(index, elem)| { |
156 | let member_str = format!("field {}" , index); |
157 | let nested_str = gen_ts_func_arg(elem); |
158 | format!(" {}: {}" , member_str, nested_str) |
159 | }) |
160 | .collect::<Vec<_>>() |
161 | .join(", " ), |
162 | ), |
163 | Pat::Tuple(t) => format!( |
164 | "[ {}]" , |
165 | t.elems |
166 | .iter() |
167 | .map(gen_ts_func_arg) |
168 | .collect::<Vec<_>>() |
169 | .join(", " ) |
170 | ), |
171 | _ => pat.to_token_stream().to_string().to_case(Case::Camel), |
172 | } |
173 | } |
174 | |
175 | impl NapiFn { |
176 | fn gen_ts_func_args(&self) -> String { |
177 | format!( |
178 | " {}" , |
179 | self |
180 | .args |
181 | .iter() |
182 | .filter_map(|arg| match &arg.kind { |
183 | crate::NapiFnArgKind::PatType(path) => { |
184 | let ty_string = path.ty.to_token_stream().to_string(); |
185 | if ty_string == "Env" { |
186 | return None; |
187 | } |
188 | if let syn::Type::Path(path) = path.ty.as_ref() { |
189 | if let Some(PathSegment { ident, arguments }) = path.path.segments.last() { |
190 | if ident == "Reference" || ident == "WeakReference" { |
191 | if let Some(parent) = &self.parent { |
192 | if let PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { |
193 | args: angle_bracketed_args, |
194 | .. |
195 | }) = arguments |
196 | { |
197 | if let Some(syn::GenericArgument::Type(syn::Type::Path(syn::TypePath { |
198 | path, |
199 | .. |
200 | }))) = angle_bracketed_args.first() |
201 | { |
202 | if let Some(segment) = path.segments.first() { |
203 | if *parent == segment.ident { |
204 | // If we have a Reference<A> in an impl A block, it shouldn't be an arg |
205 | return None; |
206 | } |
207 | } |
208 | } |
209 | } |
210 | } |
211 | } |
212 | if ident == "This" || ident == "this" { |
213 | if self.kind != FnKind::Normal { |
214 | return None; |
215 | } |
216 | if let PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { |
217 | args: angle_bracketed_args, |
218 | .. |
219 | }) = arguments |
220 | { |
221 | if let Some(syn::GenericArgument::Type(ty)) = angle_bracketed_args.first() { |
222 | let (ts_type, _) = ty_to_ts_type(ty, false, false, false); |
223 | return Some(FnArg { |
224 | arg: "this" .to_owned(), |
225 | ts_type, |
226 | is_optional: false, |
227 | }); |
228 | } |
229 | } else { |
230 | return Some(FnArg { |
231 | arg: "this" .to_owned(), |
232 | ts_type: "this" .to_owned(), |
233 | is_optional: false, |
234 | }); |
235 | } |
236 | return None; |
237 | } |
238 | } |
239 | } |
240 | |
241 | let mut path = path.clone(); |
242 | // remove mutability from PatIdent |
243 | if let Pat::Ident(i) = path.pat.as_mut() { |
244 | i.mutability = None; |
245 | } |
246 | |
247 | let (ts_type, is_optional) = ty_to_ts_type(&path.ty, false, false, false); |
248 | let ts_type = arg.use_overridden_type_or(|| ts_type); |
249 | let arg = gen_ts_func_arg(&path.pat); |
250 | Some(FnArg { |
251 | arg, |
252 | ts_type, |
253 | is_optional, |
254 | }) |
255 | } |
256 | crate::NapiFnArgKind::Callback(cb) => { |
257 | let ts_type = arg.use_overridden_type_or(|| gen_callback_type(cb)); |
258 | let arg = cb.pat.to_token_stream().to_string().to_case(Case::Camel); |
259 | |
260 | Some(FnArg { |
261 | arg, |
262 | ts_type, |
263 | is_optional: false, |
264 | }) |
265 | } |
266 | }) |
267 | .collect::<FnArgList>() |
268 | ) |
269 | } |
270 | |
271 | fn gen_ts_func_prefix(&self) -> &'static str { |
272 | if self.parent.is_some() { |
273 | match self.kind { |
274 | crate::FnKind::Normal => match self.fn_self { |
275 | Some(_) => "" , |
276 | None => "static" , |
277 | }, |
278 | crate::FnKind::Factory => "static" , |
279 | crate::FnKind::Constructor => "" , |
280 | crate::FnKind::Getter => "get" , |
281 | crate::FnKind::Setter => "set" , |
282 | } |
283 | } else if self.js_mod.is_some() { |
284 | "export function" |
285 | } else { |
286 | "export declare function" |
287 | } |
288 | } |
289 | |
290 | fn gen_ts_func_ret(&self) -> String { |
291 | match self.kind { |
292 | FnKind::Constructor | FnKind::Setter => "" .to_owned(), |
293 | FnKind::Factory => self |
294 | .parent |
295 | .clone() |
296 | .map(|i| { |
297 | let parent = i.to_string().to_case(Case::Pascal); |
298 | if self.is_async { |
299 | format!(": Promise< {}>" , parent) |
300 | } else { |
301 | format!(": {}" , parent) |
302 | } |
303 | }) |
304 | .unwrap_or_else(|| "" .to_owned()), |
305 | _ => { |
306 | let ret = if let Some(ret) = &self.ret { |
307 | let (ts_type, _) = ty_to_ts_type(ret, true, false, false); |
308 | if ts_type == "undefined" { |
309 | "void" .to_owned() |
310 | } else if ts_type == "Self" { |
311 | "this" .to_owned() |
312 | } else { |
313 | ts_type |
314 | } |
315 | } else { |
316 | "void" .to_owned() |
317 | }; |
318 | |
319 | if self.is_async { |
320 | format!(": Promise< {}>" , ret) |
321 | } else { |
322 | format!(": {}" , ret) |
323 | } |
324 | } |
325 | } |
326 | } |
327 | } |
328 | |