diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index cb7421dd..3c17d872 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -1,5 +1,5 @@ use proc_macro2::Ident; -use syn::{Attribute, Type}; +use syn::{Attribute, Expr, Type}; #[derive(Debug, Clone)] pub struct NapiFn { @@ -93,3 +93,11 @@ pub struct NapiEnumVariant { pub val: i32, pub comments: Vec, } + +#[derive(Debug, Clone)] +pub struct NapiConst { + pub name: Ident, + pub js_name: String, + pub type_name: Type, + pub value: Expr, +} diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 71657ee8..ccbddca8 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -2,6 +2,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use crate::BindgenResult; +mod r#const; mod r#enum; mod r#fn; mod r#struct; diff --git a/crates/backend/src/codegen/const.rs b/crates/backend/src/codegen/const.rs new file mode 100644 index 00000000..ae4d04d4 --- /dev/null +++ b/crates/backend/src/codegen/const.rs @@ -0,0 +1,41 @@ +use proc_macro2::{Literal, TokenStream}; +use quote::ToTokens; + +use crate::{codegen::get_register_ident, BindgenResult, NapiConst, TryToTokens}; + +impl TryToTokens for NapiConst { + fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { + let register = self.gen_module_register(); + (quote! { + #register + }) + .to_tokens(tokens); + + Ok(()) + } +} + +impl NapiConst { + fn gen_module_register(&self) -> TokenStream { + let name_str = self.name.to_string(); + let name_ident = self.name.clone(); + let js_name_lit = Literal::string(&self.js_name); + let register_name = get_register_ident(&name_str); + let type_name = &self.type_name; + quote! { + #[allow(non_snake_case)] + #[allow(clippy::all)] + #[napi::bindgen_prelude::ctor] + fn #register_name() { + use std::ffi::CString; + use std::ptr; + + unsafe fn cb(env: napi::sys::napi_env) -> napi::Result { + <#type_name as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, #name_ident) + } + + napi::bindgen_prelude::register_module_export(#js_name_lit, cb); + } + } + } +} diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index 918a8b4a..d9242d21 100644 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -53,4 +53,5 @@ napi_ast_impl! { (Struct, NapiStruct), (Impl, NapiImpl), (Enum, NapiEnum), + (Const, NapiConst), } diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index a5197acb..12ff33da 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -1,3 +1,4 @@ +mod r#const; mod r#enum; mod r#fn; pub(crate) mod r#struct; diff --git a/crates/backend/src/typegen/const.rs b/crates/backend/src/typegen/const.rs new file mode 100644 index 00000000..94fc81c6 --- /dev/null +++ b/crates/backend/src/typegen/const.rs @@ -0,0 +1,17 @@ +use super::{ToTypeDef, TypeDef}; + +use crate::{ty_to_ts_type, NapiConst}; + +impl ToTypeDef for NapiConst { + fn to_type_def(&self) -> TypeDef { + TypeDef { + kind: "const".to_owned(), + name: self.js_name.to_owned(), + def: format!( + "export const {}: {}", + &self.js_name, + ty_to_ts_type(&self.type_name, false).0 + ), + } + } +} diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index 7d6aa765..4f864247 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -9,8 +9,9 @@ use attrs::{BindgenAttr, BindgenAttrs}; use convert_case::{Case, Casing}; use napi_derive_backend::{ - BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiEnum, NapiEnumVariant, NapiFn, - NapiFnArgKind, NapiImpl, NapiItem, NapiStruct, NapiStructField, NapiStructKind, + BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiConst, NapiEnum, + NapiEnumVariant, NapiFn, NapiFnArgKind, NapiImpl, NapiItem, NapiStruct, NapiStructField, + NapiStructKind, }; use proc_macro2::{Ident, TokenStream, TokenTree}; use quote::ToTokens; @@ -296,7 +297,7 @@ fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec { }) } -// Unescapes a quoted string. char::escape_debug() was used to escape the text. +// Unescaped a quoted string. char::escape_debug() was used to escape the text. fn try_unescape(s: &str) -> Option { if s.is_empty() { return Some(String::new()); @@ -584,6 +585,7 @@ impl ParseNapi for syn::Item { syn::Item::Struct(s) => s.parse_napi(tokens, opts), syn::Item::Impl(i) => i.parse_napi(tokens, opts), syn::Item::Enum(e) => e.parse_napi(tokens, opts), + syn::Item::Const(c) => c.parse_napi(tokens, opts), _ => bail_span!( self, "#[napi] can only be applied to a function, struct, enum or impl." @@ -625,6 +627,14 @@ impl ParseNapi for syn::ItemEnum { } } +impl ParseNapi for syn::ItemConst { + fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { + let napi = self.convert_to_ast(opts); + self.to_tokens(tokens); + napi + } +} + fn fn_kind(opts: &BindgenAttrs) -> FnKind { let mut kind = FnKind::Normal; @@ -910,3 +920,22 @@ impl ConvertToAST for syn::ItemEnum { }) } } + +impl ConvertToAST for syn::ItemConst { + fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult { + match self.vis { + Visibility::Public(_) => Ok(Napi { + comments: vec![], + item: NapiItem::Const(NapiConst { + name: self.ident.clone(), + js_name: opts + .js_name() + .map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string()), + type_name: *self.ty.clone(), + value: *self.expr.clone(), + }), + }), + _ => bail_span!(self, "only public const allowed"), + } + } +} diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 785d9c9d..937c2973 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -8,7 +8,8 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 - `export function getWords(): Array␊ + `export const DEFAULT_COST: number␊ + export function getWords(): Array␊ export function getNums(): Array␊ export function sumNums(nums: Array): number␊ export function readFileAsync(path: string): Promise␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index fae58d3b..f58e243c 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index f5cb81fc..6dbf8e47 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -3,6 +3,7 @@ import { join } from 'path' import test from 'ava' import { + DEFAULT_COST, add, fibonacci, contains, @@ -48,6 +49,10 @@ import { createSymbol, } from '../' +test('export const', (t) => { + t.is(DEFAULT_COST, 12) +}) + test('number', (t) => { t.is(add(1, 2), 3) t.is(fibonacci(5), 5) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 4a78e624..396941f4 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -1,3 +1,4 @@ +export const DEFAULT_COST: number export function getWords(): Array export function getNums(): Array export function sumNums(nums: Array): number diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index bbd3e620..a06b7695 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -3,6 +3,9 @@ extern crate napi_derive; #[macro_use] extern crate serde_derive; +#[napi] +pub const DEFAULT_COST: u32 = 12; + mod array; mod r#async; mod bigint;