diff --git a/cli/src/build.ts b/cli/src/build.ts index 0638968b..e1732f40 100644 --- a/cli/src/build.ts +++ b/cli/src/build.ts @@ -255,20 +255,25 @@ async function processIntermediateTypeFile(source: string, target: string) { } }) - for (const [name, def] of impls.entries()) { - const classDef = classes.get(name) + for (const [name, classDef] of classes.entries()) { + const implDef = impls.get(name) dts += `export class ${name} { - ${(classDef ?? '') + ${classDef .split('\n') .map((line) => line.trim()) - .join('\n ')} - ${def - .split('\n') - .map((line) => line.trim()) - .join('\n ')} -} -` + .join('\n ')}` + + if (implDef) { + dts += + '\n ' + + implDef + .split('\n') + .map((line) => line.trim()) + .join('\n ') + } + + dts += '\n}\n' } await unlinkAsync(source) diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 2ee8b960..7dd173f0 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -35,6 +35,7 @@ pub static TYPE_REGEXES: Lazy> = Lazy::new(|| { ("Vec", Regex::new(r"^Vec < (.*) >$").unwrap()), ("Option", Regex::new(r"^Option < (.*) >").unwrap()), ("Result", Regex::new(r"^Result < (.*) >").unwrap()), + ("HashMap", Regex::new(r"HashMap < (.*), (.*) >").unwrap()), ]); map @@ -88,12 +89,24 @@ pub fn str_to_ts_type(ty: &str, omit_top_level_result: bool) -> String { s if s.starts_with("Result") && TYPE_REGEXES["Result"].is_match(s) => { let captures = TYPE_REGEXES["Result"].captures(s).unwrap(); let inner = captures.get(1).unwrap().as_str(); + if omit_top_level_result { str_to_ts_type(inner, false) } else { format!("Error | {}", str_to_ts_type(inner, false)) } } + s if TYPE_REGEXES["HashMap"].is_match(s) => { + let captures = TYPE_REGEXES["HashMap"].captures(s).unwrap(); + let key = captures.get(1).unwrap().as_str(); + let val = captures.get(2).unwrap().as_str(); + + format!( + "Record<{}, {}>", + str_to_ts_type(key, false), + str_to_ts_type(val, false) + ) + } s => s.to_owned(), } } diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index e12fee88..712a5a29 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -685,9 +685,10 @@ impl ConvertToAST for syn::ItemStruct { let (js_name, name) = match &field.ident { Some(ident) => ( - field_opts - .js_name() - .map_or_else(|| ident.to_string(), |(js_name, _)| js_name.to_owned()), + field_opts.js_name().map_or_else( + || ident.to_string().to_case(Case::Camel), + |(js_name, _)| js_name.to_owned(), + ), syn::Member::Named(ident.clone()), ), None => { diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index 8946ad54..ee7479db 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -4,6 +4,7 @@ use std::ptr; mod array; mod boolean; mod buffer; +mod map; mod nil; mod number; mod object; diff --git a/crates/napi/src/bindgen_runtime/js_values/map.rs b/crates/napi/src/bindgen_runtime/js_values/map.rs new file mode 100644 index 00000000..ddbd33e0 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/map.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; +use std::hash::Hash; + +use crate::bindgen_prelude::{Env, Result, ToNapiValue, *}; + +impl TypeName for HashMap { + fn type_name() -> &'static str { + "HashMap" + } +} + +impl ToNapiValue for HashMap +where + K: AsRef, + V: ToNapiValue, +{ + unsafe fn to_napi_value(raw_env: sys::napi_env, val: Self) -> Result { + let env = Env::from(raw_env); + let mut obj = env.create_object()?; + for (k, v) in val.into_iter() { + obj.set(k.as_ref(), v)?; + } + + Object::to_napi_value(raw_env, obj) + } +} + +impl FromNapiValue for HashMap +where + K: From + Eq + Hash, + V: FromNapiValue, +{ + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let obj = Object::from_napi_value(env, napi_val)?; + let mut map = HashMap::new(); + for key in Object::keys(&obj)?.into_iter() { + if let Some(val) = obj.get(&key)? { + map.insert(K::from(key), val); + } + } + + Ok(map) + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/object.rs b/crates/napi/src/bindgen_runtime/js_values/object.rs index 768ce591..90f62fb6 100644 --- a/crates/napi/src/bindgen_runtime/js_values/object.rs +++ b/crates/napi/src/bindgen_runtime/js_values/object.rs @@ -19,8 +19,8 @@ impl Object { Ok(Object { env, inner: ptr }) } - pub fn get(&self, field: String) -> Result> { - let c_field = CString::new(field)?; + pub fn get, V: FromNapiValue>(&self, field: K) -> Result> { + let c_field = CString::new(field.as_ref())?; unsafe { let mut ret = ptr::null_mut(); @@ -36,16 +36,16 @@ impl Object { Ok(if ty == ValueType::Undefined { None } else { - Some(T::from_napi_value(self.env, ret)?) + Some(V::from_napi_value(self.env, ret)?) }) } } - pub fn set(&mut self, field: String, val: T) -> Result<()> { - let c_field = CString::new(field)?; + pub fn set, V: ToNapiValue>(&mut self, field: K, val: V) -> Result<()> { + let c_field = CString::new(field.as_ref())?; unsafe { - let napi_val = T::to_napi_value(self.env, val)?; + let napi_val = V::to_napi_value(self.env, val)?; check_status!( sys::napi_set_named_property(self.env, self.inner, c_field.as_ptr(), napi_val), diff --git a/examples/napi/Cargo.toml b/examples/napi/Cargo.toml index 3911e133..f864ae91 100644 --- a/examples/napi/Cargo.toml +++ b/examples/napi/Cargo.toml @@ -13,7 +13,10 @@ napi3 = ["napi/napi3"] [dependencies] napi-derive = { path = "../../crates/macro", features = ["type-def"] } -napi = { path = "../../crates/napi", features = ["latin1"] } +napi = { path = "../../crates/napi", features = ["latin1", "serde-json"] } +serde = "1" +serde_derive = "1" +serde_json = "1" [build-dependencies] napi-build = { path = "../../crates/build" } \ No newline at end of file diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 8981df32..9cf5bdc4 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -13,6 +13,7 @@ Generated by [AVA](https://avajs.dev). export function sumNums(nums: Array): number␊ export function getCwd(callback: (arg0: string) => void): void␊ export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void␊ + export function readPackageJson(): PackageJson␊ export enum Kind { Dog = 0, Cat = 1, Duck = 2 }␊ export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }␊ export function enumToI32(e: CustomNumEnum): number␊ @@ -33,4 +34,11 @@ Generated by [AVA](https://avajs.dev). set name(name: string)␊ whoami(): string␊ }␊ + export class PackageJson {␊ + name: string␊ + version: string␊ + dependencies: Record | null␊ + devDependencies: Record | null␊ + constructor(name: string, version: string, dependencies: Record | null, devDependencies: Record | null)␊ + }␊ ` diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 095ccffd..bc94ec3f 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 23810e19..643e74e7 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -20,6 +20,7 @@ import { mapOption, readFile, throwError, + readPackageJson, } from '../' test('number', (t) => { @@ -67,6 +68,12 @@ test('class', (t) => { dog.name = '可乐' t.is(dog.name, '可乐') + + const packageJson = readPackageJson() + t.is(packageJson.name, 'napi-rs') + t.is(packageJson.version, '0.0.0') + t.is(packageJson.dependencies, null) + t.snapshot(Object.keys(packageJson.devDependencies!).sort()) }) test('callback', (t) => { diff --git a/examples/napi/__test__/values.spec.ts.md b/examples/napi/__test__/values.spec.ts.md new file mode 100644 index 00000000..a33cc770 --- /dev/null +++ b/examples/napi/__test__/values.spec.ts.md @@ -0,0 +1,38 @@ +# Snapshot report for `examples/napi/__test__/values.spec.ts` + +The actual snapshot is saved in `values.spec.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## class + +> Snapshot 1 + + [ + '@types/debug', + '@types/lodash-es', + '@types/node', + '@types/sinon', + '@typescript-eslint/eslint-plugin', + '@typescript-eslint/parser', + 'ava', + 'benny', + 'c8', + 'cross-env', + 'esbuild', + 'eslint', + 'eslint-config-prettier', + 'eslint-plugin-import', + 'eslint-plugin-prettier', + 'husky', + 'lerna', + 'lint-staged', + 'npm-run-all', + 'prettier', + 'shx', + 'sinon', + 'source-map-support', + 'ts-node', + 'tslib', + 'typescript', + ] diff --git a/examples/napi/__test__/values.spec.ts.snap b/examples/napi/__test__/values.spec.ts.snap new file mode 100644 index 00000000..3004ebd5 Binary files /dev/null and b/examples/napi/__test__/values.spec.ts.snap differ diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index aae4452d..e354a68c 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -3,6 +3,7 @@ export function getNums(): Array export function sumNums(nums: Array): number export function getCwd(callback: (arg0: string) => void): void export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void +export function readPackageJson(): PackageJson export enum Kind { Dog = 0, Cat = 1, Duck = 2 } export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 } export function enumToI32(e: CustomNumEnum): number @@ -23,3 +24,10 @@ export class Animal { set name(name: string) whoami(): string } +export class PackageJson { + name: string + version: string + dependencies: Record | null + devDependencies: Record | null + constructor(name: string, version: string, dependencies: Record | null, devDependencies: Record | null) +} diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs index a6146b63..91c060ba 100644 --- a/examples/napi/src/class.rs +++ b/examples/napi/src/class.rs @@ -1,3 +1,5 @@ +use std::{collections::HashMap, fs}; + use napi::bindgen_prelude::*; use crate::r#enum::Kind; @@ -40,3 +42,20 @@ impl Animal { } } } + +#[napi(constructor)] +#[derive(Serialize, Deserialize, Debug)] +struct PackageJson { + pub name: String, + pub version: String, + pub dependencies: Option>, + #[serde(rename = "devDependencies")] + pub dev_dependencies: Option>, +} + +#[napi] +fn read_package_json() -> Result { + let raw = fs::read_to_string("package.json")?; + let p: PackageJson = serde_json::from_str(&raw)?; + Ok(p) +} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index b688315e..f1789ff7 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -1,5 +1,7 @@ #[macro_use] extern crate napi_derive; +#[macro_use] +extern crate serde_derive; mod array; mod callback; diff --git a/examples/napi/src/object.rs b/examples/napi/src/object.rs index 4f4cac2b..690addea 100644 --- a/examples/napi/src/object.rs +++ b/examples/napi/src/object.rs @@ -8,7 +8,7 @@ fn list_obj_keys(obj: Object) -> Vec { #[napi] fn create_obj(env: Env) -> Object { let mut obj = env.create_object().unwrap(); - obj.set("test".to_owned(), 1).unwrap(); + obj.set("test", 1).unwrap(); obj }