1use convert_case::{Case, Casing};
2use quote::ToTokens;
3use std::fmt::{Display, Formatter};
4use syn::{Member, Pat, PathArguments, PathSegment};
5
6use super::{ty_to_ts_type, ToTypeDef, TypeDef};
7use crate::{js_doc_from_comments, CallbackArg, FnKind, NapiFn};
8
9pub(crate) struct FnArg {
10 pub(crate) arg: String,
11 pub(crate) ts_type: String,
12 pub(crate) is_optional: bool,
13}
14
15pub(crate) struct FnArgList {
16 this: Option<FnArg>,
17 args: Vec<FnArg>,
18 last_required: Option<usize>,
19}
20
21impl 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
44impl 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
68impl 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
105fn 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
128fn 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
175impl 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