1#[macro_use]
2pub mod attrs;
3
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::str::Chars;
7use std::sync::atomic::AtomicUsize;
8
9use attrs::BindgenAttrs;
10
11use convert_case::{Case, Casing};
12use napi_derive_backend::{
13 rm_raw_prefix, BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiConst, NapiEnum,
14 NapiEnumValue, NapiEnumVariant, NapiFn, NapiFnArg, NapiFnArgKind, NapiImpl, NapiItem, NapiStruct,
15 NapiStructField, NapiStructKind,
16};
17use proc_macro2::{Ident, Span, TokenStream};
18use quote::ToTokens;
19use syn::ext::IdentExt;
20use syn::parse::{Parse, ParseStream, Result as SynResult};
21use syn::spanned::Spanned;
22use syn::{Attribute, ExprLit, Meta, PatType, PathSegment, Signature, Type, Visibility};
23
24use crate::parser::attrs::{check_recorded_struct_for_impl, record_struct};
25
26thread_local! {
27 static GENERATOR_STRUCT: RefCell<HashMap<String, bool>> = Default::default();
28}
29
30static REGISTER_INDEX: AtomicUsize = AtomicUsize::new(0);
31
32fn get_register_ident(name: &str) -> Ident {
33 let new_name: String = format!(
34 "__napi_register__{}_{}",
35 rm_raw_prefix(name),
36 REGISTER_INDEX.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
37 );
38 Ident::new(&new_name, Span::call_site())
39}
40
41struct AnyIdent(Ident);
42
43impl Parse for AnyIdent {
44 fn parse(input: ParseStream) -> SynResult<Self> {
45 input.step(|cursor: StepCursor<'_, '_>| match cursor.ident() {
46 Some((ident: Ident, remaining: Cursor<'_>)) => Ok((AnyIdent(ident), remaining)),
47 None => Err(cursor.error(message:"expected an identifier")),
48 })
49 }
50}
51
52pub trait ConvertToAST {
53 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi>;
54}
55
56pub trait ParseNapi {
57 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi>;
58}
59
60/// This function does a few things:
61/// - parses the tokens for the given argument `p` to find the `#[napi(ts_arg_type = "MyType")]`
62/// attribute and return the manually overridden type.
63/// - If both the `ts_args_type` override and the `ts_arg_type` override are present, bail
64/// since it should only allow one at a time.
65/// - Bails if it finds the `#[napi...]` attribute but it has the wrong data.
66/// - Removes the attribute from the output token stream so this
67/// `pub fn add(u: u32, #[napi(ts_arg_type = "MyType")] f: String)`
68/// turns into
69/// `pub fn add(u: u32, f: String)`
70/// otherwise it won't compile
71fn find_ts_arg_type_and_remove_attribute(
72 p: &mut PatType,
73 ts_args_type: Option<&(&str, Span)>,
74) -> BindgenResult<Option<String>> {
75 let mut ts_type_attr: Option<(usize, String)> = None;
76 for (idx, attr) in p.attrs.iter().enumerate() {
77 if attr.path().is_ident("napi") {
78 if let Some((ts_args_type, _)) = ts_args_type {
79 bail_span!(
80 attr,
81 "Found a 'ts_args_type'=\"{}\" override. Cannot use 'ts_arg_type' at the same time since they are mutually exclusive.",
82 ts_args_type
83 );
84 }
85
86 match &attr.meta {
87 syn::Meta::Path(_) | syn::Meta::NameValue(_) => {
88 bail_span!(
89 attr,
90 "Expects an assignment #[napi(ts_arg_type = \"MyType\")]"
91 )
92 }
93 syn::Meta::List(list) => {
94 let mut found = false;
95 list
96 .parse_args_with(|tokens: &syn::parse::ParseBuffer<'_>| {
97 // tokens:
98 // #[napi(xxx, xxx=xxx)]
99 // ^^^^^^^^^^^^
100 let list = tokens.parse_terminated(Meta::parse, Token![,])?;
101
102 for meta in list {
103 if meta.path().is_ident("ts_arg_type") {
104 match meta {
105 Meta::Path(_) | Meta::List(_) => {
106 return Err(syn::Error::new(
107 meta.path().span(),
108 "Expects an assignment (ts_arg_type = \"MyType\")",
109 ))
110 }
111 Meta::NameValue(name_value) => match name_value.value {
112 syn::Expr::Lit(syn::ExprLit {
113 lit: syn::Lit::Str(str),
114 ..
115 }) => {
116 let value = str.value();
117 found = true;
118 ts_type_attr = Some((idx, value));
119 }
120 _ => {
121 return Err(syn::Error::new(
122 name_value.value.span(),
123 "Expects a string literal",
124 ))
125 }
126 },
127 }
128 }
129 }
130
131 Ok(())
132 })
133 .map_err(Diagnostic::from)?;
134
135 if !found {
136 bail_span!(attr, "Expects a 'ts_arg_type'");
137 }
138 }
139 }
140 }
141 }
142
143 if let Some((idx, value)) = ts_type_attr {
144 p.attrs.remove(idx);
145 Ok(Some(value))
146 } else {
147 Ok(None)
148 }
149}
150
151fn find_enum_value_and_remove_attribute(v: &mut syn::Variant) -> BindgenResult<Option<String>> {
152 let mut name_attr: Option<(usize, String)> = None;
153 for (idx, attr) in v.attrs.iter().enumerate() {
154 if attr.path().is_ident("napi") {
155 match &attr.meta {
156 syn::Meta::Path(_) | syn::Meta::NameValue(_) => {
157 bail_span!(
158 attr,
159 "Expects an assignment #[napi(value = \"enum-variant-value\")]"
160 )
161 }
162 syn::Meta::List(list) => {
163 let mut found = false;
164 list
165 .parse_args_with(|tokens: &syn::parse::ParseBuffer<'_>| {
166 // tokens:
167 // #[napi(xxx, xxx=xxx)]
168 // ^^^^^^^^^^^^
169 let list = tokens.parse_terminated(Meta::parse, Token![,])?;
170
171 for meta in list {
172 if meta.path().is_ident("value") {
173 match meta {
174 Meta::Path(_) | Meta::List(_) => {
175 return Err(syn::Error::new(
176 meta.path().span(),
177 "Expects an assignment (value = \"enum-variant-value\")",
178 ))
179 }
180 Meta::NameValue(name_value) => match name_value.value {
181 syn::Expr::Lit(syn::ExprLit {
182 lit: syn::Lit::Str(str),
183 ..
184 }) => {
185 let value = str.value();
186 found = true;
187 name_attr = Some((idx, value));
188 }
189 _ => {
190 return Err(syn::Error::new(
191 name_value.value.span(),
192 "Expects a string literal",
193 ))
194 }
195 },
196 }
197 }
198 }
199
200 Ok(())
201 })
202 .map_err(Diagnostic::from)?;
203
204 if !found {
205 bail_span!(attr, "Expects a 'value'");
206 }
207 }
208 }
209 }
210 }
211
212 if let Some((idx, value)) = name_attr {
213 v.attrs.remove(idx);
214 Ok(Some(value))
215 } else {
216 Ok(None)
217 }
218}
219
220fn get_ty(mut ty: &syn::Type) -> &syn::Type {
221 while let syn::Type::Group(g: &TypeGroup) = ty {
222 ty = &g.elem;
223 }
224
225 ty
226}
227
228fn replace_self(ty: syn::Type, self_ty: Option<&Ident>) -> syn::Type {
229 let self_ty: &Ident = match self_ty {
230 Some(i: &Ident) => i,
231 None => return ty,
232 };
233 let path: Path = match get_ty(&ty) {
234 syn::Type::Path(syn::TypePath { qself: None, path: &Path }) => path.clone(),
235 other: &Type => return other.clone(),
236 };
237 let new_path: Path = if path.segments.len() == 1 && path.segments[0].ident == "Self" {
238 self_ty.clone().into()
239 } else {
240 path
241 };
242 syn::Type::Path(syn::TypePath {
243 qself: None,
244 path: new_path,
245 })
246}
247
248/// Extracts the last ident from the path
249fn extract_path_ident(path: &syn::Path) -> BindgenResult<Ident> {
250 for segment: &PathSegment in path.segments.iter() {
251 match segment.arguments {
252 syn::PathArguments::None => {}
253 _ => bail_span!(path, "paths with type parameters are not supported yet"),
254 }
255 }
256
257 match path.segments.last() {
258 Some(value: &PathSegment) => Ok(value.ident.clone()),
259 None => {
260 bail_span!(path, "empty idents are not supported");
261 }
262 }
263}
264
265fn extract_callback_trait_types(
266 arguments: &syn::PathArguments,
267) -> BindgenResult<(Vec<syn::Type>, Option<syn::Type>)> {
268 match arguments {
269 // <T: Fn>
270 syn::PathArguments::None => Ok((vec![], None)),
271 syn::PathArguments::AngleBracketed(_) => {
272 bail_span!(arguments, "use parentheses for napi callback trait")
273 }
274 syn::PathArguments::Parenthesized(arguments) => {
275 let args = arguments.inputs.iter().cloned().collect::<Vec<_>>();
276
277 let ret = match &arguments.output {
278 syn::ReturnType::Type(_, ret_ty) => {
279 let ret_ty = &**ret_ty;
280 if let Some(ty_of_result) = extract_result_ty(ret_ty)? {
281 if ty_of_result.to_token_stream().to_string() == "()" {
282 None
283 } else {
284 Some(ty_of_result)
285 }
286 } else {
287 bail_span!(ret_ty, "The return type of callback can only be `Result`");
288 }
289 }
290 _ => {
291 bail_span!(
292 arguments,
293 "The return type of callback can only be `Result`. Try with `Result<()>`"
294 );
295 }
296 };
297
298 Ok((args, ret))
299 }
300 }
301}
302
303fn extract_result_ty(ty: &syn::Type) -> BindgenResult<Option<syn::Type>> {
304 match ty {
305 syn::Type::Path(syn::TypePath { qself: None, path }) => {
306 let segment = path.segments.last().unwrap();
307 if segment.ident != "Result" {
308 Ok(None)
309 } else {
310 match &segment.arguments {
311 syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
312 args, ..
313 }) => {
314 let ok_arg = args.first().unwrap();
315 match ok_arg {
316 syn::GenericArgument::Type(ty) => Ok(Some(ty.clone())),
317 _ => bail_span!(ok_arg, "unsupported generic type"),
318 }
319 }
320 _ => {
321 bail_span!(segment, "unsupported generic type")
322 }
323 }
324 }
325 }
326 _ => Ok(None),
327 }
328}
329
330fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
331 while let syn::Expr::Group(g: &ExprGroup) = expr {
332 expr = &g.expr;
333 }
334
335 expr
336}
337
338/// Extract the documentation comments from a Vec of attributes
339fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
340 attrs
341 .iter()
342 .filter_map(|a| {
343 // if the path segments include an ident of "doc" we know this
344 // this is a doc comment
345 let name_value = a.meta.require_name_value();
346 if let Ok(name) = name_value {
347 if a.path().is_ident("doc") {
348 Some(
349 // We want to filter out any Puncts so just grab the Literals
350 match &name.value {
351 syn::Expr::Lit(ExprLit {
352 lit: syn::Lit::Str(str),
353 ..
354 }) => {
355 let quoted = str.token().to_string();
356 Some(try_unescape(&quoted).unwrap_or(quoted))
357 }
358 _ => None,
359 },
360 )
361 } else {
362 None
363 }
364 } else {
365 None
366 }
367 })
368 //Fold up the [[String]] iter we created into Vec<String>
369 .fold(vec![], |mut acc, a| {
370 acc.extend(a);
371 acc
372 })
373}
374
375// Unescaped a quoted string. char::escape_debug() was used to escape the text.
376fn try_unescape(s: &str) -> Option<String> {
377 if s.is_empty() {
378 return Some(String::new());
379 }
380 let mut result = String::with_capacity(s.len());
381 let mut chars = s.chars();
382 for i in 0.. {
383 let c = match chars.next() {
384 Some(c) => c,
385 None => {
386 if result.ends_with('"') {
387 result.pop();
388 }
389 return Some(result);
390 }
391 };
392 if i == 0 && c == '"' {
393 // ignore it
394 } else if c == '\\' {
395 let c = chars.next()?;
396 match c {
397 't' => result.push('\t'),
398 'r' => result.push('\r'),
399 'n' => result.push('\n'),
400 '\\' | '\'' | '"' => result.push(c),
401 'u' => {
402 if chars.next() != Some('{') {
403 return None;
404 }
405 let (c, next) = unescape_unicode(&mut chars)?;
406 result.push(c);
407 if next != '}' {
408 return None;
409 }
410 }
411 _ => return None,
412 }
413 } else {
414 result.push(c);
415 }
416 }
417 None
418}
419
420fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
421 let mut value = 0;
422 for i in 0..7 {
423 let c = chars.next()?;
424 let num = match c {
425 '0'..='9' => c as u32 - '0' as u32,
426 'a'..='f' => c as u32 - 'a' as u32,
427 'A'..='F' => c as u32 - 'A' as u32,
428 _ => {
429 if i == 0 {
430 return None;
431 }
432
433 if i == 0 {
434 return None;
435 }
436 let decoded = char::from_u32(value)?;
437 return Some((decoded, c));
438 }
439 };
440
441 if i >= 6 {
442 return None;
443 }
444 value = (value << 4) | num;
445 }
446 None
447}
448
449fn extract_fn_closure_generics(
450 generics: &syn::Generics,
451) -> BindgenResult<HashMap<String, syn::PathArguments>> {
452 let mut errors = vec![];
453
454 let mut map = HashMap::default();
455 if generics.params.is_empty() {
456 return Ok(map);
457 }
458
459 if let Some(where_clause) = &generics.where_clause {
460 for prediction in where_clause.predicates.iter() {
461 match prediction {
462 syn::WherePredicate::Type(syn::PredicateType {
463 bounded_ty, bounds, ..
464 }) => {
465 for bound in bounds {
466 match bound {
467 syn::TypeParamBound::Trait(t) => {
468 for segment in t.path.segments.iter() {
469 match segment.ident.to_string().as_str() {
470 "Fn" | "FnOnce" | "FnMut" => {
471 map.insert(
472 bounded_ty.to_token_stream().to_string(),
473 segment.arguments.clone(),
474 );
475 }
476 _ => {}
477 };
478 }
479 }
480 syn::TypeParamBound::Lifetime(lifetime) => {
481 if lifetime.ident != "static" {
482 errors.push(err_span!(
483 bound,
484 "only 'static is supported in lifetime bound for fn arguments"
485 ));
486 }
487 }
488 _ => errors.push(err_span! {
489 bound,
490 "unsupported bound in napi"
491 }),
492 }
493 }
494 }
495 _ => errors.push(err_span! {
496 prediction,
497 "unsupported where clause prediction in napi"
498 }),
499 };
500 }
501 }
502
503 for param in generics.params.iter() {
504 match param {
505 syn::GenericParam::Type(syn::TypeParam { ident, bounds, .. }) => {
506 for bound in bounds {
507 match bound {
508 syn::TypeParamBound::Trait(t) => {
509 for segment in t.path.segments.iter() {
510 match segment.ident.to_string().as_str() {
511 "Fn" | "FnOnce" | "FnMut" => {
512 map.insert(ident.to_string(), segment.arguments.clone());
513 }
514 _ => {}
515 };
516 }
517 }
518 syn::TypeParamBound::Lifetime(lifetime) => {
519 if lifetime.ident != "static" {
520 errors.push(err_span!(
521 bound,
522 "only 'static is supported in lifetime bound for fn arguments"
523 ));
524 }
525 }
526 _ => errors.push(err_span! {
527 bound,
528 "unsupported bound in napi"
529 }),
530 }
531 }
532 }
533 _ => {
534 errors.push(err_span!(param, "unsupported napi generic param for fn"));
535 }
536 }
537 }
538
539 Diagnostic::from_vec(errors).and(Ok(map))
540}
541
542fn napi_fn_from_decl(
543 sig: &mut Signature,
544 opts: &BindgenAttrs,
545 attrs: Vec<Attribute>,
546 vis: Visibility,
547 parent: Option<&Ident>,
548) -> BindgenResult<NapiFn> {
549 let mut errors = vec![];
550
551 let syn::Signature {
552 ident,
553 asyncness,
554 output,
555 generics,
556 ..
557 } = sig.clone();
558
559 let mut fn_self = None;
560 let callback_traits = extract_fn_closure_generics(&generics)?;
561
562 let args = sig
563 .inputs
564 .iter_mut()
565 .filter_map(|arg| match arg {
566 syn::FnArg::Typed(ref mut p) => {
567 let ts_arg_type = find_ts_arg_type_and_remove_attribute(p, opts.ts_args_type().as_ref())
568 .unwrap_or_else(|e| {
569 errors.push(e);
570 None
571 });
572
573 let ty_str = p.ty.to_token_stream().to_string();
574 if let Some(path_arguments) = callback_traits.get(&ty_str) {
575 match extract_callback_trait_types(path_arguments) {
576 Ok((fn_args, fn_ret)) => Some(NapiFnArg {
577 kind: NapiFnArgKind::Callback(Box::new(CallbackArg {
578 pat: p.pat.clone(),
579 args: fn_args,
580 ret: fn_ret,
581 })),
582 ts_arg_type,
583 }),
584 Err(e) => {
585 errors.push(e);
586 None
587 }
588 }
589 } else {
590 let ty = replace_self(p.ty.as_ref().clone(), parent);
591 p.ty = Box::new(ty);
592 Some(NapiFnArg {
593 kind: NapiFnArgKind::PatType(Box::new(p.clone())),
594 ts_arg_type,
595 })
596 }
597 }
598 syn::FnArg::Receiver(r) => {
599 if parent.is_some() {
600 assert!(fn_self.is_none());
601 if r.reference.is_none() {
602 errors.push(err_span!(
603 r,
604 "The native methods can't move values from napi. Try `&self` or `&mut self` instead."
605 ));
606 } else if r.mutability.is_some() {
607 fn_self = Some(FnSelf::MutRef);
608 } else {
609 fn_self = Some(FnSelf::Ref);
610 }
611 } else {
612 errors.push(err_span!(r, "arguments cannot be `self`"));
613 }
614 None
615 }
616 })
617 .collect::<Vec<_>>();
618
619 let (ret, is_ret_result) = match output {
620 syn::ReturnType::Default => (None, false),
621 syn::ReturnType::Type(_, ty) => {
622 let result_ty = extract_result_ty(&ty)?;
623 if let Some(result_ty) = result_ty {
624 (Some(replace_self(result_ty, parent)), true)
625 } else {
626 (Some(replace_self(*ty, parent)), false)
627 }
628 }
629 };
630
631 Diagnostic::from_vec(errors).and_then(|_| {
632 let js_name = if let Some(prop_name) = opts.getter() {
633 opts.js_name().map_or_else(
634 || {
635 if let Some(ident) = prop_name {
636 ident.to_string()
637 } else {
638 ident
639 .to_string()
640 .trim_start_matches("get_")
641 .to_case(Case::Camel)
642 }
643 },
644 |(js_name, _)| js_name.to_owned(),
645 )
646 } else if let Some(prop_name) = opts.setter() {
647 opts.js_name().map_or_else(
648 || {
649 if let Some(ident) = prop_name {
650 ident.to_string()
651 } else {
652 ident
653 .to_string()
654 .trim_start_matches("set_")
655 .to_case(Case::Camel)
656 }
657 },
658 |(js_name, _)| js_name.to_owned(),
659 )
660 } else if opts.constructor().is_some() {
661 "constructor".to_owned()
662 } else {
663 opts.js_name().map_or_else(
664 || ident.to_string().to_case(Case::Camel),
665 |(js_name, _)| js_name.to_owned(),
666 )
667 };
668
669 let namespace = opts.namespace().map(|(m, _)| m.to_owned());
670 let parent_is_generator = if let Some(p) = parent {
671 GENERATOR_STRUCT.with(|inner| {
672 let inner = inner.borrow();
673 let key = namespace
674 .as_ref()
675 .map(|n| format!("{}::{}", n, p))
676 .unwrap_or_else(|| p.to_string());
677 *inner.get(&key).unwrap_or(&false)
678 })
679 } else {
680 false
681 };
682
683 let kind = fn_kind(opts);
684
685 if !matches!(kind, FnKind::Normal) && parent.is_none() {
686 bail_span!(
687 sig.ident,
688 "Only fn in impl block can be marked as factory, constructor, getter or setter"
689 );
690 }
691
692 if matches!(kind, FnKind::Constructor) && asyncness.is_some() {
693 bail_span!(sig.ident, "Constructor don't support asynchronous function");
694 }
695
696 Ok(NapiFn {
697 name: ident.clone(),
698 js_name,
699 args,
700 ret,
701 is_ret_result,
702 is_async: asyncness.is_some(),
703 vis,
704 kind,
705 fn_self,
706 parent: parent.cloned(),
707 comments: extract_doc_comments(&attrs),
708 attrs,
709 strict: opts.strict().is_some(),
710 return_if_invalid: opts.return_if_invalid().is_some(),
711 js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
712 ts_generic_types: opts.ts_generic_types().map(|(m, _)| m.to_owned()),
713 ts_args_type: opts.ts_args_type().map(|(m, _)| m.to_owned()),
714 ts_return_type: opts.ts_return_type().map(|(m, _)| m.to_owned()),
715 skip_typescript: opts.skip_typescript().is_some(),
716 parent_is_generator,
717 writable: opts.writable(),
718 enumerable: opts.enumerable(),
719 configurable: opts.configurable(),
720 catch_unwind: opts.catch_unwind().is_some(),
721 unsafe_: sig.unsafety.is_some(),
722 register_name: get_register_ident(ident.to_string().as_str()),
723 })
724 })
725}
726
727impl ParseNapi for syn::Item {
728 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
729 match self {
730 syn::Item::Fn(f: &mut ItemFn) => f.parse_napi(tokens, opts),
731 syn::Item::Struct(s: &mut ItemStruct) => s.parse_napi(tokens, opts),
732 syn::Item::Impl(i: &mut ItemImpl) => i.parse_napi(tokens, opts),
733 syn::Item::Enum(e: &mut ItemEnum) => e.parse_napi(tokens, opts),
734 syn::Item::Const(c: &mut ItemConst) => c.parse_napi(tokens, opts),
735 _ => bail_span!(
736 self,
737 "#[napi] can only be applied to a function, struct, enum, const, mod or impl."
738 ),
739 }
740 }
741}
742
743impl ParseNapi for syn::ItemFn {
744 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
745 if opts.ts_type().is_some() {
746 bail_span!(
747 self,
748 "#[napi] can't be applied to a function with #[napi(ts_type)]"
749 );
750 }
751 if opts.return_if_invalid().is_some() && opts.strict().is_some() {
752 bail_span!(
753 self,
754 "#[napi(return_if_invalid)] can't be used with #[napi(strict)]"
755 );
756 }
757 let napi: Result = self.convert_to_ast(opts);
758 self.to_tokens(tokens);
759
760 napi
761 }
762}
763impl ParseNapi for syn::ItemStruct {
764 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
765 if opts.ts_args_type().is_some()
766 || opts.ts_return_type().is_some()
767 || opts.skip_typescript().is_some()
768 || opts.ts_type().is_some()
769 {
770 bail_span!(
771 self,
772 "#[napi] can't be applied to a struct with #[napi(ts_args_type)], #[napi(ts_return_type)], #[napi(skip_typescript)] or #[napi(ts_type)]"
773 );
774 }
775 if opts.return_if_invalid().is_some() {
776 bail_span!(
777 self,
778 "#[napi(return_if_invalid)] can only be applied to a function or method."
779 );
780 }
781 if opts.catch_unwind().is_some() {
782 bail_span!(
783 self,
784 "#[napi(catch_unwind)] can only be applied to a function or method."
785 );
786 }
787 if opts.object().is_some() && opts.custom_finalize().is_some() {
788 bail_span!(self, "Custom finalize is not supported for #[napi(object)]");
789 }
790 let napi = self.convert_to_ast(opts);
791 self.to_tokens(tokens);
792
793 napi
794 }
795}
796
797impl ParseNapi for syn::ItemImpl {
798 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
799 if opts.ts_args_type().is_some()
800 || opts.ts_return_type().is_some()
801 || opts.skip_typescript().is_some()
802 || opts.ts_type().is_some()
803 || opts.custom_finalize().is_some()
804 {
805 bail_span!(
806 self,
807 "#[napi] can't be applied to impl with #[napi(ts_args_type)], #[napi(ts_return_type)], #[napi(skip_typescript)] or #[napi(ts_type)] or #[napi(custom_finalize)]"
808 );
809 }
810 if opts.return_if_invalid().is_some() {
811 bail_span!(
812 self,
813 "#[napi(return_if_invalid)] can only be applied to a function or method."
814 );
815 }
816 if opts.catch_unwind().is_some() {
817 bail_span!(
818 self,
819 "#[napi(catch_unwind)] can only be applied to a function or method."
820 );
821 }
822 // #[napi] macro will be remove from impl items after converted to ast
823 let napi = self.convert_to_ast(opts);
824 self.to_tokens(tokens);
825
826 napi
827 }
828}
829
830impl ParseNapi for syn::ItemEnum {
831 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
832 if opts.ts_args_type().is_some()
833 || opts.ts_return_type().is_some()
834 || opts.ts_type().is_some()
835 || opts.custom_finalize().is_some()
836 {
837 bail_span!(
838 self,
839 "#[napi] can't be applied to a enum with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)] or #[napi(custom_finalize)]"
840 );
841 }
842 if opts.return_if_invalid().is_some() {
843 bail_span!(
844 self,
845 "#[napi(return_if_invalid)] can only be applied to a function or method."
846 );
847 }
848 if opts.catch_unwind().is_some() {
849 bail_span!(
850 self,
851 "#[napi(catch_unwind)] can only be applied to a function or method."
852 );
853 }
854 let napi = self.convert_to_ast(opts);
855 self.to_tokens(tokens);
856
857 napi
858 }
859}
860impl ParseNapi for syn::ItemConst {
861 fn parse_napi(&mut self, tokens: &mut TokenStream, opts: &BindgenAttrs) -> BindgenResult<Napi> {
862 if opts.ts_args_type().is_some()
863 || opts.ts_return_type().is_some()
864 || opts.ts_type().is_some()
865 || opts.custom_finalize().is_some()
866 {
867 bail_span!(
868 self,
869 "#[napi] can't be applied to a const with #[napi(ts_args_type)], #[napi(ts_return_type)] or #[napi(ts_type)] or #[napi(custom_finalize)]"
870 );
871 }
872 if opts.return_if_invalid().is_some() {
873 bail_span!(
874 self,
875 "#[napi(return_if_invalid)] can only be applied to a function or method."
876 );
877 }
878 if opts.catch_unwind().is_some() {
879 bail_span!(
880 self,
881 "#[napi(catch_unwind)] can only be applied to a function or method."
882 );
883 }
884 let napi = self.convert_to_ast(opts);
885 self.to_tokens(tokens);
886 napi
887 }
888}
889
890fn fn_kind(opts: &BindgenAttrs) -> FnKind {
891 let mut kind: FnKind = FnKind::Normal;
892
893 if opts.getter().is_some() {
894 kind = FnKind::Getter;
895 }
896
897 if opts.setter().is_some() {
898 kind = FnKind::Setter;
899 }
900
901 if opts.constructor().is_some() {
902 kind = FnKind::Constructor;
903 }
904
905 if opts.factory().is_some() {
906 kind = FnKind::Factory;
907 }
908
909 kind
910}
911
912impl ConvertToAST for syn::ItemFn {
913 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi> {
914 let func: NapiFn = napi_fn_from_decl(
915 &mut self.sig,
916 opts,
917 self.attrs.clone(),
918 self.vis.clone(),
919 parent:None,
920 )?;
921
922 Ok(Napi {
923 item: NapiItem::Fn(func),
924 })
925 }
926}
927
928impl ConvertToAST for syn::ItemStruct {
929 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi> {
930 let mut errors = vec![];
931
932 let vis = self.vis.clone();
933 let struct_name = self.ident.clone();
934 let js_name = opts.js_name().map_or_else(
935 || self.ident.to_string().to_case(Case::Pascal),
936 |(js_name, _)| js_name.to_owned(),
937 );
938 let mut fields = vec![];
939 let mut is_tuple = false;
940 let struct_kind = if opts.constructor().is_some() {
941 NapiStructKind::Constructor
942 } else if opts.object().is_some() {
943 NapiStructKind::Object
944 } else {
945 NapiStructKind::None
946 };
947 let use_nullable = opts.use_nullable();
948
949 for (i, field) in self.fields.iter_mut().enumerate() {
950 match field.vis {
951 syn::Visibility::Public(..) => {}
952 _ => {
953 if struct_kind != NapiStructKind::None {
954 errors.push(err_span!(
955 field,
956 "#[napi] requires all struct fields to be public to mark struct as constructor or object shape\nthis field is not public."
957 ));
958 }
959 continue;
960 }
961 }
962
963 let field_opts = BindgenAttrs::find(&mut field.attrs)?;
964
965 let (js_name, name) = match &field.ident {
966 Some(ident) => (
967 field_opts.js_name().map_or_else(
968 || ident.unraw().to_string().to_case(Case::Camel),
969 |(js_name, _)| js_name.to_owned(),
970 ),
971 syn::Member::Named(ident.clone()),
972 ),
973 None => {
974 is_tuple = true;
975 (format!("field{}", i), syn::Member::Unnamed(i.into()))
976 }
977 };
978
979 let ignored = field_opts.skip().is_some();
980 let readonly = field_opts.readonly().is_some();
981 let writable = field_opts.writable();
982 let enumerable = field_opts.enumerable();
983 let configurable = field_opts.configurable();
984 let skip_typescript = field_opts.skip_typescript().is_some();
985 let ts_type = field_opts.ts_type().map(|e| e.0.to_string());
986
987 fields.push(NapiStructField {
988 name,
989 js_name,
990 ty: field.ty.clone(),
991 getter: !ignored,
992 setter: !(ignored || readonly),
993 writable,
994 enumerable,
995 configurable,
996 comments: extract_doc_comments(&field.attrs),
997 skip_typescript,
998 ts_type,
999 })
1000 }
1001
1002 record_struct(&struct_name, js_name.clone(), opts);
1003 let namespace = opts.namespace().map(|(m, _)| m.to_owned());
1004 let implement_iterator = opts.iterator().is_some();
1005 GENERATOR_STRUCT.with(|inner| {
1006 let mut inner = inner.borrow_mut();
1007 let key = namespace
1008 .as_ref()
1009 .map(|n| format!("{}::{}", n, struct_name))
1010 .unwrap_or_else(|| struct_name.to_string());
1011 inner.insert(key, implement_iterator);
1012 });
1013
1014 Diagnostic::from_vec(errors).map(|()| Napi {
1015 item: NapiItem::Struct(NapiStruct {
1016 js_name,
1017 name: struct_name.clone(),
1018 vis,
1019 fields,
1020 is_tuple,
1021 kind: struct_kind,
1022 object_from_js: opts.object_from_js(),
1023 object_to_js: opts.object_to_js(),
1024 js_mod: namespace,
1025 comments: extract_doc_comments(&self.attrs),
1026 implement_iterator,
1027 use_custom_finalize: opts.custom_finalize().is_some(),
1028 register_name: get_register_ident(format!("{struct_name}_struct").as_str()),
1029 use_nullable,
1030 }),
1031 })
1032 }
1033}
1034
1035impl ConvertToAST for syn::ItemImpl {
1036 fn convert_to_ast(&mut self, impl_opts: &BindgenAttrs) -> BindgenResult<Napi> {
1037 let struct_name = match get_ty(&self.self_ty) {
1038 syn::Type::Path(syn::TypePath {
1039 ref path,
1040 qself: None,
1041 }) => path,
1042 _ => {
1043 bail_span!(self.self_ty, "unsupported self type in #[napi] impl")
1044 }
1045 };
1046
1047 let struct_name = extract_path_ident(struct_name)?;
1048
1049 let mut struct_js_name = struct_name.to_string().to_case(Case::UpperCamel);
1050 let mut items = vec![];
1051 let mut task_output_type = None;
1052 let mut iterator_yield_type = None;
1053 let mut iterator_next_type = None;
1054 let mut iterator_return_type = None;
1055 for item in self.items.iter_mut() {
1056 if let Some(method) = match item {
1057 syn::ImplItem::Fn(m) => Some(m),
1058 syn::ImplItem::Type(m) => {
1059 if let Some((_, t, _)) = &self.trait_ {
1060 if let Some(PathSegment { ident, .. }) = t.segments.last() {
1061 if ident == "Task" && m.ident == "JsValue" {
1062 task_output_type = Some(m.ty.clone());
1063 } else if ident == "Generator" {
1064 if let Type::Path(_) = &m.ty {
1065 if m.ident == "Yield" {
1066 iterator_yield_type = Some(m.ty.clone());
1067 } else if m.ident == "Next" {
1068 iterator_next_type = Some(m.ty.clone());
1069 } else if m.ident == "Return" {
1070 iterator_return_type = Some(m.ty.clone());
1071 }
1072 }
1073 }
1074 }
1075 }
1076 None
1077 }
1078 _ => {
1079 bail_span!(item, "unsupported impl item in #[napi]")
1080 }
1081 } {
1082 let opts = BindgenAttrs::find(&mut method.attrs)?;
1083
1084 // it'd better only care methods decorated with `#[napi]` attribute
1085 if !opts.exists {
1086 continue;
1087 }
1088
1089 if opts.constructor().is_some() || opts.factory().is_some() {
1090 struct_js_name = check_recorded_struct_for_impl(&struct_name, &opts)?;
1091 }
1092
1093 let vis = method.vis.clone();
1094
1095 match &vis {
1096 Visibility::Public(_) => {}
1097 _ => {
1098 bail_span!(method.sig.ident, "only pub method supported by #[napi].",);
1099 }
1100 }
1101
1102 let func = napi_fn_from_decl(
1103 &mut method.sig,
1104 &opts,
1105 method.attrs.clone(),
1106 vis,
1107 Some(&struct_name),
1108 )?;
1109
1110 items.push(func);
1111 }
1112 }
1113
1114 let namespace = impl_opts.namespace().map(|(m, _)| m.to_owned());
1115
1116 Ok(Napi {
1117 item: NapiItem::Impl(NapiImpl {
1118 name: struct_name.clone(),
1119 js_name: struct_js_name,
1120 items,
1121 task_output_type,
1122 iterator_yield_type,
1123 iterator_next_type,
1124 iterator_return_type,
1125 js_mod: namespace,
1126 comments: extract_doc_comments(&self.attrs),
1127 register_name: get_register_ident(format!("{struct_name}_impl").as_str()),
1128 }),
1129 })
1130 }
1131}
1132
1133impl ConvertToAST for syn::ItemEnum {
1134 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi> {
1135 match self.vis {
1136 Visibility::Public(_) => {}
1137 _ => bail_span!(self, "only public enum allowed"),
1138 }
1139
1140 self.attrs.push(parse_quote!(#[derive(Copy, Clone)]));
1141
1142 let js_name = opts
1143 .js_name()
1144 .map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string());
1145
1146 let variants = match opts.string_enum() {
1147 Some(case) => {
1148 let case = case.map(|c| Ok::<Case, Diagnostic>(match c.0.as_str() {
1149 "lowercase" => Case::Flat,
1150 "UPPERCASE" => Case::UpperFlat,
1151 "PascalCase" => Case::Pascal,
1152 "camelCase" => Case::Camel,
1153 "snake_case" => Case::Snake,
1154 "SCREAMING_SNAKE_CASE" => Case::UpperSnake,
1155 "kebab-case" => Case::Kebab,
1156 "SCREAMING-KEBAB-CASE" => Case::UpperKebab,
1157 _ => {
1158 bail_span!(self, "Unknown string enum case. Possible values are \"lowercase\", \"UPPERCASE\", \"PascalCase\", \"camelCase\", \"snake_case\", \"SCREAMING_SNAKE_CASE\", \"kebab-case\", or \"SCREAMING-KEBAB-CASE\"")
1159 }
1160 })).transpose()?;
1161
1162 self
1163 .variants
1164 .iter_mut()
1165 .map(|v| {
1166 if !matches!(v.fields, syn::Fields::Unit) {
1167 bail_span!(v.fields, "Structured enum is not supported in #[napi]")
1168 }
1169 if matches!(&v.discriminant, Some((_, _))) {
1170 bail_span!(
1171 v.fields,
1172 "Literal values are not supported with string enum in #[napi]"
1173 )
1174 }
1175
1176 let val = find_enum_value_and_remove_attribute(v)?.unwrap_or_else(|| {
1177 let mut val = v.ident.to_string();
1178 if let Some(case) = case {
1179 val = val.to_case(case)
1180 }
1181 val
1182 });
1183
1184 Ok(NapiEnumVariant {
1185 name: v.ident.clone(),
1186 val: NapiEnumValue::String(val),
1187 comments: extract_doc_comments(&v.attrs),
1188 })
1189 })
1190 .collect::<BindgenResult<Vec<NapiEnumVariant>>>()?
1191 }
1192 None => {
1193 let mut last_variant_val: i32 = -1;
1194
1195 self
1196 .variants
1197 .iter()
1198 .map(|v| {
1199 if !matches!(v.fields, syn::Fields::Unit) {
1200 bail_span!(v.fields, "Structured enum is not supported in #[napi]")
1201 }
1202
1203 let val = match &v.discriminant {
1204 Some((_, expr)) => {
1205 let mut symbol = 1;
1206 let mut inner_expr = get_expr(expr);
1207 if let syn::Expr::Unary(syn::ExprUnary {
1208 attrs: _,
1209 op: syn::UnOp::Neg(_),
1210 expr,
1211 }) = inner_expr
1212 {
1213 symbol = -1;
1214 inner_expr = expr;
1215 }
1216
1217 match inner_expr {
1218 syn::Expr::Lit(syn::ExprLit {
1219 attrs: _,
1220 lit: syn::Lit::Int(int_lit),
1221 }) => match int_lit.base10_digits().parse::<i32>() {
1222 Ok(v) => symbol * v,
1223 Err(_) => {
1224 bail_span!(
1225 int_lit,
1226 "enums with #[wasm_bindgen] can only support \
1227 numbers that can be represented as i32",
1228 );
1229 }
1230 },
1231 _ => bail_span!(
1232 expr,
1233 "enums with #[wasm_bindgen] may only have \
1234 number literal values",
1235 ),
1236 }
1237 }
1238 None => last_variant_val + 1,
1239 };
1240
1241 last_variant_val = val;
1242
1243 Ok(NapiEnumVariant {
1244 name: v.ident.clone(),
1245 val: NapiEnumValue::Number(val),
1246 comments: extract_doc_comments(&v.attrs),
1247 })
1248 })
1249 .collect::<BindgenResult<Vec<NapiEnumVariant>>>()?
1250 }
1251 };
1252
1253 Ok(Napi {
1254 item: NapiItem::Enum(NapiEnum {
1255 name: self.ident.clone(),
1256 js_name,
1257 variants,
1258 js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
1259 comments: extract_doc_comments(&self.attrs),
1260 skip_typescript: opts.skip_typescript().is_some(),
1261 register_name: get_register_ident(self.ident.to_string().as_str()),
1262 }),
1263 })
1264 }
1265}
1266
1267impl ConvertToAST for syn::ItemConst {
1268 fn convert_to_ast(&mut self, opts: &BindgenAttrs) -> BindgenResult<Napi> {
1269 match self.vis {
1270 Visibility::Public(_) => Ok(Napi {
1271 item: NapiItem::Const(NapiConst {
1272 name: self.ident.clone(),
1273 js_name: opts
1274 .js_name()
1275 .map_or_else(|| self.ident.to_string(), |(s: &str, _)| s.to_string()),
1276 type_name: *self.ty.clone(),
1277 value: *self.expr.clone(),
1278 js_mod: opts.namespace().map(|(m: &str, _)| m.to_owned()),
1279 comments: extract_doc_comments(&self.attrs),
1280 skip_typescript: opts.skip_typescript().is_some(),
1281 register_name: get_register_ident(self.ident.to_string().as_str()),
1282 }),
1283 }),
1284 _ => bail_span!(self, "only public const allowed"),
1285 }
1286 }
1287}
1288