diff --git a/.eslintrc.yml b/.eslintrc.yml index 83281eed..b820e7e7 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -196,11 +196,12 @@ overrides: parserOptions: project: ./tsconfig.json - files: - - ./test_module/**/*.{ts,js} + - ./examples/**/*.{ts,js} plugins: - '@typescript-eslint' parserOptions: - project: ./test_module/tsconfig.json + project: + - ./examples/tsconfig.json - files: - ./bench/**/*.{ts,js} diff --git a/.github/workflows/linux-aarch64-musl.yaml b/.github/workflows/linux-aarch64-musl.yaml index f799c896..2d8029c2 100644 --- a/.github/workflows/linux-aarch64-musl.yaml +++ b/.github/workflows/linux-aarch64-musl.yaml @@ -77,7 +77,7 @@ jobs: - name: Cross build native tests run: | - docker run -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/napi-rs -w /napi-rs -e CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc builder yarn --cwd ./test_module build --target aarch64-unknown-linux-musl + docker run -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/napi-rs -w /napi-rs -e CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc builder sh -c "yarn --cwd ./examples/napi-compat-mode build --target aarch64-unknown-linux-musl && yarn --cwd ./examples/napi build --target aarch64-unknown-linux-musl" - name: Setup and run tests uses: docker://multiarch/alpine:aarch64-latest-stable diff --git a/.github/workflows/napi3.yaml b/.github/workflows/napi3.yaml index 7c0a6ad2..863c99c0 100644 --- a/.github/workflows/napi3.yaml +++ b/.github/workflows/napi3.yaml @@ -70,7 +70,8 @@ jobs: - name: Unit tests run: | - yarn --cwd ./test_module --ignore-engines build-napi3 + yarn --cwd ./examples/napi-compat-mode --ignore-engines build-napi3 + yarn --cwd ./examples/napi --ignore-engines build-napi3 yarn --ignore-engines test env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/windows-i686.yml b/.github/workflows/windows-i686.yml index f383269b..11c82bb5 100644 --- a/.github/workflows/windows-i686.yml +++ b/.github/workflows/windows-i686.yml @@ -40,7 +40,7 @@ jobs: - name: Install uses: actions-rs/toolchain@v1 with: - toolchain: 1.51.0 + toolchain: stable profile: minimal override: true @@ -72,7 +72,8 @@ jobs: - name: Build Tests run: | - yarn --cwd ./test_module build-i686 + yarn --cwd ./examples/napi-compat-mode build-i686 + yarn --cwd ./examples/napi build-i686 yarn test env: RUST_BACKTRACE: 1 diff --git a/Cargo.toml b/Cargo.toml index b785fb59..5f0d577b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,12 @@ [workspace] members = [ - "./build", - "./napi", - "./napi-derive", - "./napi-derive-example", - "./sys", - "./test_module", + "./crates/backend", + "./crates/build", + "./crates/macro", + "./crates/napi", + "./crates/sys", + "./examples/napi", + "./examples/napi-compat-mode", "./bench", "./memory-testing", ] diff --git a/README.md b/README.md index b5fab58b..ef1ef806 100644 --- a/README.md +++ b/README.md @@ -81,52 +81,36 @@ One nice feature is that this crate allows you to build add-ons purely with the ### Define JavaScript functions ```rust -#[js_function(1)] // ------> arguments length -fn fibonacci(ctx: CallContext) -> Result { - let n = ctx.get::(0)?.try_into()?; - ctx.env.create_int64(fibonacci_native(n)) -} +#[macro_use] +extern crate napi; -#[inline(always)] -fn fibonacci_native(n: i64) -> i64 { +// import the preludes +use napi::bindgen_prelude::*; + +/// module registerion is done by the runtime, no need to explicitly do it now. +#[napi] +fn fibonacci(n: u32) -> u32 { match n { 1 | 2 => 1, _ => fibonacci_native(n - 1) + fibonacci_native(n - 2), } } -``` -### Register module - -```rust -#[macro_use] -extern crate napi_derive; - -use napi::{JsObject, Result}; - -/// `exports` is `module.exports` object in NodeJS -#[module_exports] -fn init(mut exports: JsObject) -> Result<()> { - exports.create_named_method("fibonacci", fibonacci)?; - Ok(()) +/// use `Fn`, `FnMut` or `FnOnce` traits to defined JavaScript callbacks +/// the return type of callbacks can only be `Result`. +#[napi] +fn get_cwd Result<()>>(callback: T) { + callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap(); } + +/// or, define the callback signature in where clause +#[napi] +fn test_callback(callback: T) +where T: Fn(String) -> Result<()> +{} ``` -And you can also create `JavaScript` value while registering module: - -```rust -#[macro_use] -extern crate napi_derive; - -use napi::{JsObject, Result, Env}; - -#[module_exports] -fn init(mut exports: JsObject, env: Env) -> Result<()> { - exports.create_named_method("fibonacci", fibonacci)?; - exports.set_named_property("DEFAULT_VALUE", env.create_int64(100)?)?; - Ok(()) -} -``` +Checkout more examples in [examples](./examples) folder ## Building @@ -142,8 +126,8 @@ name = "awesome" crate-type = ["cdylib"] [dependencies] -napi = "1" -napi-derive = "1" +napi = "2" +napi-derive = "2" [build-dependencies] napi-build = "1" @@ -162,8 +146,6 @@ fn main() { So far, the `napi` build script has only been tested on `macOS` `Linux` `Windows x64 MSVC` and `FreeBSD`. -See the included [test_module](./test_module) for an example add-on. - Install the `@napi-rs/cli` to help you build your `Rust` codes and copy `Dynamic lib` file to `.node` file in case you can `require` it in your program. ```js @@ -221,84 +203,26 @@ yarn test ## Features table -### Create JavaScript values - -| NAPI | NAPI Version | Minimal Node version | Status | -| ------------------------------------------------------------------------------------------------------------ | ------------ | -------------------- | ------ | -| [napi_create_array](https://nodejs.org/api/n-api.html#n_api_napi_create_array) | 1 | v8.0.0 | ✅ | -| [napi_create_array_with_length](https://nodejs.org/api/n-api.html#n_api_napi_create_array_with_length) | 1 | v8.0.0 | ✅ | -| [napi_create_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_create_arraybuffer) | 1 | v8.0.0 | ✅ | -| [napi_create_buffer](https://nodejs.org/api/n-api.html#n_api_napi_create_buffer) | 1 | v8.0.0 | ✅ | -| [napi_create_buffer_copy](https://nodejs.org/api/n-api.html#n_api_napi_create_buffer_copy) | 1 | v8.0.0 | ✅ | -| [napi_create_date](https://nodejs.org/api/n-api.html#n_api_napi_create_date) | 5 | v11.11.0 | ✅ | -| [napi_create_external](https://nodejs.org/api/n-api.html#n_api_napi_create_external) | 1 | v8.0.0 | ✅ | -| [napi_create_external_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_create_external_arraybuffer) | 1 | v8.0.0 | ✅ | -| [napi_create_external_buffer](https://nodejs.org/api/n-api.html#n_api_napi_create_external_buffer) | 1 | v8.0.0 | ✅ | -| [napi_create_object](https://nodejs.org/api/n-api.html#n_api_napi_create_object) | 1 | v8.0.0 | ✅ | -| [napi_create_symbol](https://nodejs.org/api/n-api.html#n_api_napi_create_symbol) | 1 | v8.0.0 | ✅ | -| [napi_create_typedarray](https://nodejs.org/api/n-api.html#n_api_napi_create_typedarray) | 1 | v8.0.0 | ✅ | -| [napi_create_dataview](https://nodejs.org/api/n-api.html#n_api_napi_create_dataview) | 1 | v8.3.0 | ✅ | -| [napi_create_int32](https://nodejs.org/api/n-api.html#n_api_napi_create_int32) | 1 | v8.4.0 | ✅ | -| [napi_create_uint32](https://nodejs.org/api/n-api.html#n_api_napi_create_uint32) | 1 | v8.4.0 | ✅ | -| [napi_create_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_int64) | 1 | v8.4.0 | ✅ | -| [napi_create_double](https://nodejs.org/api/n-api.html#n_api_napi_create_double) | 1 | v8.4.0 | ✅ | -| [napi_create_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_int64) | 6 | v10.7.0 | ✅ | -| [napi_create_bigint_uint64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_uint64) | 6 | v10.7.0 | ✅ | -| [napi_create_bigint_words](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_words) | 6 | v10.7.0 | ✅ | -| [napi_create_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_create_string_latin1) | 1 | v8.0.0 | ✅ | -| [napi_create_string_utf16](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf16) | 1 | v8.0.0 | ✅ | -| [napi_create_string_utf8](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf8) | 1 | v8.0.0 | ✅ | -| [napi_type_tag](https://nodejs.org/api/n-api.html#n_api_napi_type_tag) | 8 | v14.8.0, v12.19.0 | ⚠️ | - -> I have no plan to implement `nape_type_tag` and related API in `napi-rs`, because we have implemented a `rust` replacement in [TaggedObject](https://github.com/napi-rs/napi-rs/blob/main/napi/src/js_values/tagged_object.rs) which is more convenient and more compatible. - -### [Functions to convert from Node-API to C types](https://nodejs.org/api/n-api.html#n_api_functions_to_convert_from_node_api_to_c_types) - -| NAPI | NAPI Version | Minimal Node Version | Status | -| ---------------------------------------------------------------------------------------------------- | ------------ | -------------------- | ------ | -| [napi_get_array_length](https://nodejs.org/api/n-api.html#n_api_napi_get_array_length) | 1 | v8.0.0 | ✅ | -| [napi_get_arraybuffer_info](https://nodejs.org/api/n-api.html#n_api_napi_get_arraybuffer_info) | 1 | v8.0.0 | ✅ | -| [napi_get_buffer_info](https://nodejs.org/api/n-api.html#n_api_napi_get_buffer_info) | 1 | v8.0.0 | ✅ | -| [napi_get_prototype](https://nodejs.org/api/n-api.html#n_api_napi_get_prototype) | 1 | v8.0.0 | ✅ | -| [napi_get_typedarray_info](https://nodejs.org/api/n-api.html#n_api_napi_get_typedarray_info) | 1 | v8.0.0 | ✅ | -| [napi_get_dataview_info](https://nodejs.org/api/n-api.html#n_api_napi_get_dataview_info) | 1 | v8.3.0 | ✅ | -| [napi_get_date_value](https://nodejs.org/api/n-api.html#n_api_napi_get_date_value) | 5 | v11.11.0 | ✅ | -| [napi_get_value_bool](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bool) | 1 | v8.0.0 | ✅ | -| [napi_get_value_double](https://nodejs.org/api/n-api.html#n_api_napi_get_value_double) | 1 | v8.0.0 | ✅ | -| [napi_get_value_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_int64) | 6 | v10.7.0 | ✅ | -| [napi_get_value_bigint_uint64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_uint64) | 6 | v10.7.0 | ✅ | -| [napi_get_value_bigint_words](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words) | 6 | v10.7.0 | ✅ | -| [napi_get_value_external](https://nodejs.org/api/n-api.html#n_api_napi_get_value_external) | 1 | v8.0.0 | ✅ | -| [napi_get_value_int32](https://nodejs.org/api/n-api.html#n_api_napi_get_value_int32) | 1 | v8.0.0 | ✅ | -| [napi_get_value_int64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_int64) | 1 | v8.0.0 | ✅ | -| [napi_get_value_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_latin1) | 1 | v8.0.0 | ✅ | -| [napi_get_value_string_utf8](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_utf8) | 1 | v8.0.0 | ✅ | -| [napi_get_value_string_utf16](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_utf16) | 1 | v8.0.0 | ✅ | -| [napi_get_value_uint32](https://nodejs.org/api/n-api.html#n_api_napi_get_value_uint32) | 1 | v8.0.0 | ✅ | -| [napi_get_boolean](https://nodejs.org/api/n-api.html#n_api_napi_get_boolean) | 1 | v8.0.0 | ✅ | -| [napi_get_global](https://nodejs.org/api/n-api.html#n_api_napi_get_global) | 1 | v8.0.0 | ✅ | -| [napi_get_null](https://nodejs.org/api/n-api.html#n_api_napi_get_null) | 1 | v8.0.0 | ✅ | -| [napi_get_undefined](https://nodejs.org/api/n-api.html#n_api_napi_get_undefined) | 1 | v8.0.0 | ✅ | - -### [Working with JavaScript Values and Abstract Operations](https://nodejs.org/api/n-api.html#n_api_working_with_javascript_values_and_abstract_operations) - -| NAPI | NAPI Version | Minimal Node Version | Status | -| ---------------------------------------------------------------------------------------------------- | ------------ | -------------------- | ------ | -| [napi_coerce_to_bool](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_bool) | 1 | v8.0.0 | ✅ | -| [napi_coerce_to_number](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_number) | 1 | v8.0.0 | ✅ | -| [napi_coerce_to_object](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_object) | 1 | v8.0.0 | ✅ | -| [napi_coerce_to_string](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_string) | 1 | v8.0.0 | ✅ | -| [napi_typeof](https://nodejs.org/api/n-api.html#n_api_napi_typeof) | 1 | v8.0.0 | ✅ | -| [napi_instanceof](https://nodejs.org/api/n-api.html#n_api_napi_instanceof) | 1 | v8.0.0 | ✅ | -| [napi_is_array](https://nodejs.org/api/n-api.html#n_api_napi_is_array) | 1 | v8.0.0 | ✅ | -| [napi_is_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_is_arraybuffer) | 1 | v8.0.0 | ✅ | -| [napi_is_buffer](https://nodejs.org/api/n-api.html#n_api_napi_is_buffer) | 1 | v8.0.0 | ✅ | -| [napi_is_date](https://nodejs.org/api/n-api.html#n_api_napi_is_date) | 1 | v8.0.0 | ✅ | -| [napi_is_error](https://nodejs.org/api/n-api.html#n_api_napi_is_error_1) | 1 | v8.0.0 | ✅ | -| [napi_is_typedarray](https://nodejs.org/api/n-api.html#n_api_napi_is_typedarray) | 1 | v8.0.0 | ✅ | -| [napi_is_dataview](https://nodejs.org/api/n-api.html#n_api_napi_is_dataview) | 1 | v8.3.0 | ✅ | -| [napi_strict_equals](https://nodejs.org/api/n-api.html#n_api_napi_strict_equals) | 1 | v8.0.0 | ✅ | -| [napi_detach_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_detach_arraybuffer) | 7 | v13.3.0 | ✅ | -| [napi_is_detached_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_is_detached_arraybuffer) | 7 | v13.3.0 | ✅ | -| [napi_object_freeze](https://nodejs.org/api/n-api.html#n_api_napi_object_freeze) | 8 | v14.14.0, v12.20.0 | ✅ | -| [napi_object_seal](https://nodejs.org/api/n-api.html#n_api_napi_object_seal) | 8 | v14.14.0, v12.20.0 | ✅ | +| Rust Type | Node Type | [NAPI Version](https://nodejs.org/api/n-api.html#n_api_node_api_version_matrix) | Minimal Node version | +| ----------------------- | ---------------------- | ------------------------------------------------------------------------------- | -------------------- | +| u32 | Number | 1 | v8.0.0 | +| i32/i64 | Number | 1 | v8.0.0 | +| f64 | Number | 1 | v8.0.0 | +| bool | Boolean | 1 | v8.0.0 | +| String/&'a str | String | 1 | v8.0.0 | +| Latin1String | String | 1 | v8.0.0 | +| UTF16String | String | 1 | v8.0.0 | +| Object | Object | 1 | v8.0.0 | +| Array | Array | 1 | v8.0.0 | +| Vec | Array | 1 | v8.0.0 | +| Buffer | Buffer | 1 | v8.0.0 | +| Null | null | 1 | v8.0.0 | +| Undefined/() | undefined | 1 | v8.0.0 | +| Result<()> | Error | 1 | v8.0.0 | +| T: Fn(...) -> Result | function | 1 | v8.0.0 | +| (NOT YET) | global | 1 | v8.0.0 | +| (NOT YET) | Symbol | 1 | v8.0.0 | +| (NOT YET) | Promise | 1 | b8.5.0 | +| (NOT YET) | ArrayBuffer/TypedArray | 1 | v8.0.0 | +| (NOT YET) | threadsafe function | 4 | v10.6.0 | +| (NOT YET) | BigInt | 6 | v10.7.0 | diff --git a/ava.config.js b/ava.config.js index ddb3882a..0490d102 100644 --- a/ava.config.js +++ b/ava.config.js @@ -1,9 +1,9 @@ const configuration = { extensions: ['ts', 'tsx'], - files: ['test_module/__test__/**/*.spec.ts', 'cli/__test__/**/*.spec.ts'], + files: ['examples/**/__test__/**/*.spec.ts'], require: ['ts-node/register/transpile-only'], environmentVariables: { - TS_NODE_PROJECT: './test_module/tsconfig.json', + TS_NODE_PROJECT: './examples/tsconfig.json', }, timeout: '1m', } diff --git a/bench/Cargo.toml b/bench/Cargo.toml index 92c3a8a4..d47aae60 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -8,8 +8,8 @@ version = "0.1.0" crate-type = ["cdylib"] [dependencies] -napi = {path = "../napi", features = ["tokio_rt", "serde-json"]} -napi-derive = {path = "../napi-derive"} +napi = {path = "../crates/napi", features = ["tokio_rt", "serde-json", "compat-mode"]} +napi-derive = {path = "../crates/macro", features = ["compat-mode"]} serde = "1" serde_json = "1" @@ -17,4 +17,4 @@ serde_json = "1" mimalloc = {version = "0.1"} [build-dependencies] -napi-build = {path = "../build"} +napi-build = {path = "../crates/build"} diff --git a/bench/src/get_set_property.rs b/bench/src/get_set_property.rs index 9962cb78..96779789 100644 --- a/bench/src/get_set_property.rs +++ b/bench/src/get_set_property.rs @@ -44,16 +44,16 @@ pub fn register_js(exports: &mut JsObject, env: &Env) -> Result<()> { "TestClass", test_class_constructor, &[ - Property::new(env, "miterNative")? + Property::new("miterNative")? .with_getter(get_miter_native) .with_setter(set_miter_native), - Property::new(env, "miter")? + Property::new("miter")? .with_getter(get_miter) .with_setter(set_miter), - Property::new(env, "lineJoinNative")? + Property::new("lineJoinNative")? .with_getter(get_line_join_native) .with_setter(set_line_join_native), - Property::new(env, "lineJoin")? + Property::new("lineJoin")? .with_getter(get_line_join) .with_setter(set_line_join), ], diff --git a/cli/src/build.ts b/cli/src/build.ts index 10e299e1..e34baf3b 100644 --- a/cli/src/build.ts +++ b/cli/src/build.ts @@ -14,6 +14,7 @@ import { mkdirAsync, readFileAsync, unlinkAsync, + writeFileAsync, } from './utils' const debug = debugFactory('build') @@ -50,7 +51,7 @@ export class BuildCommand extends Command { ? join(process.cwd(), this.cargoCwd) : process.cwd() const releaseFlag = this.isRelease ? `--release` : '' - const targetFLag = this.targetTripleDir + const targetFlag = this.targetTripleDir ? `--target ${this.targetTripleDir}` : '' const featuresFlag = this.features ? `--features ${this.features}` : '' @@ -64,16 +65,20 @@ export class BuildCommand extends Command { debug(`Current triple is: ${chalk.green(triple.raw)}`) const externalFlags = [ releaseFlag, - targetFLag, + targetFlag, featuresFlag, this.cargoFlags, ] .filter((flag) => Boolean(flag)) .join(' ') const cargoCommand = `cargo build ${externalFlags}` + const intermediateTypeFile = join(__dirname, `type_def.${Date.now()}.tmp`) debug(`Run ${chalk.green(cargoCommand)}`) execSync(cargoCommand, { - env: process.env, + env: { + ...process.env, + TYPE_DEF_TMP_PATH: intermediateTypeFile, + }, stdio: 'inherit', cwd, }) @@ -190,6 +195,11 @@ export class BuildCommand extends Command { debug(`Write binary content to [${chalk.yellowBright(distModulePath)}]`) await copyFileAsync(sourcePath, distModulePath) + + await processIntermediateTypeFile( + intermediateTypeFile, + join(this.destDir ?? '.', 'type.d.ts'), + ) } } @@ -205,3 +215,60 @@ async function findUp(dir = process.cwd()): Promise { dirs.pop() return findUp(dirs.join(sep)) } + +interface TypeDef { + kind: 'fn' | 'struct' | 'impl' | 'enum' + name: string + def: string +} + +async function processIntermediateTypeFile(source: string, target: string) { + if (!(await existsAsync(source))) { + debug(`do not find tmp type file. skip type generation`) + return + } + + const tmpFile = await readFileAsync(source, 'utf8') + const lines = tmpFile + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + let dts = '' + const classes = new Map() + const impls = new Map() + + lines.forEach((line) => { + const def = JSON.parse(line) as TypeDef + + switch (def.kind) { + case 'fn': + case 'enum': + dts += def.def + '\n' + break + case 'struct': + classes.set(def.name, def.def) + break + case 'impl': + impls.set(def.name, def.def) + } + }) + + for (const [name, def] of impls.entries()) { + const classDef = classes.get(name) + + dts += `export class ${name} { + ${(classDef ?? '') + .split('\n') + .map((line) => line.trim()) + .join('\n ')} + ${def + .split('\n') + .map((line) => line.trim()) + .join('\n ')} +} +` + } + + await unlinkAsync(source) + await writeFileAsync(target, dts, 'utf8') +} diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml new file mode 100644 index 00000000..8a179940 --- /dev/null +++ b/crates/backend/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "napi-derive-backend" +version = "0.1.0" +edition = "2018" + +[features] +type-def = ["regex", "once_cell"] +strict = [] + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = {version = "1.0", features = ["fold", "full"]} +convert_case = "0.4.0" + +[dependencies.regex] +optional = true +version = "1" + +[dependencies.once_cell] +optional = true +version = "1" diff --git a/crates/backend/README.md b/crates/backend/README.md new file mode 100644 index 00000000..70bcca03 --- /dev/null +++ b/crates/backend/README.md @@ -0,0 +1,3 @@ +# napi-derive-backend + +Take care the ast parsing from `napi-derive` and generate "bridge" runtime code for both nodejs and rust. diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs new file mode 100644 index 00000000..004707b5 --- /dev/null +++ b/crates/backend/src/ast.rs @@ -0,0 +1,85 @@ +use proc_macro2::Ident; +use syn::Attribute; + +#[derive(Debug, Clone)] +pub struct NapiFn { + pub name: Ident, + pub js_name: String, + pub attrs: Vec, + pub args: Vec, + pub ret: Option, + pub is_async: bool, + pub fn_self: Option, + pub kind: FnKind, + pub vis: syn::Visibility, + pub parent: Option, + pub strict: bool, +} + +#[derive(Debug, Clone)] +pub struct CallbackArg { + pub pat: Box, + pub args: Vec, + pub ret: Option, +} + +#[derive(Debug, Clone)] +pub enum NapiFnArgKind { + PatType(Box), + Callback(Box), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum FnKind { + Normal, + Constructor, + Getter, + Setter, +} + +#[derive(Debug, Clone)] +pub enum FnSelf { + Value, + Ref, + MutRef, +} + +#[derive(Debug, Clone)] +pub struct NapiStruct { + pub name: Ident, + pub js_name: String, + pub vis: syn::Visibility, + pub fields: Vec, + pub is_tuple: bool, + pub gen_default_ctor: bool, +} + +#[derive(Debug, Clone)] +pub struct NapiStructField { + pub name: syn::Member, + pub js_name: String, + pub ty: syn::Type, + pub getter: bool, + pub setter: bool, +} + +#[derive(Debug, Clone)] +pub struct NapiImpl { + pub name: Ident, + pub js_name: String, + pub items: Vec, +} + +#[derive(Debug, Clone)] +pub struct NapiEnum { + pub name: Ident, + pub js_name: String, + pub variants: Vec, +} + +#[derive(Debug, Clone)] +pub struct NapiEnumVariant { + pub name: Ident, + pub val: i32, + pub comments: Vec, +} diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs new file mode 100644 index 00000000..71657ee8 --- /dev/null +++ b/crates/backend/src/codegen.rs @@ -0,0 +1,28 @@ +use proc_macro2::{Ident, Span, TokenStream}; + +use crate::BindgenResult; + +mod r#enum; +mod r#fn; +mod r#struct; + +pub trait TryToTokens { + fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()>; + + fn try_to_token_stream(&self) -> BindgenResult { + let mut tokens = TokenStream::default(); + self.try_to_tokens(&mut tokens)?; + + Ok(tokens) + } +} + +fn get_intermediate_ident(name: &str) -> Ident { + let new_name = format!("__napi__{}", name); + Ident::new(&new_name, Span::call_site()) +} + +fn get_register_ident(name: &str) -> Ident { + let new_name = format!("__napi_register__{}", name); + Ident::new(&new_name, Span::call_site()) +} diff --git a/crates/backend/src/codegen/enum.rs b/crates/backend/src/codegen/enum.rs new file mode 100644 index 00000000..a8857afe --- /dev/null +++ b/crates/backend/src/codegen/enum.rs @@ -0,0 +1,134 @@ +use proc_macro2::{Literal, TokenStream}; +use quote::ToTokens; + +use crate::{codegen::get_register_ident, BindgenResult, NapiEnum, TryToTokens}; + +impl TryToTokens for NapiEnum { + fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { + let register = self.gen_module_register(); + let napi_value_conversion = self.gen_napi_value_map_impl(); + + (quote! { + #napi_value_conversion + #register + }) + .to_tokens(tokens); + + Ok(()) + } +} + +impl NapiEnum { + fn gen_napi_value_map_impl(&self) -> TokenStream { + let name = &self.name; + let name_str = self.name.to_string(); + let mut from_napi_branches = vec![]; + let mut to_napi_branches = vec![]; + + self.variants.iter().for_each(|v| { + let val = Literal::i32_unsuffixed(v.val); + let v_name = &v.name; + + from_napi_branches.push(quote! { #val => Ok(#name::#v_name) }); + to_napi_branches.push(quote! { #name::#v_name => #val }); + }); + + quote! { + impl TypeName for #name { + fn type_name() -> &'static str { + #name_str + } + } + + impl ValidateNapiValue for #name { + unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<()> { + assert_type_of!(env, napi_val, ValueType::Number) + } + } + + impl FromNapiValue for #name { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let val = i32::from_napi_value(env, napi_val).map_err(|e| { + error!( + e.status, + "Failed to convert napi value into enum `{}`. {}", + #name_str, + e, + ) + })?; + + match val { + #(#from_napi_branches,)* + _ => { + Err(error!( + Status::InvalidArg, + "value `{}` does not match any variant of enum `{}`", + val, + #name_str + )) + } + } + } + } + + impl ToNapiValue for #name { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let val = match val { + #(#to_napi_branches,)* + }; + + i32::to_napi_value(env, val) + } + } + } + } + + fn gen_module_register(&self) -> TokenStream { + let name_str = self.name.to_string(); + let js_name_lit = Literal::string(&self.js_name); + let register_name = get_register_ident(&name_str); + + let mut define_properties = vec![]; + + for variant in self.variants.iter() { + let name_lit = Literal::string(&variant.name.to_string()); + let val_lit = Literal::i32_unsuffixed(variant.val); + + define_properties.push(quote! { + { + let name = CString::new(#name_lit).unwrap(); + check_status!( + sys::napi_set_named_property(env, obj_ptr, name.as_ptr(), i32::to_napi_value(env, #val_lit)?), + "Failed to defined enum `{}`", + #js_name_lit + )?; + }; + }) + } + + quote! { + #[allow(non_snake_case)] + #[allow(clippy::all)] + #[ctor] + fn #register_name() { + use std::ffi::CString; + use std::ptr; + + unsafe fn cb(env: sys::napi_env) -> Result { + let mut obj_ptr = ptr::null_mut(); + + check_status!( + sys::napi_create_object(env, &mut obj_ptr), + "Failed to create napi object" + )?; + + #(#define_properties)* + + Ok(obj_ptr) + } + + register_module_export(#js_name_lit, cb); + } + } + } +} diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs new file mode 100644 index 00000000..9dc6f285 --- /dev/null +++ b/crates/backend/src/codegen/fn.rs @@ -0,0 +1,259 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::ToTokens; + +use crate::{ + codegen::{get_intermediate_ident, get_register_ident}, + BindgenResult, CallbackArg, FnKind, FnSelf, NapiFn, NapiFnArgKind, TryToTokens, +}; + +impl TryToTokens for NapiFn { + fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { + let name_str = self.name.to_string(); + let intermediate_ident = get_intermediate_ident(&name_str); + let args_len = self.args.len(); + + let (arg_conversions, arg_names) = self.gen_arg_conversions(); + let receiver = self.gen_fn_receiver(); + let receiver_ret_name = Ident::new("_ret", Span::call_site()); + let ret = self.gen_fn_return(&receiver_ret_name); + let register = self.gen_fn_register(); + + let attrs = &self.attrs; + + (quote! { + #(#attrs)* + #[doc(hidden)] + #[allow(non_snake_case)] + #[allow(clippy::all)] + extern "C" fn #intermediate_ident( + env: sys::napi_env, + cb: sys::napi_callback_info + ) -> sys::napi_value { + unsafe { + CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| { + #(#arg_conversions)* + let #receiver_ret_name = { + #receiver(#(#arg_names),*) + }; + + #ret + }).unwrap_or_else(|e| { + JsError::from(e).throw_into(env); + std::ptr::null_mut::() + }) + } + } + + #register + }) + .to_tokens(tokens); + + Ok(()) + } +} + +impl NapiFn { + fn gen_arg_conversions(&self) -> (Vec, Vec) { + let mut arg_conversions = vec![]; + let mut args = vec![]; + + // fetch this + if let Some(parent) = &self.parent { + match self.fn_self { + Some(FnSelf::Ref) => { + arg_conversions.push(quote! { let this = cb.unwrap_borrow::<#parent>()?; }); + } + Some(FnSelf::MutRef) => { + arg_conversions.push(quote! { let this = cb.unwrap_borrow_mut::<#parent>()?; }); + } + _ => {} + }; + } + + let mut skipped_arg_count = 0; + self.args.iter().enumerate().for_each(|(i, arg)| { + let i = i - skipped_arg_count; + let ident = Ident::new(&format!("arg{}", i), Span::call_site()); + + match arg { + NapiFnArgKind::PatType(path) => { + if &path.ty.to_token_stream().to_string() == "Env" { + args.push(quote! { Env::from(env) }); + skipped_arg_count += 1; + } else { + arg_conversions.push(self.gen_ty_arg_conversion(&ident, i, path)); + args.push(quote! { #ident }); + } + } + NapiFnArgKind::Callback(cb) => { + arg_conversions.push(self.gen_cb_arg_conversion(&ident, i, cb)); + args.push(quote! { #ident }); + } + } + }); + + (arg_conversions, args) + } + + fn gen_ty_arg_conversion( + &self, + arg_name: &Ident, + index: usize, + path: &syn::PatType, + ) -> TokenStream { + let ty = &*path.ty; + match ty { + syn::Type::Reference(syn::TypeReference { + mutability: Some(_), + elem, + .. + }) => { + quote! { + let #arg_name = unsafe { <#elem as FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))? }; + } + } + syn::Type::Reference(syn::TypeReference { elem, .. }) => { + quote! { + let #arg_name = unsafe { <#elem as FromNapiRef>::from_napi_ref(env, cb.get_arg(#index))? }; + } + } + _ => { + let type_check = if self.strict { + quote! { + <#ty as ValidateNapiValue>::validate(env, cb.get_arg(#index))?; + } + } else { + quote! {} + }; + + quote! { + let #arg_name = unsafe { + #type_check + <#ty as FromNapiValue>::from_napi_value(env, cb.get_arg(#index))? + }; + } + } + } + } + + fn gen_cb_arg_conversion(&self, arg_name: &Ident, index: usize, cb: &CallbackArg) -> TokenStream { + let mut inputs = vec![]; + let mut arg_conversions = vec![]; + + for (i, ty) in cb.args.iter().enumerate() { + let cb_arg_ident = Ident::new(&format!("callback_arg_{}", i), Span::call_site()); + inputs.push(quote! { #cb_arg_ident: #ty }); + arg_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, #cb_arg_ident)? }); + } + + let ret = match &cb.ret { + Some(ty) => { + quote! { + let ret = <#ty as FromNapiValue>::from_napi_value(env, ret_ptr)?; + + Ok(ret) + } + } + None => quote! { Ok(()) }, + }; + + quote! { + assert_type_of!(env, cb.get_arg(#index), ValueType::Function)?; + let #arg_name = |#(#inputs),*| { + let args = vec![ + #(#arg_conversions),* + ]; + + let mut ret_ptr = std::ptr::null_mut(); + + check_status!( + sys::napi_call_function( + env, + cb.this(), + cb.get_arg(#index), + args.len(), + args.as_ptr(), + &mut ret_ptr + ), + "Failed to call napi callback", + )?; + + #ret + }; + } + } + + fn gen_fn_receiver(&self) -> TokenStream { + let name = &self.name; + + match self.fn_self { + Some(FnSelf::Value) => { + // impossible, errord in parser + unimplemented!(); + } + Some(FnSelf::Ref) | Some(FnSelf::MutRef) => quote! { this.#name }, + None => match &self.parent { + Some(class) => quote! { #class::#name }, + None => quote! { #name }, + }, + } + } + + fn gen_fn_return(&self, ret: &Ident) -> TokenStream { + let js_name = &self.js_name; + let ret_ty = &self.ret; + + if self.kind == FnKind::Constructor { + quote! { cb.construct(#js_name, #ret) } + } else if let Some(ref ty) = ret_ty { + quote! { + <#ty as ToNapiValue>::to_napi_value(env, #ret) + } + } else { + quote! { + <() as ToNapiValue>::to_napi_value(env, ()) + } + } + } + + fn gen_fn_register(&self) -> TokenStream { + if self.parent.is_some() { + quote! {} + } else { + let name_str = self.name.to_string(); + let name_len = name_str.len(); + let js_name = &self.js_name; + let module_register_name = get_register_ident(&name_str); + let intermediate_ident = get_intermediate_ident(&name_str); + + quote! { + #[allow(clippy::all)] + #[allow(non_snake_case)] + #[ctor] + fn #module_register_name() { + unsafe fn cb(env: sys::napi_env) -> Result { + let mut fn_ptr = std::ptr::null_mut(); + let js_name = std::ffi::CString::new(#js_name).unwrap(); + + check_status!( + sys::napi_create_function( + env, + js_name.as_ptr(), + #name_len, + Some(#intermediate_ident), + std::ptr::null_mut(), + &mut fn_ptr, + ), + "Failed to register function `{}`", + #name_str, + )?; + + Ok(fn_ptr) + } + + register_module_export(#js_name, cb); + } + } + } + } +} diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs new file mode 100644 index 00000000..13d316c4 --- /dev/null +++ b/crates/backend/src/codegen/struct.rs @@ -0,0 +1,386 @@ +use std::collections::HashMap; + +use proc_macro2::{Ident, Literal, Span, TokenStream}; +use quote::ToTokens; + +use crate::{ + codegen::{get_intermediate_ident, get_register_ident}, + BindgenResult, FnKind, NapiImpl, NapiStruct, TryToTokens, +}; + +// Generate trait implementations for given Struct. +fn gen_napi_value_map_impl(name: &Ident, to_napi_val_impl: TokenStream) -> TokenStream { + let name_str = name.to_string(); + quote! { + impl TypeName for #name { + fn type_name() -> &'static str { + #name_str + } + } + + #to_napi_val_impl + + impl FromNapiRef for #name { + unsafe fn from_napi_ref(env: sys::napi_env, napi_val: sys::napi_value) -> Result<&'static Self> { + let mut wrapped_val: *mut std::ffi::c_void = std::ptr::null_mut(); + + check_status!( + sys::napi_unwrap(env, napi_val, &mut wrapped_val), + "Failed to recover `{}` type from napi value", + #name_str, + )?; + + Ok(&*(wrapped_val as *const #name)) + } + } + + impl FromNapiMutRef for #name { + unsafe fn from_napi_mut_ref(env: sys::napi_env, napi_val: sys::napi_value) -> Result<&'static mut Self> { + let mut wrapped_val: *mut std::ffi::c_void = std::ptr::null_mut(); + + check_status!( + sys::napi_unwrap(env, napi_val, &mut wrapped_val), + "Failed to recover `{}` type from napi value", + #name_str, + )?; + + Ok(&mut *(wrapped_val as *mut #name)) + } + } + } +} + +impl TryToTokens for NapiStruct { + fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { + let napi_value_map_impl = self.gen_napi_value_map_impl(); + let class_helper_mod = self.gen_helper_mod(); + + (quote! { + #napi_value_map_impl + #class_helper_mod + }) + .to_tokens(tokens); + + Ok(()) + } +} + +impl NapiStruct { + fn gen_helper_mod(&self) -> TokenStream { + let mod_name = Ident::new( + &format!("__napi_helper__{}", self.name.to_string()), + Span::call_site(), + ); + + let ctor = if self.gen_default_ctor { + self.gen_default_ctor() + } else { + quote! {} + }; + + let getters_setters = self.gen_default_getters_setters(); + let register = self.gen_register(); + + quote! { + #[allow(clippy::all)] + #[allow(non_snake_case)] + mod #mod_name { + use std::ptr; + use super::*; + + #ctor + #(#getters_setters)* + #register + } + } + } + + fn gen_default_ctor(&self) -> TokenStream { + let name = &self.name; + let js_name_str = &self.js_name; + let fields_len = self.fields.len(); + let mut fields = vec![]; + + for (i, field) in self.fields.iter().enumerate() { + let ty = &field.ty; + match &field.name { + syn::Member::Named(ident) => fields + .push(quote! { #ident: <#ty as FromNapiValue>::from_napi_value(env, cb.get_arg(#i))? }), + syn::Member::Unnamed(_) => { + fields.push(quote! { <#ty as FromNapiValue>::from_napi_value(env, cb.get_arg(#i))? }); + } + } + } + + let construct = if self.is_tuple { + quote! { #name (#(#fields),*) } + } else { + quote! { #name {#(#fields),*} } + }; + + quote! { + extern "C" fn constructor( + env: sys::napi_env, + cb: sys::napi_callback_info + ) -> sys::napi_value { + CallbackInfo::<#fields_len>::new(env, cb, None) + .and_then(|cb| unsafe { cb.construct(#js_name_str, #construct) }) + .unwrap_or_else(|e| { + unsafe { JsError::from(e).throw_into(env) }; + std::ptr::null_mut::() + }) + } + } + } + + fn gen_napi_value_map_impl(&self) -> TokenStream { + if !self.gen_default_ctor { + return gen_napi_value_map_impl(&self.name, quote! {}); + } + + let name = &self.name; + let js_name_str = &self.js_name; + + let mut fields_conversions = vec![]; + let mut field_destructions = vec![]; + + for field in self.fields.iter() { + let ty = &field.ty; + + match &field.name { + syn::Member::Named(ident) => { + field_destructions.push(quote! { #ident }); + fields_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, #ident)? }); + } + syn::Member::Unnamed(i) => { + field_destructions.push(quote! { arg#i }); + fields_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, arg#i)? }); + } + } + } + + let destructed_fields = if self.is_tuple { + quote! { + let Self (#(#field_destructions),*) + } + } else { + quote! { + let Self {#(#field_destructions),*} + } + }; + + gen_napi_value_map_impl( + name, + quote! { + impl ToNapiValue for #name { + unsafe fn to_napi_value(env: sys::napi_env, val: #name) -> Result { + if let Some(ctor_ref) = get_class_constructor(#js_name_str) { + let mut ctor = std::ptr::null_mut(); + + check_status!( + sys::napi_get_reference_value(env, ctor_ref, &mut ctor), + "Failed to get constructor of class `{}`", + #js_name_str + )?; + + let mut result = std::ptr::null_mut(); + #destructed_fields = val; + let args = vec![#(#fields_conversions),*]; + + check_status!( + sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut result), + "Failed to construct class `{}`", + #js_name_str + )?; + + Ok(result) + } else { + Err(Error::new(Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_str))) + } + } + } + }, + ) + } + + fn gen_default_getters_setters(&self) -> Vec { + let mut getters_setters = vec![]; + let struct_name = &self.name; + + for field in self.fields.iter() { + let field_ident = &field.name; + let field_name = match &field.name { + syn::Member::Named(ident) => ident.to_string(), + syn::Member::Unnamed(i) => format!("field{}", i.index), + }; + let ty = &field.ty; + + let getter_name = Ident::new(&format!("get_{}", field_name), Span::call_site()); + let setter_name = Ident::new(&format!("set_{}", field_name), Span::call_site()); + + if field.getter { + getters_setters.push(quote! { + extern "C" fn #getter_name( + env: sys::napi_env, + cb: sys::napi_callback_info + ) -> sys::napi_value { + CallbackInfo::<0>::new(env, cb, Some(0)) + .and_then(|mut cb| unsafe { cb.unwrap_borrow::<#struct_name>() }) + .and_then(|obj| { + let val = obj.#field_ident.to_owned(); + unsafe { <#ty as ToNapiValue>::to_napi_value(env, val) } + }) + .unwrap_or_else(|e| { + unsafe { JsError::from(e).throw_into(env) }; + std::ptr::null_mut::() + }) + } + }); + } + + if field.setter { + getters_setters.push(quote! { + extern "C" fn #setter_name( + env: sys::napi_env, + cb: sys::napi_callback_info + ) -> sys::napi_value { + CallbackInfo::<1>::new(env, cb, Some(1)) + .and_then(|mut cb_info| unsafe { + cb_info.unwrap_borrow_mut::<#struct_name>() + .and_then(|obj| { + <#ty as FromNapiValue>::from_napi_value(env, cb_info.get_arg(0)) + .and_then(move |val| { + obj.#field_ident = val; + <() as ToNapiValue>::to_napi_value(env, ()) + }) + }) + }) + .unwrap_or_else(|e| { + unsafe { JsError::from(e).throw_into(env) }; + std::ptr::null_mut::() + }) + } + }); + } + } + + getters_setters + } + + fn gen_register(&self) -> TokenStream { + let name_str = self.name.to_string(); + let struct_register_name = get_register_ident(&format!("{}_struct", name_str)); + let js_name = &self.js_name; + let mut props = vec![]; + + if self.gen_default_ctor { + props.push(quote! { Property::new("constructor").unwrap().with_ctor(constructor) }); + } + + for field in self.fields.iter() { + let field_name = match &field.name { + syn::Member::Named(ident) => ident.to_string(), + syn::Member::Unnamed(i) => format!("field{}", i.index), + }; + + if !field.getter { + continue; + } + + let js_name = &field.js_name; + let mut prop = quote! { + Property::new(#js_name) + .unwrap() + }; + + if field.getter { + let getter_name = Ident::new(&format!("get_{}", field_name), Span::call_site()); + (quote! { .with_getter(#getter_name) }).to_tokens(&mut prop); + } + + if field.setter { + let setter_name = Ident::new(&format!("set_{}", field_name), Span::call_site()); + (quote! { .with_setter(#setter_name) }).to_tokens(&mut prop); + } + + props.push(prop); + } + + quote! { + #[allow(non_snake_case)] + #[allow(clippy::all)] + #[ctor] + fn #struct_register_name() { + register_class(#name_str, #js_name, vec![#(#props),*]); + } + } + } +} + +impl TryToTokens for NapiImpl { + fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { + self.gen_helper_mod()?.to_tokens(tokens); + + Ok(()) + } +} + +impl NapiImpl { + fn gen_helper_mod(&self) -> BindgenResult { + let name_str = self.name.to_string(); + let js_name = &self.js_name; + let mod_name = Ident::new( + &format!("__napi_impl_helper__{}", name_str), + Span::call_site(), + ); + + let register_name = get_register_ident(&format!("{}_impl", name_str)); + + let mut methods = vec![]; + let mut props = HashMap::new(); + + for item in self.items.iter() { + let js_name = Literal::string(&item.js_name); + let item_str = item.name.to_string(); + let intermediate_name = get_intermediate_ident(&item_str); + methods.push(item.try_to_token_stream()?); + + let prop = props.entry(&item.js_name).or_insert_with(|| { + quote! { + Property::new(#js_name).unwrap() + } + }); + + let appendix = match item.kind { + FnKind::Constructor => quote! { .with_ctor(#intermediate_name) }, + FnKind::Getter => quote! { .with_getter(#intermediate_name) }, + FnKind::Setter => quote! { .with_setter(#intermediate_name) }, + _ => { + if item.fn_self.is_some() { + quote! { .with_method(#intermediate_name) } + } else { + quote! { .with_method(#intermediate_name).with_property_attributes(PropertyAttributes::Static) } + } + } + }; + + appendix.to_tokens(prop); + } + + let props: Vec<_> = props.values().collect(); + + Ok(quote! { + #[allow(non_snake_case)] + #[allow(clippy::all)] + mod #mod_name { + use super::*; + #(#methods)* + + #[ctor] + fn #register_name() { + register_class(#name_str, #js_name, vec![#(#props),*]); + } + } + }) + } +} diff --git a/crates/backend/src/error.rs b/crates/backend/src/error.rs new file mode 100644 index 00000000..ce5ed1d5 --- /dev/null +++ b/crates/backend/src/error.rs @@ -0,0 +1,136 @@ +use proc_macro2::*; +use quote::{ToTokens, TokenStreamExt}; +use syn::parse::Error; + +/// Provide a Diagnostic with the given span and message +#[macro_export] +macro_rules! err_span { + ($span:expr, $($msg:tt)*) => ( + $crate::Diagnostic::spanned_error(&$span, format!($($msg)*)) + ) +} + +/// Immediately fail and return an Err, with the arguments passed to err_span! +#[macro_export] +macro_rules! bail_span { + ($($t:tt)*) => ( + return Err(err_span!($($t)*).into()) + ) +} + +/// A struct representing a diagnostic to emit to the end-user as an error. +#[derive(Debug)] +pub struct Diagnostic { + inner: Repr, +} + +pub type BindgenResult = Result; + +#[derive(Debug)] +enum Repr { + Single { + text: String, + span: Option<(Span, Span)>, + }, + SynError(Error), + Multi { + diagnostics: Vec, + }, +} + +impl Diagnostic { + /// Generate a `Diagnostic` from an informational message with no Span + pub fn error>(text: T) -> Diagnostic { + Diagnostic { + inner: Repr::Single { + text: text.into(), + span: None, + }, + } + } + + /// Generate a `Diagnostic` from a Span and an informational message + pub fn span_error>(span: Span, text: T) -> Diagnostic { + Diagnostic { + inner: Repr::Single { + text: text.into(), + span: Some((span, span)), + }, + } + } + + /// Generate a `Diagnostic` from the span of any tokenizable object and a message + pub fn spanned_error>(node: &dyn ToTokens, text: T) -> Diagnostic { + Diagnostic { + inner: Repr::Single { + text: text.into(), + span: extract_spans(node), + }, + } + } + + /// Attempt to generate a `Diagnostic` from a vector of other `Diagnostic` instances. + /// If the `Vec` is empty, returns `Ok(())`, otherwise returns the new `Diagnostic` + pub fn from_vec(diagnostics: Vec) -> BindgenResult<()> { + if diagnostics.is_empty() { + Ok(()) + } else { + Err(Diagnostic { + inner: Repr::Multi { diagnostics }, + }) + } + } + + /// Immediately trigger a panic from this `Diagnostic` + #[allow(unconditional_recursion)] + pub fn panic(&self) -> ! { + match &self.inner { + Repr::Single { text, .. } => panic!("{}", text), + Repr::SynError(error) => panic!("{}", error), + Repr::Multi { diagnostics } => diagnostics[0].panic(), + } + } +} + +impl From for Diagnostic { + fn from(err: Error) -> Diagnostic { + Diagnostic { + inner: Repr::SynError(err), + } + } +} + +fn extract_spans(node: &dyn ToTokens) -> Option<(Span, Span)> { + let mut t = TokenStream::new(); + node.to_tokens(&mut t); + let mut tokens = t.into_iter(); + let start = tokens.next().map(|t| t.span()); + let end = tokens.last().map(|t| t.span()); + start.map(|start| (start, end.unwrap_or(start))) +} + +impl ToTokens for Diagnostic { + fn to_tokens(&self, dst: &mut TokenStream) { + match &self.inner { + Repr::Single { text, span } => { + let cs2 = (Span::call_site(), Span::call_site()); + let (start, end) = span.unwrap_or(cs2); + dst.append(Ident::new("compile_error", start)); + dst.append(Punct::new('!', Spacing::Alone)); + let mut message = TokenStream::new(); + message.append(Literal::string(text)); + let mut group = Group::new(Delimiter::Brace, message); + group.set_span(end); + dst.append(group); + } + Repr::Multi { diagnostics } => { + for diagnostic in diagnostics { + diagnostic.to_tokens(dst); + } + } + Repr::SynError(err) => { + err.to_compile_error().to_tokens(dst); + } + } + } +} diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs new file mode 100644 index 00000000..918a8b4a --- /dev/null +++ b/crates/backend/src/lib.rs @@ -0,0 +1,56 @@ +#[macro_use] +extern crate quote; + +use proc_macro2::TokenStream; + +#[macro_use] +pub mod error; +pub mod ast; +pub mod codegen; +#[cfg(feature = "type-def")] +pub mod typegen; + +pub use ast::*; +pub use codegen::*; +pub use error::{BindgenResult, Diagnostic}; +#[cfg(feature = "type-def")] +pub use typegen::*; + +#[derive(Debug)] +pub struct Napi { + pub comments: Vec, + pub item: NapiItem, +} + +macro_rules! napi_ast_impl { + ( $( ($v:ident, $ast:ident), )* ) => { + #[derive(Debug)] + pub enum NapiItem { + $($v($ast)),* + } + + impl TryToTokens for Napi { + fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { + match self.item { + $( NapiItem::$v(ref ast) => ast.try_to_tokens(tokens) ),* + } + } + } + + #[cfg(feature = "type-def")] + impl ToTypeDef for Napi { + fn to_type_def(&self) -> TypeDef { + match self.item { + $( NapiItem::$v(ref ast) => ast.to_type_def() ),* + } + } + } + }; +} + +napi_ast_impl! { + (Fn, NapiFn), + (Struct, NapiStruct), + (Impl, NapiImpl), + (Enum, NapiEnum), +} diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs new file mode 100644 index 00000000..350fb9c9 --- /dev/null +++ b/crates/backend/src/typegen.rs @@ -0,0 +1,78 @@ +mod r#enum; +mod r#fn; +mod r#struct; + +use once_cell::sync::Lazy; +use quote::ToTokens; +use regex::Regex; +use syn::Type; + +#[derive(Default)] +pub struct TypeDef { + pub kind: String, + pub name: String, + pub def: String, +} + +impl ToString for TypeDef { + fn to_string(&self) -> String { + format!( + r#"{{"kind": "{}", "name": "{}", "def": "{}"}}"#, + self.kind, self.name, self.def, + ) + } +} + +pub trait ToTypeDef { + fn to_type_def(&self) -> TypeDef; +} + +pub static VEC_REGEX: Lazy = Lazy::new(|| Regex::new(r"^Vec < (.*) >$").unwrap()); +pub static OPTION_REGEX: Lazy = Lazy::new(|| Regex::new(r"^Option < (.*) >").unwrap()); + +pub fn ty_to_ts_type(ty: &Type) -> String { + match ty { + Type::Reference(r) => ty_to_ts_type(&r.elem), + Type::Tuple(tuple) => { + format!( + "[{}]", + tuple + .elems + .iter() + .map(|elem| ty_to_ts_type(elem)) + .collect::>() + .join(", ") + ) + } + Type::Path(syn::TypePath { qself: None, path }) => { + str_to_ts_type(path.to_token_stream().to_string().as_str()) + } + + _ => "any".to_owned(), + } +} + +pub fn str_to_ts_type(ty: &str) -> String { + match ty { + "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" => "number".to_owned(), + "i128" | "isize" | "u64" | "u128" | "usize" => "BigInt".to_owned(), + "bool" => "boolean".to_owned(), + "String" | "str" | "char" => "string".to_owned(), + "Object" => "object".to_owned(), + // nothing but `& 'lifetime str` could ends with ` str` + s if s.ends_with(" str") => "string".to_owned(), + s if s.starts_with("Vec") && VEC_REGEX.is_match(s) => { + let captures = VEC_REGEX.captures(s).unwrap(); + let inner = captures.get(1).unwrap().as_str(); + + format!("Array<{}>", str_to_ts_type(inner)) + } + s if s.starts_with("Option") && OPTION_REGEX.is_match(s) => { + let captures = OPTION_REGEX.captures(s).unwrap(); + let inner = captures.get(1).unwrap().as_str(); + + format!("{} | undefined", str_to_ts_type(inner)) + } + s => s.to_owned(), + } +} diff --git a/crates/backend/src/typegen/enum.rs b/crates/backend/src/typegen/enum.rs new file mode 100644 index 00000000..edaf9f8c --- /dev/null +++ b/crates/backend/src/typegen/enum.rs @@ -0,0 +1,27 @@ +use super::{ToTypeDef, TypeDef}; +use crate::NapiEnum; + +impl ToTypeDef for NapiEnum { + fn to_type_def(&self) -> TypeDef { + TypeDef { + kind: "enum".to_owned(), + name: self.js_name.to_owned(), + def: format!( + r"export enum {js_name} {{ {variants} }}", + js_name = &self.js_name, + variants = self.gen_ts_variants() + ), + } + } +} + +impl NapiEnum { + fn gen_ts_variants(&self) -> String { + self + .variants + .iter() + .map(|v| format!("{} = {}", v.name, v.val)) + .collect::>() + .join(", ") + } +} diff --git a/crates/backend/src/typegen/fn.rs b/crates/backend/src/typegen/fn.rs new file mode 100644 index 00000000..08caaa29 --- /dev/null +++ b/crates/backend/src/typegen/fn.rs @@ -0,0 +1,104 @@ +use convert_case::{Case, Casing}; +use quote::ToTokens; + +use super::{ty_to_ts_type, ToTypeDef, TypeDef}; +use crate::{CallbackArg, FnKind, NapiFn}; + +impl ToTypeDef for NapiFn { + fn to_type_def(&self) -> TypeDef { + let def = format!( + r#"{prefix} {name}({args}){ret}"#, + prefix = self.gen_ts_func_prefix(), + name = &self.js_name, + args = self.gen_ts_func_args(), + ret = self.gen_ts_func_ret(), + ); + + TypeDef { + kind: "fn".to_owned(), + name: self.js_name.clone(), + def, + } + } +} + +fn gen_callback_type(callback: &CallbackArg) -> String { + format!( + "({args}) => {ret}", + args = &callback + .args + .iter() + .enumerate() + .map(|(i, arg)| { format!("arg{}: {}", i, ty_to_ts_type(arg)) }) + .collect::>() + .join(", "), + ret = match &callback.ret { + Some(ty) => ty_to_ts_type(ty), + None => "void".to_owned(), + } + ) +} + +impl NapiFn { + fn gen_ts_func_args(&self) -> String { + self + .args + .iter() + .filter_map(|arg| match arg { + crate::NapiFnArgKind::PatType(path) => { + if path.ty.to_token_stream().to_string() == "Env" { + return None; + } + let mut arg = path.pat.to_token_stream().to_string().to_case(Case::Camel); + arg.push_str(": "); + arg.push_str(&ty_to_ts_type(&path.ty)); + + Some(arg) + } + crate::NapiFnArgKind::Callback(cb) => { + let mut arg = cb.pat.to_token_stream().to_string().to_case(Case::Camel); + arg.push_str(": "); + arg.push_str(&gen_callback_type(cb)); + + Some(arg) + } + }) + .collect::>() + .join(", ") + } + + fn gen_ts_func_prefix(&self) -> &'static str { + if self.parent.is_some() { + match self.kind { + crate::FnKind::Normal => match self.fn_self { + Some(_) => "", + None => "static", + }, + crate::FnKind::Constructor => "constructor", + crate::FnKind::Getter => "get", + crate::FnKind::Setter => "set", + } + } else { + "export function" + } + } + + fn gen_ts_func_ret(&self) -> String { + match self.kind { + FnKind::Constructor | FnKind::Setter => "".to_owned(), + _ => { + let ret = if let Some(ref ret) = self.ret { + ty_to_ts_type(ret) + } else { + "void".to_owned() + }; + + if self.is_async { + format!(": Promise<{}>", ret) + } else { + format!(": {}", ret) + } + } + } + } +} diff --git a/crates/backend/src/typegen/struct.rs b/crates/backend/src/typegen/struct.rs new file mode 100644 index 00000000..406636fd --- /dev/null +++ b/crates/backend/src/typegen/struct.rs @@ -0,0 +1,59 @@ +use super::{ToTypeDef, TypeDef}; +use crate::{ty_to_ts_type, NapiImpl, NapiStruct}; + +impl ToTypeDef for NapiStruct { + fn to_type_def(&self) -> TypeDef { + TypeDef { + kind: "struct".to_owned(), + name: self.js_name.to_owned(), + def: self.gen_ts_class_fields(), + } + } +} + +impl ToTypeDef for NapiImpl { + fn to_type_def(&self) -> TypeDef { + TypeDef { + kind: "impl".to_owned(), + name: self.js_name.to_owned(), + def: self + .items + .iter() + .map(|f| f.to_type_def().def) + .collect::>() + .join("\\n"), + } + } +} + +impl NapiStruct { + fn gen_ts_class_fields(&self) -> String { + let mut ctor_args = vec![]; + let def = self + .fields + .iter() + .filter(|f| f.getter) + .map(|f| { + let mut field_str = String::from(""); + + if !f.setter { + field_str.push_str("readonly ") + } + let arg = format!("{}: {}", &f.js_name, ty_to_ts_type(&f.ty)); + if self.gen_default_ctor { + ctor_args.push(arg.clone()); + } + field_str.push_str(&arg); + + field_str + }) + .collect::>() + .join("\\n"); + + if self.gen_default_ctor { + format!("{}\\nconstructor({})", def, ctor_args.join(", ")) + } else { + def + } + } +} diff --git a/build/Cargo.toml b/crates/build/Cargo.toml similarity index 100% rename from build/Cargo.toml rename to crates/build/Cargo.toml diff --git a/build/README.md b/crates/build/README.md similarity index 100% rename from build/README.md rename to crates/build/README.md diff --git a/build/src/lib.rs b/crates/build/src/lib.rs similarity index 100% rename from build/src/lib.rs rename to crates/build/src/lib.rs diff --git a/build/src/libs/node-arm64.lib b/crates/build/src/libs/node-arm64.lib similarity index 100% rename from build/src/libs/node-arm64.lib rename to crates/build/src/libs/node-arm64.lib diff --git a/build/src/libs/node-x64.lib b/crates/build/src/libs/node-x64.lib similarity index 100% rename from build/src/libs/node-x64.lib rename to crates/build/src/libs/node-x64.lib diff --git a/build/src/libs/node-x86.lib b/crates/build/src/libs/node-x86.lib similarity index 100% rename from build/src/libs/node-x86.lib rename to crates/build/src/libs/node-x86.lib diff --git a/build/src/macos.rs b/crates/build/src/macos.rs similarity index 100% rename from build/src/macos.rs rename to crates/build/src/macos.rs diff --git a/build/src/windows.rs b/crates/build/src/windows.rs similarity index 100% rename from build/src/windows.rs rename to crates/build/src/windows.rs diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml new file mode 100644 index 00000000..f102d40c --- /dev/null +++ b/crates/macro/Cargo.toml @@ -0,0 +1,27 @@ +[package] +authors = ["LongYinan ", "Forehalo "] +description = "N-API procedural macros" +edition = "2018" +keywords = ["NodeJS", "FFI", "NAPI", "n-api"] +license = "MIT" +name = "napi-derive" +readme = "README.md" +repository = "https://github.com/napi-rs/napi-rs" +version = "2.0.0-alpha.0" + +[features] +compat-mode = [] +default = ["compat-mode", "full"] +full = ["type-def", "strict"] +strict = ["napi-derive-backend/strict"] +type-def = ["napi-derive-backend/type-def"] + +[dependencies] +convert_case = "0.4" +napi-derive-backend = {path = "../backend"} +proc-macro2 = "1.0" +quote = "1.0" +syn = {version = "1.0", features = ["fold", "full", "extra-traits"]} + +[lib] +proc-macro = true diff --git a/crates/macro/README.md b/crates/macro/README.md new file mode 100644 index 00000000..a0614b72 --- /dev/null +++ b/crates/macro/README.md @@ -0,0 +1,29 @@ +# napi-derive + + + + +chat + + +Checkout more examples in [examples](../../examples) folder + +```rust +#[macro_use] +extern crate napi_derive; +use napi::bindgen_prelude::*; + +#[napi] +fn fibonacci(n: u32) -> u32 { + match n { + 1 | 2 => 1, + _ => fibonacci_native(n - 1) + fibonacci_native(n - 2), + } +} + +#[napi] +fn get_cwd Result<()>>(callback: T) { + callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap(); +} +``` diff --git a/crates/macro/src/compat_macro.rs b/crates/macro/src/compat_macro.rs new file mode 100644 index 00000000..40a1945f --- /dev/null +++ b/crates/macro/src/compat_macro.rs @@ -0,0 +1,110 @@ +use proc_macro2::{Ident, Literal, TokenStream}; +use quote::{format_ident, quote}; +use syn::fold::{fold_fn_arg, fold_signature, Fold}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{Block, FnArg, Signature, Token, Visibility}; + +pub struct ArgLength { + pub length: Literal, +} + +impl Parse for ArgLength { + fn parse(input: ParseStream) -> Result { + let vars = Punctuated::::parse_terminated(input)?; + Ok(ArgLength { + length: vars + .first() + .cloned() + .unwrap_or_else(|| Literal::usize_unsuffixed(0)), + }) + } +} + +pub struct JsFunction { + pub args: Vec, + pub name: Option, + pub signature: Option, + pub signature_raw: Option, + pub block: Vec, + pub visibility: Visibility, +} + +impl JsFunction { + pub fn new() -> Self { + JsFunction { + args: vec![], + name: None, + signature: None, + signature_raw: None, + visibility: Visibility::Inherited, + block: vec![], + } + } +} + +impl Fold for JsFunction { + fn fold_fn_arg(&mut self, arg: FnArg) -> FnArg { + self.args.push(arg.clone()); + fold_fn_arg(self, arg) + } + + fn fold_signature(&mut self, signature: Signature) -> Signature { + self.name = Some(format_ident!("{}", signature.ident)); + let mut new_signature = signature.clone(); + new_signature.ident = format_ident!("_generated_{}_generated_", signature.ident); + self.signature = Some(new_signature); + self.signature_raw = Some(signature.clone()); + fold_signature(self, signature) + } + + fn fold_visibility(&mut self, v: Visibility) -> Visibility { + self.visibility = v.clone(); + v + } + + fn fold_block(&mut self, node: Block) -> Block { + self.block.push(node.clone()); + node + } +} +pub enum FunctionKind { + Contextless, + JsFunction, +} + +pub fn get_execute_js_code(new_fn_name: Ident, function_kind: FunctionKind) -> TokenStream { + let return_token_stream = match function_kind { + FunctionKind::Contextless => { + quote! { + Ok(Some(v)) => unsafe { v.raw() }, + Ok(None) => ptr::null_mut(), + } + } + FunctionKind::JsFunction => { + quote! { + Ok(v) => unsafe { v.raw() }, + } + } + }; + quote! { + match panic::catch_unwind(AssertUnwindSafe(move || #new_fn_name(ctx))).map_err(|e| { + let message = { + if let Some(string) = e.downcast_ref::() { + string.clone() + } else if let Some(string) = e.downcast_ref::<&str>() { + string.to_string() + } else { + format!("panic from Rust code: {:?}", e) + } + }; + Error::from_reason(message) + }).and_then(|v| v) { + #return_token_stream + Err(e) => { + unsafe { napi::JsError::from(e).throw_into(raw_env) }; + ptr::null_mut() + } + } + } +} diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs new file mode 100644 index 00000000..b0fb9454 --- /dev/null +++ b/crates/macro/src/lib.rs @@ -0,0 +1,209 @@ +#[cfg(feature = "compat-mode")] +mod compat_macro; +mod parser; + +#[macro_use] +extern crate syn; +#[macro_use] +extern crate napi_derive_backend; +#[macro_use] +extern crate quote; +use napi_derive_backend::{BindgenResult, TryToTokens}; + +#[cfg(feature = "type-def")] +use napi_derive_backend::{ToTypeDef, TypeDef}; +use parser::ParseNapi; +use proc_macro::TokenStream as RawStream; +use proc_macro2::TokenStream; +use std::env; +#[cfg(feature = "type-def")] +use std::{ + fs, + io::{BufWriter, Result as IOResult, Write}, +}; +#[cfg(feature = "compat-mode")] +use syn::{fold::Fold, parse_macro_input, ItemFn}; + +/// ```ignore +/// #[napi] +/// fn test(ctx: CallContext, name: String) { +/// "hello" + name +/// } +/// ``` +#[proc_macro_attribute] +pub fn napi(attr: RawStream, input: RawStream) -> RawStream { + match expand(attr.into(), input.into()) { + Ok(tokens) => { + if env::var("DEBUG_GENERATED_CODE").is_ok() { + println!("{}", tokens.to_string()); + } + tokens.into() + } + Err(diagnostic) => { + println!("`napi` macro expand failed."); + + (quote! { #diagnostic }).into() + } + } +} + +fn expand(attr: TokenStream, input: TokenStream) -> BindgenResult { + let mut item = syn::parse2::(input)?; + let opts = syn::parse2(attr)?; + + let mut tokens = proc_macro2::TokenStream::new(); + + let napi = item.parse_napi(&mut tokens, opts)?; + napi.try_to_tokens(&mut tokens)?; + + #[cfg(feature = "type-def")] + if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") { + if let Err(e) = output_type_def(type_def_file, napi.to_type_def()) { + println!("Failed to write type def file: {:?}", e); + }; + } + + Ok(tokens) +} + +#[cfg(feature = "type-def")] +fn output_type_def(type_def_file: String, type_def: TypeDef) -> IOResult<()> { + let file = fs::OpenOptions::new() + .append(true) + .create(true) + .open(type_def_file)?; + + let mut writer = BufWriter::::new(file); + writer.write_all(type_def.to_string().as_bytes())?; + writer.write_all("\n".as_bytes()) +} + +#[cfg(feature = "compat-mode")] +#[proc_macro_attribute] +pub fn contextless_function(_attr: RawStream, input: RawStream) -> RawStream { + let input = parse_macro_input!(input as ItemFn); + let mut js_fn = compat_macro::JsFunction::new(); + js_fn.fold_item_fn(input); + let fn_name = js_fn.name.unwrap(); + let fn_block = js_fn.block; + let signature = js_fn.signature.unwrap(); + let visibility = js_fn.visibility; + let new_fn_name = signature.ident.clone(); + let execute_js_function = + compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::Contextless); + + let expanded = quote! { + #[inline(always)] + #signature #(#fn_block)* + + #visibility extern "C" fn #fn_name( + raw_env: napi::sys::napi_env, + cb_info: napi::sys::napi_callback_info, + ) -> napi::sys::napi_value { + use std::ptr; + use std::panic::{self, AssertUnwindSafe}; + use std::ffi::CString; + use napi::{Env, NapiValue, NapiRaw, Error, Status}; + + let ctx = unsafe { Env::from_raw(raw_env) }; + #execute_js_function + } + }; + // Hand the output tokens back to the compiler + RawStream::from(expanded) +} + +#[cfg(feature = "compat-mode")] +#[proc_macro_attribute] +pub fn js_function(attr: RawStream, input: RawStream) -> RawStream { + let arg_len = parse_macro_input!(attr as compat_macro::ArgLength); + let arg_len_span = arg_len.length; + let input = parse_macro_input!(input as ItemFn); + let mut js_fn = compat_macro::JsFunction::new(); + js_fn.fold_item_fn(input); + let fn_name = js_fn.name.unwrap(); + let fn_block = js_fn.block; + let signature = js_fn.signature.unwrap(); + let visibility = js_fn.visibility; + let new_fn_name = signature.ident.clone(); + let execute_js_function = + compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::JsFunction); + let expanded = quote! { + #[inline(always)] + #signature #(#fn_block)* + + #visibility extern "C" fn #fn_name( + raw_env: napi::sys::napi_env, + cb_info: napi::sys::napi_callback_info, + ) -> napi::sys::napi_value { + use std::ptr; + use std::panic::{self, AssertUnwindSafe}; + use std::ffi::CString; + use napi::{Env, Error, Status, NapiValue, NapiRaw, CallContext}; + let mut argc = #arg_len_span as usize; + let mut raw_args: [napi::sys::napi_value; #arg_len_span] = [ptr::null_mut(); #arg_len_span]; + let mut raw_this = ptr::null_mut(); + + unsafe { + let status = napi::sys::napi_get_cb_info( + raw_env, + cb_info, + &mut argc, + raw_args.as_mut_ptr(), + &mut raw_this, + ptr::null_mut(), + ); + debug_assert!(Status::from(status) == Status::Ok, "napi_get_cb_info failed"); + } + + let mut env = unsafe { Env::from_raw(raw_env) }; + let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, argc); + #execute_js_function + } + }; + // Hand the output tokens back to the compiler + RawStream::from(expanded) +} + +#[cfg(feature = "compat-mode")] +#[proc_macro_attribute] +pub fn module_exports(_attr: RawStream, input: RawStream) -> RawStream { + let input = parse_macro_input!(input as ItemFn); + let mut js_fn = compat_macro::JsFunction::new(); + js_fn.fold_item_fn(input); + let fn_block = js_fn.block; + let fn_name = js_fn.name.unwrap(); + let signature = js_fn.signature_raw.unwrap(); + let args_len = js_fn.args.len(); + let call_expr = if args_len == 1 { + quote! { #fn_name(exports) } + } else if args_len == 2 { + quote! { #fn_name(exports, env) } + } else { + panic!("Arguments length of #[module_exports] function must be 1 or 2"); + }; + + let register = quote! { + #[napi::bindgen_prelude::ctor] + fn __napi__explicit_module_register() { + unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> { + use napi::{Env, JsObject, NapiValue}; + + let env = Env::from_raw(raw_env); + let exports = JsObject::from_raw_unchecked(raw_env, raw_exports); + + #call_expr + } + + napi::bindgen_prelude::register_module_exports(register) + } + }; + + (quote! { + #[inline] + #signature #(#fn_block)* + + #register + }) + .into() +} diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs new file mode 100644 index 00000000..c234b281 --- /dev/null +++ b/crates/macro/src/parser/attrs.rs @@ -0,0 +1,267 @@ +use napi_derive_backend::{bail_span, BindgenResult, Diagnostic}; +use proc_macro2::{Delimiter, Ident, Span, TokenTree}; +use std::{ + cell::{Cell, RefCell}, + collections::HashMap, +}; +use syn::spanned::Spanned; + +thread_local! { + static ATTRS: AttributeParseState = Default::default(); + static STRUCTS: StructParseState = Default::default(); +} + +#[derive(Default)] +struct StructParseState { + parsed: RefCell>, +} + +struct ParsedStruct { + js_name: String, + ctor_defined: bool, +} + +#[derive(Default)] +struct AttributeParseState { + parsed: Cell, + checks: Cell, +} + +/// Parsed attributes from a `#[napi(..)]`. +pub struct BindgenAttrs { + /// Whether `#[napi]` attribute exists + pub exists: bool, + /// List of parsed attributes + pub attrs: Vec<(Cell, BindgenAttr)>, + /// Span of original attribute + pub span: Span, +} + +// NOTE: borrowed from wasm-bindgen +// some of them may useless is #[napi] macro +macro_rules! attrgen { + ($mac:ident) => { + $mac! { + (js_name, JsName(Span, String, Span)), + (constructor, Constructor(Span)), + (getter, Getter(Span, Option)), + (setter, Setter(Span, Option)), + (readonly, Readonly(Span)), + (skip, Skip(Span)), + (strict, Strict(Span)), + + // impl later + // (inspectable, Inspectable(Span)), + // (typescript_custom_section, TypescriptCustomSection(Span)), + // (skip_typescript, SkipTypescript(Span)), + // (typescript_type, TypeScriptType(Span, String, Span)), + // (getter_with_clone, GetterWithClone(Span)), + + // For testing purposes only. + // (assert_no_shim, AssertNoShim(Span)), + } + }; +} + +macro_rules! methods { + ($(($name:ident, $variant:ident($($contents:tt)*)),)*) => { + $(methods!(@method $name, $variant($($contents)*));)* + + #[cfg(feature = "strict")] + fn check_used(self) -> Result<(), Diagnostic> { + // Account for the fact this method was called + ATTRS.with(|state| state.checks.set(state.checks.get() + 1)); + + let mut errors = Vec::new(); + for (used, attr) in self.attrs.iter() { + if used.get() { + continue + } + let span = match attr { + $(BindgenAttr::$variant(span, ..) => span,)* + }; + errors.push(Diagnostic::span_error(*span, "unused #[napi] attribute")); + } + Diagnostic::from_vec(errors) + } + + #[cfg(not(feature = "strict"))] + fn check_used(self) -> Result<(), Diagnostic> { + // Account for the fact this method was called + ATTRS.with(|state| state.checks.set(state.checks.get() + 1)); + Ok(()) + } + }; + + (@method $name:ident, $variant:ident(Span, String, Span)) => { + pub fn $name(&self) -> Option<(&str, Span)> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(_, s, span) => { + a.0.set(true); + Some((&s[..], *span)) + } + _ => None, + }) + .next() + } + }; + + (@method $name:ident, $variant:ident(Span, Vec, Vec)) => { + pub fn $name(&self) -> Option<(&[String], &[Span])> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(_, ss, spans) => { + a.0.set(true); + Some((&ss[..], &spans[..])) + } + _ => None, + }) + .next() + } + }; + + (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => { + #[allow(unused)] + pub fn $name(&self) -> Option<&$($other)*> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(_, s) => { + a.0.set(true); + Some(s) + } + _ => None, + }) + .next() + } + }; + + (@method $name:ident, $variant:ident($($other:tt)*)) => { + #[allow(unused)] + pub fn $name(&self) -> Option<&$($other)*> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(s) => { + a.0.set(true); + Some(s) + } + _ => None, + }) + .next() + } + }; +} + +impl BindgenAttrs { + /// Find and parse the napi attributes. + pub fn find(attrs: &mut Vec) -> Result { + let mut ret = BindgenAttrs::default(); + loop { + let napi_attr = attrs + .iter() + .enumerate() + .find(|&(_, m)| m.path.segments[0].ident == "napi"); + + let pos = match &napi_attr { + Some((pos, raw_attr)) => { + ret.exists = true; + ret.span = raw_attr.tokens.span(); + *pos + } + None => return Ok(ret), + }; + let attr = attrs.remove(pos); + let mut tts = attr.tokens.clone().into_iter(); + let group = match tts.next() { + Some(TokenTree::Group(d)) => d, + Some(_) => bail_span!(attr, "malformed #[napi] attribute"), + None => continue, + }; + if tts.next().is_some() { + bail_span!(attr, "malformed #[napi] attribute"); + } + if group.delimiter() != Delimiter::Parenthesis { + bail_span!(attr, "malformed #[napi] attribute"); + } + let mut attrs: BindgenAttrs = syn::parse2(group.stream())?; + ret.attrs.append(&mut attrs.attrs); + attrs.check_used()?; + } + } + + attrgen!(methods); +} + +impl Default for BindgenAttrs { + fn default() -> BindgenAttrs { + // Add 1 to the list of parsed attribute sets. We'll use this counter to + // sanity check that we call `check_used` an appropriate number of + // times. + ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1)); + BindgenAttrs { + span: Span::call_site(), + attrs: Vec::new(), + exists: false, + } + } +} + +macro_rules! gen_bindgen_attr { + ($( ($method:ident, $($variants:tt)*) ,)*) => { + /// The possible attributes in the `#[napi]`. + pub enum BindgenAttr { + $($($variants)*,)* + } + } +} + +attrgen!(gen_bindgen_attr); + +pub fn record_struct(ident: &Ident, js_name: String, opts: &BindgenAttrs) { + STRUCTS.with(|state| { + let struct_name = ident.to_string(); + + let mut map = state.parsed.borrow_mut(); + + map.insert( + struct_name, + ParsedStruct { + js_name, + ctor_defined: opts.constructor().is_some(), + }, + ); + }); +} + +pub fn check_recorded_struct_for_impl(ident: &Ident, opts: &BindgenAttrs) -> BindgenResult { + STRUCTS.with(|state| { + let struct_name = ident.to_string(); + + let mut map = state.parsed.borrow_mut(); + if let Some(parsed) = map.get_mut(&struct_name) { + if opts.constructor().is_some() { + if parsed.ctor_defined { + bail_span!( + ident, + "Constructor has already been defined for struct `{}`", + &struct_name + ); + } else { + parsed.ctor_defined = true; + } + } + + Ok(parsed.js_name.clone()) + } else { + bail_span!( + ident, + "Did not find struct `{}` parsed before expand #[napi] for impl", + &struct_name, + ) + } + }) +} diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs new file mode 100644 index 00000000..0326ac55 --- /dev/null +++ b/crates/macro/src/parser/mod.rs @@ -0,0 +1,868 @@ +#[macro_use] +pub mod attrs; + +use std::cell::Cell; +use std::collections::HashMap; +use std::str::Chars; + +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, +}; +use proc_macro2::{Ident, TokenStream, TokenTree}; +use quote::ToTokens; +use syn::parse::{Parse, ParseStream, Result as SynResult}; +use syn::{Attribute, Signature, Visibility}; + +use crate::parser::attrs::{check_recorded_struct_for_impl, record_struct}; + +struct AnyIdent(Ident); + +impl Parse for AnyIdent { + fn parse(input: ParseStream) -> SynResult { + input.step(|cursor| match cursor.ident() { + Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)), + None => Err(cursor.error("expected an identifier")), + }) + } +} + +impl Parse for BindgenAttrs { + fn parse(input: ParseStream) -> SynResult { + let mut attrs = BindgenAttrs::default(); + if input.is_empty() { + return Ok(attrs); + } + + let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?; + attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect(); + Ok(attrs) + } +} + +impl Parse for BindgenAttr { + fn parse(input: ParseStream) -> SynResult { + let original = input.fork(); + let attr: AnyIdent = input.parse()?; + let attr = attr.0; + let attr_span = attr.span(); + let attr_string = attr.to_string(); + let raw_attr_string = format!("r#{}", attr_string); + + macro_rules! parsers { + ($(($name:ident, $($contents:tt)*),)*) => { + $( + if attr_string == stringify!($name) || raw_attr_string == stringify!($name) { + parsers!( + @parser + $($contents)* + ); + } + )* + }; + + (@parser $variant:ident(Span)) => ({ + return Ok(BindgenAttr::$variant(attr_span)); + }); + + (@parser $variant:ident(Span, Ident)) => ({ + input.parse::()?; + let ident = input.parse::()?.0; + return Ok(BindgenAttr::$variant(attr_span, ident)) + }); + + (@parser $variant:ident(Span, Option)) => ({ + if input.parse::().is_ok() { + let ident = input.parse::()?.0; + return Ok(BindgenAttr::$variant(attr_span, Some(ident))) + } else { + return Ok(BindgenAttr::$variant(attr_span, None)); + } + }); + + (@parser $variant:ident(Span, syn::Path)) => ({ + input.parse::()?; + return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); + }); + + (@parser $variant:ident(Span, syn::Expr)) => ({ + input.parse::()?; + return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); + }); + + (@parser $variant:ident(Span, String, Span)) => ({ + input.parse::()?; + let (val, span) = match input.parse::() { + Ok(str) => (str.value(), str.span()), + Err(_) => { + let ident = input.parse::()?.0; + (ident.to_string(), ident.span()) + } + }; + return Ok(BindgenAttr::$variant(attr_span, val, span)) + }); + + (@parser $variant:ident(Span, Vec, Vec)) => ({ + input.parse::()?; + let (vals, spans) = match input.parse::() { + Ok(exprs) => { + let mut vals = vec![]; + let mut spans = vec![]; + + for expr in exprs.elems.iter() { + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(ref str), + .. + }) = expr { + vals.push(str.value()); + spans.push(str.span()); + } else { + return Err(syn::Error::new(expr.span(), "expected string literals")); + } + } + + (vals, spans) + }, + Err(_) => { + let ident = input.parse::()?.0; + (vec![ident.to_string()], vec![ident.span()]) + } + }; + return Ok(BindgenAttr::$variant(attr_span, vals, spans)) + }); + } + + attrgen!(parsers); + + Err(original.error("unknown attribute")) + } +} + +pub trait ConvertToAST { + fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult; +} + +pub trait ParseNapi { + fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult; +} + +fn get_ty(mut ty: &syn::Type) -> &syn::Type { + while let syn::Type::Group(g) = ty { + ty = &g.elem; + } + + ty +} + +fn replace_self(ty: syn::Type, self_ty: Option<&Ident>) -> syn::Type { + let self_ty = match self_ty { + Some(i) => i, + None => return ty, + }; + let path = match get_ty(&ty) { + syn::Type::Path(syn::TypePath { qself: None, path }) => path.clone(), + other => return other.clone(), + }; + let new_path = if path.segments.len() == 1 && path.segments[0].ident == "Self" { + self_ty.clone().into() + } else { + path + }; + syn::Type::Path(syn::TypePath { + qself: None, + path: new_path, + }) +} + +/// Extracts the last ident from the path +fn extract_path_ident(path: &syn::Path) -> BindgenResult { + for segment in path.segments.iter() { + match segment.arguments { + syn::PathArguments::None => {} + _ => bail_span!(path, "paths with type parameters are not supported yet"), + } + } + + match path.segments.last() { + Some(value) => Ok(value.ident.clone()), + None => { + bail_span!(path, "empty idents are not supported"); + } + } +} + +fn extract_fn_types( + arguments: &syn::PathArguments, +) -> BindgenResult<(Vec, Option)> { + match arguments { + // + syn::PathArguments::None => Ok((vec![], None)), + syn::PathArguments::AngleBracketed(_) => { + bail_span!(arguments, "use parentheses for napi callback trait") + } + syn::PathArguments::Parenthesized(arguments) => { + let args = arguments.inputs.iter().cloned().collect::>(); + + let ret = match &arguments.output { + syn::ReturnType::Type(_, ret_ty) => { + let ret_ty = &**ret_ty; + match ret_ty { + syn::Type::Path(syn::TypePath { + qself: None, + ref path, + }) if path.segments.len() == 1 => { + let segment = path.segments.first().unwrap(); + + if segment.ident != "Result" { + bail_span!(ret_ty, "The return type of callback can only be `Result`"); + } else { + match &segment.arguments { + syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + args, + .. + }) => { + // fast test + if args.to_token_stream().to_string() == "()" { + None + } else { + let ok_arg = args.first().unwrap(); + match ok_arg { + syn::GenericArgument::Type(ty) => Some(ty.clone()), + _ => bail_span!(ok_arg, "unsupported generic type"), + } + } + } + _ => { + bail_span!(segment, "Too many arguments") + } + } + } + } + _ => bail_span!(ret_ty, "The return type of callback can only be `Result`"), + } + } + _ => { + bail_span!( + arguments, + "The return type of callback can only be `Result`. Try with `Result<()>`" + ); + } + }; + + Ok((args, ret)) + } + } +} + +fn get_expr(mut expr: &syn::Expr) -> &syn::Expr { + while let syn::Expr::Group(g) = expr { + expr = &g.expr; + } + + expr +} + +/// Extract the documentation comments from a Vec of attributes +fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec { + attrs + .iter() + .filter_map(|a| { + // if the path segments include an ident of "doc" we know this + // this is a doc comment + if a.path.segments.iter().any(|s| s.ident == "doc") { + Some( + // We want to filter out any Puncts so just grab the Literals + a.tokens.clone().into_iter().filter_map(|t| match t { + TokenTree::Literal(lit) => { + let quoted = lit.to_string(); + Some(try_unescape("ed).unwrap_or(quoted)) + } + _ => None, + }), + ) + } else { + None + } + }) + //Fold up the [[String]] iter we created into Vec + .fold(vec![], |mut acc, a| { + acc.extend(a); + acc + }) +} + +// Unescapes 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()); + } + let mut result = String::with_capacity(s.len()); + let mut chars = s.chars(); + for i in 0.. { + let c = match chars.next() { + Some(c) => c, + None => { + if result.ends_with('"') { + result.pop(); + } + return Some(result); + } + }; + if i == 0 && c == '"' { + // ignore it + } else if c == '\\' { + let c = chars.next()?; + match c { + 't' => result.push('\t'), + 'r' => result.push('\r'), + 'n' => result.push('\n'), + '\\' | '\'' | '"' => result.push(c), + 'u' => { + if chars.next() != Some('{') { + return None; + } + let (c, next) = unescape_unicode(&mut chars)?; + result.push(c); + if next != '}' { + return None; + } + } + _ => return None, + } + } else { + result.push(c); + } + } + None +} + +fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> { + let mut value = 0; + for i in 0..7 { + let c = chars.next()?; + let num = match c { + '0'..='9' => c as u32 - '0' as u32, + 'a'..='f' => c as u32 - 'a' as u32, + 'A'..='F' => c as u32 - 'A' as u32, + _ => { + if i == 0 { + return None; + } + + if i == 0 { + return None; + } + let decoded = char::from_u32(value)?; + return Some((decoded, c)); + } + }; + + if i >= 6 { + return None; + } + value = (value << 4) | num; + } + None +} + +fn extract_fn_closure_generics( + generics: &syn::Generics, +) -> BindgenResult> { + let mut errors = vec![]; + + let mut map = HashMap::default(); + if generics.params.is_empty() { + return Ok(map); + } + + if let Some(where_clause) = &generics.where_clause { + for prediction in where_clause.predicates.iter() { + match prediction { + syn::WherePredicate::Type(syn::PredicateType { + bounded_ty, bounds, .. + }) => { + for bound in bounds { + match bound { + syn::TypeParamBound::Trait(t) => { + for segment in t.path.segments.iter() { + match segment.ident.to_string().as_str() { + "Fn" | "FnOnce" | "FnMut" => { + map.insert( + bounded_ty.to_token_stream().to_string(), + segment.arguments.clone(), + ); + } + _ => {} + }; + } + } + syn::TypeParamBound::Lifetime(lifetime) => { + if lifetime.ident != "static" { + errors.push(err_span!( + bound, + "only 'static is supported in lifetime bound for fn arguments" + )); + } + } + } + } + } + _ => errors.push(err_span! { + prediction, + "unsupported where clause prediction in napi" + }), + }; + } + } + + for param in generics.params.iter() { + match param { + syn::GenericParam::Type(syn::TypeParam { ident, bounds, .. }) => { + for bound in bounds { + match bound { + syn::TypeParamBound::Trait(t) => { + for segment in t.path.segments.iter() { + match segment.ident.to_string().as_str() { + "Fn" | "FnOnce" | "FnMut" => { + map.insert(ident.to_string(), segment.arguments.clone()); + } + _ => {} + }; + } + } + syn::TypeParamBound::Lifetime(lifetime) => { + if lifetime.ident != "static" { + errors.push(err_span!( + bound, + "only 'static is supported in lifetime bound for fn arguments" + )); + } + } + } + } + } + _ => { + errors.push(err_span!(param, "unsupported napi generic param for fn")); + } + } + } + + Diagnostic::from_vec(errors).and(Ok(map)) +} + +fn napi_fn_from_decl( + sig: Signature, + opts: &BindgenAttrs, + attrs: Vec, + vis: Visibility, + parent: Option<&Ident>, +) -> BindgenResult { + let mut errors = vec![]; + + let syn::Signature { + ident, + asyncness, + inputs, + output, + generics, + .. + } = sig; + + let mut fn_self = None; + let callback_traits = extract_fn_closure_generics(&generics)?; + + let args = inputs + .into_iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(mut p) => { + let ty_str = p.ty.to_token_stream().to_string(); + if let Some(path_arguments) = callback_traits.get(&ty_str) { + match extract_fn_types(path_arguments) { + Ok((fn_args, fn_ret)) => Some(NapiFnArgKind::Callback(Box::new(CallbackArg { + pat: p.pat, + args: fn_args, + ret: fn_ret, + }))), + Err(e) => { + errors.push(e); + None + } + } + } else { + let ty = replace_self(*p.ty, parent); + p.ty = Box::new(ty); + Some(NapiFnArgKind::PatType(Box::new(p))) + } + } + syn::FnArg::Receiver(r) => { + if parent.is_some() { + assert!(fn_self.is_none()); + if r.reference.is_none() { + errors.push(err_span!( + r, + "The native methods can't move values from napi. Try `&self` or `&mut self` instead." + )); + } else if r.mutability.is_some() { + fn_self = Some(FnSelf::MutRef); + } else { + fn_self = Some(FnSelf::Ref); + } + } else { + errors.push(err_span!(r, "arguments cannot be `self`")); + } + None + } + }) + .collect::>(); + + let ret = match output { + syn::ReturnType::Default => None, + syn::ReturnType::Type(_, ty) => Some(replace_self(*ty, parent)), + }; + + Diagnostic::from_vec(errors).map(|_| { + let js_name = if let Some(prop_name) = opts.getter() { + if let Some(ident) = prop_name { + ident.to_string() + } else { + ident + .to_string() + .trim_start_matches("get_") + .to_case(Case::Camel) + } + } else if let Some(prop_name) = opts.setter() { + if let Some(ident) = prop_name { + ident.to_string() + } else { + ident + .to_string() + .trim_start_matches("set_") + .to_case(Case::Camel) + } + } else { + opts.js_name().map_or_else( + || ident.to_string().to_case(Case::Camel), + |(js_name, _)| js_name.to_owned(), + ) + }; + + NapiFn { + name: ident, + js_name, + args, + ret, + is_async: asyncness.is_some(), + vis, + kind: fn_kind(opts), + fn_self, + parent: parent.cloned(), + attrs, + strict: opts.strict().is_some(), + } + }) +} + +impl ParseNapi for syn::Item { + fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { + match self { + syn::Item::Fn(f) => f.parse_napi(tokens, opts), + 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), + _ => bail_span!( + self, + "#[napi] can only be applied to a function, struct, enum or impl." + ), + } + } +} + +impl ParseNapi for syn::ItemFn { + fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { + self.to_tokens(tokens); + self.convert_to_ast(opts) + } +} +impl ParseNapi for syn::ItemStruct { + fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { + let napi = self.convert_to_ast(opts); + self.to_tokens(tokens); + + napi + } +} +impl ParseNapi for syn::ItemImpl { + fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult { + // #[napi] macro will be remove from impl items after converted to ast + let napi = self.convert_to_ast(opts); + self.to_tokens(tokens); + + napi + } +} + +impl ParseNapi for syn::ItemEnum { + 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; + + if opts.getter().is_some() { + kind = FnKind::Getter; + } + + if opts.setter().is_some() { + kind = FnKind::Setter; + } + + if opts.constructor().is_some() { + kind = FnKind::Constructor; + } + + kind +} + +impl ConvertToAST for syn::ItemFn { + fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult { + let func = napi_fn_from_decl( + self.sig.clone(), + &opts, + self.attrs.clone(), + self.vis.clone(), + None, + )?; + + Ok(Napi { + comments: vec![], + item: NapiItem::Fn(func), + }) + } +} + +impl ConvertToAST for syn::ItemStruct { + fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult { + let vis = self.vis.clone(); + let struct_name = self.ident.clone(); + let js_name = opts.js_name().map_or_else( + || self.ident.to_string().to_case(Case::Pascal), + |(js_name, _)| js_name.to_owned(), + ); + let mut fields = vec![]; + let mut is_tuple = false; + + for (i, field) in self.fields.iter_mut().enumerate() { + match field.vis { + syn::Visibility::Public(..) => {} + _ => continue, + } + + let field_opts = BindgenAttrs::find(&mut field.attrs)?; + + 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()), + syn::Member::Named(ident.clone()), + ), + None => { + is_tuple = true; + (i.to_string(), syn::Member::Unnamed(i.into())) + } + }; + + let ignored = field_opts.skip().is_some(); + let readonly = field_opts.readonly().is_some(); + + fields.push(NapiStructField { + name, + js_name, + ty: field.ty.clone(), + getter: !ignored, + setter: !(ignored || readonly), + }) + } + + record_struct(&struct_name, js_name.clone(), &opts); + + Ok(Napi { + comments: vec![], + item: NapiItem::Struct(NapiStruct { + js_name, + name: struct_name, + vis, + fields, + is_tuple, + gen_default_ctor: opts.constructor().is_some(), + }), + }) + } +} + +impl ConvertToAST for syn::ItemImpl { + fn convert_to_ast(&mut self, _opts: BindgenAttrs) -> BindgenResult { + let struct_name = match get_ty(&self.self_ty) { + syn::Type::Path(syn::TypePath { + ref path, + qself: None, + }) => path, + _ => { + bail_span!(self.self_ty, "unsupported self type in #[napi] impl") + } + }; + + let struct_name = extract_path_ident(struct_name)?; + + let mut struct_js_name = struct_name.to_string(); + let mut items = vec![]; + + for item in self.items.iter_mut() { + let method = match item { + syn::ImplItem::Method(m) => m, + _ => { + bail_span!(item, "unsupported impl item in #[napi]") + } + }; + let opts = BindgenAttrs::find(&mut method.attrs)?; + + // it'd better only care methods decorated with `#[napi]` attribute + if !opts.exists { + continue; + } + + if opts.constructor().is_some() { + struct_js_name = check_recorded_struct_for_impl(&struct_name, &opts)?; + } + + let vis = method.vis.clone(); + + match &vis { + Visibility::Public(_) => {} + _ => { + bail_span!(method.sig.ident, "only pub method supported by #[napi].",); + } + } + + let func = napi_fn_from_decl( + method.sig.clone(), + &opts, + method.attrs.clone(), + vis, + Some(&struct_name), + )?; + + items.push(func); + } + + Ok(Napi { + comments: vec![], + item: NapiItem::Impl(NapiImpl { + name: struct_name, + js_name: struct_js_name, + items, + }), + }) + } +} + +impl ConvertToAST for syn::ItemEnum { + fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult { + match self.vis { + Visibility::Public(_) => {} + _ => bail_span!(self, "only public enum allowed"), + } + if self.variants.is_empty() { + bail_span!(self, "cannot export empty enum to JS"); + } + + self.attrs.push(Attribute { + pound_token: Default::default(), + style: syn::AttrStyle::Outer, + bracket_token: Default::default(), + path: syn::parse_quote! { derive }, + tokens: quote! { (Copy, Clone) }, + }); + + let js_name = opts + .js_name() + .map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string()); + + let mut last_variant_val: i32 = -1; + let variants = self + .variants + .iter() + .map(|v| { + match v.fields { + syn::Fields::Unit => {} + _ => bail_span!(v.fields, "Structured enum is not supported in #[napi]"), + }; + + let val = match &v.discriminant { + Some((_, expr)) => { + let mut symbol = 1; + let mut inner_expr = get_expr(expr); + if let syn::Expr::Unary(syn::ExprUnary { + attrs: _, + op: syn::UnOp::Neg(_), + expr, + }) = inner_expr + { + symbol = -1; + inner_expr = expr; + } + + match inner_expr { + syn::Expr::Lit(syn::ExprLit { + attrs: _, + lit: syn::Lit::Int(int_lit), + }) => match int_lit.base10_digits().parse::() { + Ok(v) => symbol * v, + Err(_) => { + bail_span!( + int_lit, + "enums with #[wasm_bindgen] can only support \ + numbers that can be represented as i32", + ); + } + }, + _ => bail_span!( + expr, + "enums with #[wasm_bindgen] may only have \ + number literal values", + ), + } + } + None => last_variant_val + 1, + }; + + last_variant_val = val; + let comments = extract_doc_comments(&v.attrs); + Ok(NapiEnumVariant { + name: v.ident.clone(), + val, + comments, + }) + }) + .collect::>>()?; + + let comments = extract_doc_comments(&self.attrs); + + Ok(Napi { + comments, + item: NapiItem::Enum(NapiEnum { + name: self.ident.clone(), + js_name, + variants, + }), + }) + } +} diff --git a/napi/Cargo.toml b/crates/napi/Cargo.toml similarity index 91% rename from napi/Cargo.toml rename to crates/napi/Cargo.toml index 827db351..e0125ef0 100644 --- a/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -7,10 +7,11 @@ license = "MIT" name = "napi" readme = "README.md" repository = "https://github.com/napi-rs/napi-rs" -version = "1.7.7" +version = "2.0.0-alpha.0" [features] -default = ["napi3"] # for most Node.js users +compat-mode = [] +default = ["napi3", "compat-mode"] # for most Node.js users latin1 = ["encoding_rs"] napi1 = [] napi2 = ["napi1"] @@ -24,6 +25,7 @@ serde-json = ["serde", "serde_json"] tokio_rt = ["tokio", "once_cell", "napi4"] [dependencies] +ctor = "0.1" napi-sys = {version = "1", path = "../sys"} [target.'cfg(windows)'.dependencies] diff --git a/crates/napi/README.md b/crates/napi/README.md new file mode 100644 index 00000000..ef1ef806 --- /dev/null +++ b/crates/napi/README.md @@ -0,0 +1,228 @@ +# napi-rs + +Stake to support us + +chat + + +> This project was initialized from [xray](https://github.com/atom/xray) + +A minimal library for building compiled `Node.js` add-ons in `Rust`. + +

+ + + +

+ +## Ecosystem + +

+ + Prisma + +   +   + + swc + +   +   + + Parcel + +   + + next.js +   + nextjs.svg + +

+ +## Platform Support + +![Lint](https://github.com/napi-rs/napi-rs/workflows/Lint/badge.svg) +![Linux N-API@3](https://github.com/napi-rs/napi-rs/workflows/Linux%20N-API@3/badge.svg) +![Linux musl](https://github.com/napi-rs/napi-rs/workflows/Linux%20musl/badge.svg) +![macOS/Windows/Linux x64](https://github.com/napi-rs/napi-rs/workflows/macOS/Windows/Linux%20x64/badge.svg) +![Linux-aarch64](https://github.com/napi-rs/napi-rs/workflows/Linux-aarch64/badge.svg) +![Linux-armv7](https://github.com/napi-rs/napi-rs/workflows/Linux-armv7/badge.svg) +![macOS-Android](https://github.com/napi-rs/napi-rs/workflows/macOS-Android/badge.svg) +![Windows i686](https://github.com/napi-rs/napi-rs/workflows/Windows%20i686/badge.svg) +[![Windows arm64](https://github.com/napi-rs/napi-rs/actions/workflows/windows-arm.yml/badge.svg)](https://github.com/napi-rs/napi-rs/actions/workflows/windows-arm.yml) +[![FreeBSD](https://api.cirrus-ci.com/github/napi-rs/napi-rs.svg)](https://cirrus-ci.com/github/napi-rs/napi-rs?branch=main) + +| | node12 | node14 | node16 | +| --------------------- | ------ | ------ | ------ | +| Windows x64 | ✓ | ✓ | ✓ | +| Windows x86 | ✓ | ✓ | ✓ | +| Windows arm64 | ✓ | ✓ | ✓ | +| macOS x64 | ✓ | ✓ | ✓ | +| macOS aarch64 | ✓ | ✓ | ✓ | +| Linux x64 gnu | ✓ | ✓ | ✓ | +| Linux x64 musl | ✓ | ✓ | ✓ | +| Linux aarch64 gnu | ✓ | ✓ | ✓ | +| Linux aarch64 musl | ✓ | ✓ | ✓ | +| Linux arm gnueabihf | ✓ | ✓ | ✓ | +| Linux aarch64 android | ✓ | ✓ | ✓ | +| FreeBSD x64 | ✓ | ✓ | ✓ | + +This library depends on Node-API and requires `Node@10.0.0` or later. + +We already have some packages written by `napi-rs`: [node-rs](https://github.com/napi-rs/node-rs) + +One nice feature is that this crate allows you to build add-ons purely with the `Rust/JavaScript` toolchain and without involving `node-gyp`. + +## Taste + +> You can start from [package-template](https://github.com/napi-rs/package-template) to play with `napi-rs` + +### Define JavaScript functions + +```rust +#[macro_use] +extern crate napi; + +// import the preludes +use napi::bindgen_prelude::*; + +/// module registerion is done by the runtime, no need to explicitly do it now. +#[napi] +fn fibonacci(n: u32) -> u32 { + match n { + 1 | 2 => 1, + _ => fibonacci_native(n - 1) + fibonacci_native(n - 2), + } +} + +/// use `Fn`, `FnMut` or `FnOnce` traits to defined JavaScript callbacks +/// the return type of callbacks can only be `Result`. +#[napi] +fn get_cwd Result<()>>(callback: T) { + callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap(); +} + +/// or, define the callback signature in where clause +#[napi] +fn test_callback(callback: T) +where T: Fn(String) -> Result<()> +{} +``` + +Checkout more examples in [examples](./examples) folder + +## Building + +This repository is a `Cargo` crate. Any napi-based add-on should contain `Cargo.toml` to make it a Cargo crate. + +In your `Cargo.toml` you need to set the `crate-type` to `"cdylib"` so that cargo builds a C-style shared library that can be dynamically loaded by the Node executable. You'll also need to add this crate as a dependency. + +```toml +[package] +name = "awesome" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +napi = "2" +napi-derive = "2" + +[build-dependencies] +napi-build = "1" +``` + +And create `build.rs` in your own project: + +```rust +// build.rs +extern crate napi_build; + +fn main() { + napi_build::setup(); +} +``` + +So far, the `napi` build script has only been tested on `macOS` `Linux` `Windows x64 MSVC` and `FreeBSD`. + +Install the `@napi-rs/cli` to help you build your `Rust` codes and copy `Dynamic lib` file to `.node` file in case you can `require` it in your program. + +```js +{ + "package": "awesome-package", + "devDependencies": { + "@napi-rs/cli": "^1.0.0" + }, + "napi": { + "name": "jarvis" // <----------- Config the name of native addon, or the napi command will use the name of `Cargo.toml` for the binary file name. + }, + "scripts": { + "build": "napi build --release", + "build:debug": "napi build" + } +} +``` + +Then you can require your native binding: + +```js +require('./jarvis.node') +``` + +The `module_name` would be your `package` name in your `Cargo.toml`. + +`xxx => ./xxx.node` + +`xxx-yyy => ./xxx_yyy.node` + +You can also copy `Dynamic lib` file to an appointed location: + +```bash +napi build [--release] ./dll +napi build [--release] ./artifacts +``` + +There are [documents](./cli) which contains more details about the `@napi-rs/cli` usage. + +## Testing + +Because libraries that depend on this crate must be loaded into a Node executable in order to resolve symbols, all tests are written in JavaScript in the `test_module` subdirectory. + +To run tests: + +```sh +yarn build:test +yarn test +``` + +## Related projects + +- [neon](https://www.neon-bindings.com) +- [node-bindgen](https://github.com/infinyon/node-bindgen) + +## Features table + +| Rust Type | Node Type | [NAPI Version](https://nodejs.org/api/n-api.html#n_api_node_api_version_matrix) | Minimal Node version | +| ----------------------- | ---------------------- | ------------------------------------------------------------------------------- | -------------------- | +| u32 | Number | 1 | v8.0.0 | +| i32/i64 | Number | 1 | v8.0.0 | +| f64 | Number | 1 | v8.0.0 | +| bool | Boolean | 1 | v8.0.0 | +| String/&'a str | String | 1 | v8.0.0 | +| Latin1String | String | 1 | v8.0.0 | +| UTF16String | String | 1 | v8.0.0 | +| Object | Object | 1 | v8.0.0 | +| Array | Array | 1 | v8.0.0 | +| Vec | Array | 1 | v8.0.0 | +| Buffer | Buffer | 1 | v8.0.0 | +| Null | null | 1 | v8.0.0 | +| Undefined/() | undefined | 1 | v8.0.0 | +| Result<()> | Error | 1 | v8.0.0 | +| T: Fn(...) -> Result | function | 1 | v8.0.0 | +| (NOT YET) | global | 1 | v8.0.0 | +| (NOT YET) | Symbol | 1 | v8.0.0 | +| (NOT YET) | Promise | 1 | b8.5.0 | +| (NOT YET) | ArrayBuffer/TypedArray | 1 | v8.0.0 | +| (NOT YET) | threadsafe function | 4 | v10.6.0 | +| (NOT YET) | BigInt | 6 | v10.7.0 | diff --git a/napi/src/async_cleanup_hook.rs b/crates/napi/src/async_cleanup_hook.rs similarity index 100% rename from napi/src/async_cleanup_hook.rs rename to crates/napi/src/async_cleanup_hook.rs diff --git a/napi/src/async_work.rs b/crates/napi/src/async_work.rs similarity index 99% rename from napi/src/async_work.rs rename to crates/napi/src/async_work.rs index 31994af9..d53bacc7 100644 --- a/napi/src/async_work.rs +++ b/crates/napi/src/async_work.rs @@ -22,18 +22,15 @@ pub struct AsyncWorkPromise<'env> { } impl<'env> AsyncWorkPromise<'env> { - #[inline] pub fn promise_object(&self) -> JsObject { unsafe { JsObject::from_raw_unchecked(self.env.0, self.raw_promise) } } - #[inline] pub fn cancel(self) -> Result<()> { check_status!(unsafe { sys::napi_cancel_async_work(self.env.0, self.napi_async_work) }) } } -#[inline] pub fn run(env: &Env, task: T) -> Result> { let mut raw_resource = ptr::null_mut(); check_status!(unsafe { sys::napi_create_object(env.0, &mut raw_resource) })?; diff --git a/crates/napi/src/bindgen_runtime/callback_info.rs b/crates/napi/src/bindgen_runtime/callback_info.rs new file mode 100644 index 00000000..c71a2762 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/callback_info.rs @@ -0,0 +1,118 @@ +use crate::{bindgen_prelude::*, check_status, sys, Result}; +use std::{ffi::c_void, ptr}; + +pub struct CallbackInfo { + env: sys::napi_env, + this: sys::napi_value, + args: [sys::napi_value; N], +} + +impl CallbackInfo { + pub fn new( + env: sys::napi_env, + callback_info: sys::napi_callback_info, + required_argc: Option, + ) -> Result { + let mut this = ptr::null_mut(); + let mut args = [ptr::null_mut(); N]; + let mut argc = N; + + unsafe { + check_status!( + sys::napi_get_cb_info( + env, + callback_info, + &mut argc, + args.as_mut_ptr(), + &mut this, + ptr::null_mut(), + ), + "Failed to initialize napi function call." + )?; + }; + + if let Some(required_argc) = required_argc { + if required_argc > argc { + return Err(Error::new( + Status::InvalidArg, + format!( + "{} arguments required by received {}.", + required_argc, &argc + ), + )); + } + } + + Ok(Self { env, this, args }) + } + + pub fn get_arg(&self, index: usize) -> sys::napi_value { + *self + .args + .get(index) + .unwrap_or_else(|| panic!("index {} must < {}", index, N)) + } + + pub fn this(&self) -> sys::napi_value { + debug_assert!(!self.this.is_null()); + + self.this + } + + pub fn construct(&self, js_name: &str, obj: T) -> Result { + let obj = Box::new(obj); + let mut result = std::ptr::null_mut(); + let this = self.this(); + + unsafe { + check_status!( + sys::napi_wrap( + self.env, + this, + Box::into_raw(obj) as *mut std::ffi::c_void, + Some(raw_finalize_unchecked::), + ptr::null_mut(), + &mut result + ), + "Failed to initialize class `{}`", + js_name, + )?; + }; + + Ok(this) + } + + pub fn unwrap_borrow_mut(&mut self) -> Result<&'static mut T> + where + T: FromNapiMutRef + TypeName, + { + let mut wrapped_val: *mut c_void = std::ptr::null_mut(); + + unsafe { + check_status!( + sys::napi_unwrap(self.env, self.this, &mut wrapped_val), + "Failed to unwrap exclusive reference of `{}` type from napi value", + T::type_name(), + )?; + + Ok(&mut *(wrapped_val as *mut T)) + } + } + + pub fn unwrap_borrow(&mut self) -> Result<&'static T> + where + T: FromNapiRef + TypeName, + { + let mut wrapped_val: *mut c_void = std::ptr::null_mut(); + + unsafe { + check_status!( + sys::napi_unwrap(self.env, self.this, &mut wrapped_val), + "Failed to unwrap shared reference of `{}` type from napi value", + T::type_name(), + )?; + + Ok(&*(wrapped_val as *const T)) + } + } +} diff --git a/crates/napi/src/bindgen_runtime/env.rs b/crates/napi/src/bindgen_runtime/env.rs new file mode 100644 index 00000000..b7628670 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/env.rs @@ -0,0 +1,22 @@ +use crate::{sys, Result}; + +use super::{Array, Object}; + +#[repr(transparent)] +pub struct Env(sys::napi_env); + +impl From for Env { + fn from(raw_env: sys::napi_env) -> Env { + Env(raw_env) + } +} + +impl Env { + pub fn create_object(&self) -> Result { + Object::new(self.0) + } + + pub fn create_array(&self, len: u32) -> Result { + Array::new(self.0, len) + } +} diff --git a/crates/napi/src/bindgen_runtime/error.rs b/crates/napi/src/bindgen_runtime/error.rs new file mode 100644 index 00000000..03b0fb1e --- /dev/null +++ b/crates/napi/src/bindgen_runtime/error.rs @@ -0,0 +1,10 @@ +#[doc(hidden)] +#[macro_export] +macro_rules! check_status_or_throw { + ($env:expr, $code:expr, $($msg:tt)*) => { + if let Err(e) = $crate::check_status!($code, $($msg)*) { + $crate::JsError::from(e).throw_into($env); + return; + } + }; +} diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs new file mode 100644 index 00000000..7561c044 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -0,0 +1,145 @@ +use crate::{check_status, sys, Error, Result, Status, ValueType}; +use std::ptr; + +mod array; +mod boolean; +mod buffer; +mod nil; +mod number; +mod object; +mod string; + +pub use array::*; +pub use buffer::*; +pub use nil::*; +pub use object::*; +pub use string::*; + +#[cfg(feature = "latin1")] +pub use string::latin1_string::*; + +pub trait TypeName { + fn type_name() -> &'static str; +} + +pub trait ToNapiValue { + /// # Safety + /// + /// this function called to convert rust values to napi values + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result; +} + +pub trait FromNapiValue: Sized { + /// # Safety + /// + /// this function called to convert napi values to native rust values + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result; +} + +pub trait FromNapiRef { + /// # Safety + /// + /// this function called to convert napi values to native rust values + unsafe fn from_napi_ref(env: sys::napi_env, napi_val: sys::napi_value) -> Result<&'static Self>; +} + +pub trait FromNapiMutRef { + /// # Safety + /// + /// this function called to convert napi values to native rust values + unsafe fn from_napi_mut_ref( + env: sys::napi_env, + napi_val: sys::napi_value, + ) -> Result<&'static mut Self>; +} + +pub trait ValidateNapiValue: FromNapiValue + TypeName { + fn type_of() -> Vec { + vec![] + } + + /// # Safety + /// + /// this function called to validate whether napi value passed to rust is valid type + unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<()> { + let available_types = Self::type_of(); + if available_types.is_empty() { + return Ok(()); + } + + let mut result = -1; + check_status!( + sys::napi_typeof(env, napi_val, &mut result), + "Failed to detect napi value type", + )?; + + let received_type = ValueType::from(result); + if available_types.contains(&received_type) { + Ok(()) + } else { + Err(Error::new( + Status::InvalidArg, + if available_types.len() > 1 { + format!( + "Expect value to be one of {:?}, but received {}", + available_types, received_type + ) + } else { + format!( + "Expect value to be {}, but received {}", + available_types[0], received_type + ) + }, + )) + } + } +} + +impl TypeName for Option +where + T: TypeName, +{ + fn type_name() -> &'static str { + "Option" + } +} + +impl FromNapiValue for Option +where + T: FromNapiValue + TypeName, +{ + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let mut val_type = 0; + + check_status!( + sys::napi_typeof(env, napi_val, &mut val_type), + "Failed to convert napi value into rust type `Option<{}>`", + T::type_name() + )?; + + match val_type { + sys::ValueType::napi_undefined => Ok(None), + _ => Ok(Some(T::from_napi_value(env, napi_val)?)), + } + } +} + +impl ToNapiValue for Option +where + T: ToNapiValue + TypeName, +{ + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + match val { + Some(val) => T::to_napi_value(env, val), + None => { + let mut ptr = ptr::null_mut(); + check_status!( + sys::napi_get_undefined(env, &mut ptr), + "Failed to convert rust type `Option<{}>` into napi value", + T::type_name(), + )?; + Ok(ptr) + } + } + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/array.rs b/crates/napi/src/bindgen_runtime/js_values/array.rs new file mode 100644 index 00000000..c2500df5 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/array.rs @@ -0,0 +1,172 @@ +use crate::{bindgen_prelude::*, check_status, sys, ValueType}; +use std::ptr; + +pub struct Array { + env: sys::napi_env, + inner: sys::napi_value, + len: u32, +} + +impl Array { + pub(crate) fn new(env: sys::napi_env, len: u32) -> Result { + let mut ptr = ptr::null_mut(); + unsafe { + check_status!( + sys::napi_create_array_with_length(env, len as usize, &mut ptr), + "Failed to create napi Array" + )?; + } + + Ok(Array { + env, + inner: ptr, + len, + }) + } + + pub fn get(&self, index: u32) -> Result> { + if index >= self.len() { + return Ok(None); + } + + let mut ret = ptr::null_mut(); + unsafe { + check_status!( + sys::napi_get_element(self.env, self.inner, index, &mut ret), + "Failed to get element with index `{}`", + index, + )?; + + Ok(Some(T::from_napi_value(self.env, ret)?)) + } + } + + pub fn set(&mut self, index: u32, val: T) -> Result<()> { + unsafe { + let napi_val = T::to_napi_value(self.env, val)?; + + check_status!( + sys::napi_set_element(self.env, self.inner, index, napi_val), + "Failed to set element with index `{}`", + index, + )?; + + if index >= self.len() { + self.len = index + 1; + } + + Ok(()) + } + } + + pub fn insert(&mut self, val: T) -> Result<()> { + self.set(self.len(), val)?; + Ok(()) + } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> u32 { + self.len + } +} + +impl TypeName for Array { + fn type_name() -> &'static str { + "Array" + } +} + +impl ToNapiValue for Array { + unsafe fn to_napi_value(_env: sys::napi_env, val: Self) -> Result { + Ok(val.inner) + } +} + +impl FromNapiValue for Array { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let mut is_arr = false; + check_status!( + sys::napi_is_array(env, napi_val, &mut is_arr), + "Failed to check given napi value is array" + )?; + + if is_arr { + let mut len = 0; + + check_status!( + sys::napi_get_array_length(env, napi_val, &mut len), + "Failed to get Array length", + )?; + + Ok(Array { + inner: napi_val, + env, + len, + }) + } else { + Err(Error::new( + Status::InvalidArg, + "Given napi value is not an array".to_owned(), + )) + } + } +} + +impl ValidateNapiValue for Array { + fn type_of() -> Vec { + vec![ValueType::Object] + } +} + +impl TypeName for Vec { + fn type_name() -> &'static str { + "Array" + } +} + +impl ToNapiValue for Vec +where + T: ToNapiValue, +{ + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let mut arr = Array::new(env, val.len() as u32)?; + + for (i, v) in val.into_iter().enumerate() { + arr.set(i as u32, v)?; + } + + Array::to_napi_value(env, arr) + } +} + +impl FromNapiValue for Vec +where + T: FromNapiValue, +{ + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let arr = Array::from_napi_value(env, napi_val)?; + let mut vec = vec![]; + + for i in 0..arr.len() { + if let Some(val) = arr.get::(i)? { + vec.push(val); + } else { + return Err(Error::new( + Status::InvalidArg, + "Found inconsistent data type in Array when converting to Rust Vec".to_owned(), + )); + } + } + + Ok(vec) + } +} + +impl ValidateNapiValue for Vec +where + T: FromNapiValue, +{ + fn type_of() -> Vec { + vec![ValueType::Object] + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/boolean.rs b/crates/napi/src/bindgen_runtime/js_values/boolean.rs new file mode 100644 index 00000000..ad061eca --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/boolean.rs @@ -0,0 +1,39 @@ +use crate::{bindgen_prelude::*, check_status, sys, ValueType}; + +impl TypeName for bool { + fn type_name() -> &'static str { + "bool" + } +} + +impl ValidateNapiValue for bool { + fn type_of() -> Vec { + vec![ValueType::Boolean] + } +} + +impl ToNapiValue for bool { + unsafe fn to_napi_value(env: sys::napi_env, val: bool) -> Result { + let mut ptr = std::ptr::null_mut(); + + check_status!( + sys::napi_get_boolean(env, val, &mut ptr), + "Failed to convert rust type `bool` into napi value", + )?; + + Ok(ptr) + } +} + +impl FromNapiValue for bool { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let mut ret = false; + + check_status!( + sys::napi_get_value_bool(env, napi_val, &mut ret), + "Failed to convert napi value into rust type `bool`", + )?; + + Ok(ret) + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/buffer.rs b/crates/napi/src/bindgen_runtime/js_values/buffer.rs new file mode 100644 index 00000000..b6f002f2 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/buffer.rs @@ -0,0 +1,99 @@ +use std::ops::{Deref, DerefMut}; +use std::{mem, ptr}; + +use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType}; + +/// zero copy u8 vector shared between rust and napi +pub struct Buffer { + raw: Option, + inner: mem::ManuallyDrop>, +} + +impl From> for Buffer { + fn from(data: Vec) -> Self { + Buffer { + raw: None, + inner: mem::ManuallyDrop::new(data), + } + } +} + +impl AsRef<[u8]> for Buffer { + fn as_ref(&self) -> &[u8] { + self.inner.as_slice() + } +} + +impl AsMut<[u8]> for Buffer { + fn as_mut(&mut self) -> &mut [u8] { + self.inner.as_mut_slice() + } +} + +impl Deref for Buffer { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + self.inner.as_slice() + } +} + +impl DerefMut for Buffer { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.as_mut_slice() + } +} + +impl TypeName for Buffer { + fn type_name() -> &'static str { + "Vec" + } +} + +impl FromNapiValue for Buffer { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let mut buf = ptr::null_mut(); + let mut len = 0; + + check_status!( + sys::napi_get_buffer_info(env, napi_val, &mut buf, &mut len as *mut usize), + "Failed to convert napi buffer into rust Vec" + )?; + + Ok(Self { + raw: Some(napi_val), + inner: mem::ManuallyDrop::new(Vec::from_raw_parts(buf as *mut _, len, len)), + }) + } +} + +impl ToNapiValue for Buffer { + unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result { + match val.raw { + Some(raw) => Ok(raw), + None => { + let len = val.inner.len(); + let mut ret = ptr::null_mut(); + check_status!( + sys::napi_create_external_buffer( + env, + len, + val.inner.as_mut_ptr() as *mut _, + Some(drop_buffer), + Box::into_raw(Box::new((len, val.inner.capacity()))) as *mut _, + &mut ret, + ), + "Failed to create napi buffer" + )?; + + Ok(ret) + } + } + } +} + +impl ValidateNapiValue for Buffer { + fn type_of() -> Vec { + vec![ValueType::Object] + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/nil.rs b/crates/napi/src/bindgen_runtime/js_values/nil.rs new file mode 100644 index 00000000..05bd00f0 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/nil.rs @@ -0,0 +1,100 @@ +use std::ptr; + +use crate::{bindgen_prelude::*, check_status, sys, type_of, Error, Result, Status, ValueType}; + +pub struct Null; +pub type Undefined = (); + +impl TypeName for Null { + fn type_name() -> &'static str { + "null" + } +} + +impl ValidateNapiValue for Null { + fn type_of() -> Vec { + vec![ValueType::Null] + } +} + +impl FromNapiValue for Null { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + match type_of!(env, napi_val) { + Ok(ValueType::Null) => Ok(Null), + _ => Err(Error::new( + Status::InvalidArg, + "Value is not null".to_owned(), + )), + } + } +} + +impl ToNapiValue for Null { + unsafe fn to_napi_value(env: sys::napi_env, _val: Self) -> Result { + let mut ret = ptr::null_mut(); + + check_status!( + sys::napi_get_null(env, &mut ret), + "Failed to create napi null value" + )?; + + Ok(ret) + } +} + +impl TypeName for Undefined { + fn type_name() -> &'static str { + "undefined" + } +} + +impl ValidateNapiValue for Undefined { + fn type_of() -> Vec { + vec![ValueType::Undefined] + } +} + +impl FromNapiValue for Undefined { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + // TODO: with typecheck + match type_of!(env, napi_val) { + Ok(ValueType::Undefined) => Ok(()), + _ => Err(Error::new( + Status::InvalidArg, + "Value is not undefined".to_owned(), + )), + } + } +} + +impl ToNapiValue for Undefined { + unsafe fn to_napi_value(env: sys::napi_env, _val: Self) -> Result { + let mut ret = ptr::null_mut(); + + check_status!( + sys::napi_get_undefined(env, &mut ret), + "Failed to create napi null value" + )?; + + Ok(ret) + } +} + +impl ToNapiValue for Result<()> { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + match val { + Ok(_) => Ok(Null::to_napi_value(env, Null).unwrap_or_else(|_| ptr::null_mut())), + Err(e) => { + let error_code = String::to_napi_value(env, format!("{:?}", e.status))?; + let reason = String::to_napi_value(env, e.reason)?; + let mut error = ptr::null_mut(); + check_status!( + sys::napi_create_error(env, error_code, reason, &mut error), + "Failed to create napi error" + )?; + + Ok(error) + } + } + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/number.rs b/crates/napi/src/bindgen_runtime/js_values/number.rs new file mode 100644 index 00000000..1c49a938 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/number.rs @@ -0,0 +1,58 @@ +use super::{check_status, sys, Result}; + +macro_rules! impl_number_conversions { + ( $( ($name:literal, $t:ty, $get:ident, $create:ident) ,)* ) => { + $( + impl $crate::bindgen_prelude::TypeName for $t { + #[inline(always)] + fn type_name() -> &'static str { + $name + } + } + + impl $crate::bindgen_prelude::ValidateNapiValue for $t { + #[inline(always)] + fn type_of() -> Vec<$crate::ValueType> { + vec![$crate::ValueType::Number] + } + } + + impl $crate::bindgen_prelude::ToNapiValue for $t { + #[inline(always)] + unsafe fn to_napi_value(env: $crate::sys::napi_env, val: $t) -> Result<$crate::sys::napi_value> { + let mut ptr = std::ptr::null_mut(); + + check_status!( + sys::$create(env, val, &mut ptr), + "Failed to convert rust type `{}` into napi value", + $name, + )?; + + Ok(ptr) + } + } + + impl $crate::bindgen_prelude::FromNapiValue for $t { + #[inline(always)] + unsafe fn from_napi_value(env: $crate::sys::napi_env, napi_val: $crate::sys::napi_value) -> Result { + let mut ret = 0 as $t; + + check_status!( + sys::$get(env, napi_val, &mut ret), + "Failed to convert napi value into rust type `{}`", + $name + )?; + + Ok(ret) + } + } + )* + }; +} + +impl_number_conversions!( + ("u32", u32, napi_get_value_uint32, napi_create_uint32), + ("i32", i32, napi_get_value_int32, napi_create_int32), + ("i64", i64, napi_get_value_int64, napi_create_int64), + ("f64", f64, napi_get_value_double, napi_create_double), +); diff --git a/crates/napi/src/bindgen_runtime/js_values/object.rs b/crates/napi/src/bindgen_runtime/js_values/object.rs new file mode 100644 index 00000000..768ce591 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/object.rs @@ -0,0 +1,106 @@ +use crate::{bindgen_prelude::*, check_status, sys, type_of, ValueType}; +use std::{ffi::CString, ptr}; + +pub struct Object { + env: sys::napi_env, + inner: sys::napi_value, +} + +impl Object { + pub(crate) fn new(env: sys::napi_env) -> Result { + let mut ptr = ptr::null_mut(); + unsafe { + check_status!( + sys::napi_create_object(env, &mut ptr), + "Failed to create napi Object" + )?; + } + + Ok(Object { env, inner: ptr }) + } + + pub fn get(&self, field: String) -> Result> { + let c_field = CString::new(field)?; + + unsafe { + let mut ret = ptr::null_mut(); + + check_status!( + sys::napi_get_named_property(self.env, self.inner, c_field.as_ptr(), &mut ret), + "Failed to get property with field `{}`", + c_field.to_string_lossy(), + )?; + + let ty = type_of!(self.env, ret)?; + + Ok(if ty == ValueType::Undefined { + None + } else { + Some(T::from_napi_value(self.env, ret)?) + }) + } + } + + pub fn set(&mut self, field: String, val: T) -> Result<()> { + let c_field = CString::new(field)?; + + unsafe { + let napi_val = T::to_napi_value(self.env, val)?; + + check_status!( + sys::napi_set_named_property(self.env, self.inner, c_field.as_ptr(), napi_val), + "Failed to set property with field `{}`", + c_field.to_string_lossy(), + )?; + + Ok(()) + } + } + + pub fn keys(obj: &Object) -> Result> { + let mut names = ptr::null_mut(); + unsafe { + check_status!( + sys::napi_get_property_names(obj.env, obj.inner, &mut names), + "Failed to get property names of given object" + )?; + } + + let names = unsafe { Array::from_napi_value(obj.env, names)? }; + let mut ret = vec![]; + + for i in 0..names.len() { + ret.push(names.get::(i)?.unwrap()); + } + + Ok(ret) + } +} + +impl TypeName for Object { + fn type_name() -> &'static str { + "Object" + } +} + +impl ToNapiValue for Object { + unsafe fn to_napi_value(_env: sys::napi_env, val: Self) -> Result { + Ok(val.inner) + } +} + +impl FromNapiValue for Object { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let value_type = type_of!(env, napi_val)?; + match value_type { + ValueType::Object => Ok(Self { + inner: napi_val, + env, + }), + _ => Err(Error::new( + Status::InvalidArg, + "Given napi value is not an object".to_owned(), + )), + } + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/string.rs b/crates/napi/src/bindgen_runtime/js_values/string.rs new file mode 100644 index 00000000..bad667bd --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/string.rs @@ -0,0 +1,235 @@ +use crate::{bindgen_prelude::*, check_status, sys, Error, Result, Status}; + +use std::ffi::CStr; +use std::fmt::Display; +#[cfg(feature = "latin1")] +use std::mem; +use std::ops::Deref; +use std::ptr; + +impl TypeName for String { + fn type_name() -> &'static str { + "String" + } +} + +impl ToNapiValue for String { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let mut ptr = ptr::null_mut(); + + check_status!( + sys::napi_create_string_utf8(env, val.as_ptr() as *const _, val.len(), &mut ptr), + "Failed to convert rust `String` into napi `string`" + )?; + + Ok(ptr) + } +} + +impl FromNapiValue for String { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let mut len = 0; + + check_status!( + sys::napi_get_value_string_utf8(env, napi_val, ptr::null_mut(), 0, &mut len), + "Failed to convert napi `string` into rust type `String`", + )?; + + // end char len in C + len += 1; + let mut ret = Vec::with_capacity(len); + let buf_ptr = ret.as_mut_ptr(); + + let mut written_char_count = 0; + + check_status!( + sys::napi_get_value_string_utf8(env, napi_val, buf_ptr, len, &mut written_char_count), + "Failed to convert napi `string` into rust type `String`" + )?; + + match CStr::from_ptr(buf_ptr).to_str() { + Err(e) => Err(Error::new( + Status::InvalidArg, + format!("Failed to read utf8 string, {}", e), + )), + Ok(s) => Ok(s.to_owned()), + } + } +} + +impl TypeName for &str { + fn type_name() -> &'static str { + "String" + } +} + +impl ToNapiValue for &str { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + String::to_napi_value(env, val.to_owned()) + } +} + +#[derive(Debug)] +pub struct Utf16String(String); + +impl From for Utf16String { + fn from(s: String) -> Self { + Utf16String(s) + } +} + +impl Display for Utf16String { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Deref for Utf16String { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl TypeName for Utf16String { + fn type_name() -> &'static str { + "String(utf16)" + } +} + +impl FromNapiValue for Utf16String { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let mut len = 0; + + check_status!( + sys::napi_get_value_string_utf16(env, napi_val, ptr::null_mut(), 0, &mut len), + "Failed to convert napi `utf16 string` into rust type `String`", + )?; + + // end char len in C + len += 1; + let mut ret = vec![0; len]; + let mut written_char_count = 0; + + check_status!( + sys::napi_get_value_string_utf16( + env, + napi_val, + ret.as_mut_ptr(), + len, + &mut written_char_count + ), + "Failed to convert napi `utf16 string` into rust type `String`", + )?; + + let (_, ret) = ret.split_last().unwrap_or((&0, &[])); + + match String::from_utf16(ret) { + Err(e) => Err(Error::new( + Status::InvalidArg, + format!("Failed to read utf16 string, {}", e), + )), + Ok(s) => Ok(Utf16String(s)), + } + } +} + +impl ToNapiValue for Utf16String { + unsafe fn to_napi_value(env: sys::napi_env, val: Utf16String) -> Result { + let mut ptr = ptr::null_mut(); + + let encoded = val.0.encode_utf16().collect::>(); + + check_status!( + sys::napi_create_string_utf16(env, encoded.as_ptr() as *const _, encoded.len(), &mut ptr), + "Failed to convert napi `string` into rust type `String`" + )?; + + Ok(ptr) + } +} + +#[cfg(feature = "latin1")] +pub mod latin1_string { + use super::*; + + #[derive(Debug)] + pub struct Latin1String(String); + + impl From for Latin1String { + fn from(s: String) -> Self { + Latin1String(s) + } + } + + impl Display for Latin1String { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + impl Deref for Latin1String { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } + } + + impl TypeName for Latin1String { + fn type_name() -> &'static str { + "String(latin1)" + } + } + + #[cfg(feature = "latin1")] + impl FromNapiValue for Latin1String { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let mut len = 0; + + check_status!( + sys::napi_get_value_string_latin1(env, napi_val, ptr::null_mut(), 0, &mut len), + "Failed to convert napi `latin1 string` into rust type `String`", + )?; + + // end char len in C + len += 1; + let mut buf = Vec::with_capacity(len); + let buf_ptr = buf.as_mut_ptr(); + + let mut written_char_count = 0; + + mem::forget(buf); + + check_status!( + sys::napi_get_value_string_latin1(env, napi_val, buf_ptr, len, &mut written_char_count), + "Failed to convert napi `latin1 string` into rust type `String`" + )?; + + let buf = Vec::from_raw_parts(buf_ptr as *mut _, written_char_count, written_char_count); + let mut dst_slice = vec![0; buf.len() * 2]; + let written = + encoding_rs::mem::convert_latin1_to_utf8(buf.as_slice(), dst_slice.as_mut_slice()); + dst_slice.truncate(written); + + Ok(Latin1String(String::from_utf8_unchecked(dst_slice))) + } + } + + impl ToNapiValue for Latin1String { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let mut ptr = ptr::null_mut(); + + let mut dst = vec![0; val.len()]; + encoding_rs::mem::convert_utf8_to_latin1_lossy(val.0.as_bytes(), dst.as_mut_slice()); + + check_status!( + sys::napi_create_string_latin1(env, dst.as_ptr() as *const _, dst.len(), &mut ptr), + "Failed to convert rust type `String` into napi `latin1 string`" + )?; + + Ok(ptr) + } + } +} diff --git a/crates/napi/src/bindgen_runtime/mod.rs b/crates/napi/src/bindgen_runtime/mod.rs new file mode 100644 index 00000000..368bd41b --- /dev/null +++ b/crates/napi/src/bindgen_runtime/mod.rs @@ -0,0 +1,50 @@ +mod callback_info; +mod env; +mod error; +mod js_values; +mod module_register; + +pub use callback_info::*; +pub use ctor::ctor; +pub use env::*; +pub use js_values::*; +pub use module_register::*; + +use super::sys; +use std::{ffi::c_void, mem}; + +/// # Safety +/// +/// called when node wrapper objects destroyed +pub unsafe extern "C" fn raw_finalize_unchecked( + env: sys::napi_env, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, +) { + let obj = finalize_data as *mut T; + Box::from_raw(obj); + if !finalize_hint.is_null() { + let size_hint = *Box::from_raw(finalize_hint as *mut Option); + if let Some(changed) = size_hint { + let mut adjusted = 0i64; + let status = sys::napi_adjust_external_memory(env, -changed, &mut adjusted); + debug_assert!( + status == sys::Status::napi_ok, + "Calling napi_adjust_external_memory failed" + ); + }; + } +} + +/// # Safety +/// +/// called when node buffer is ready for gc +pub unsafe extern "C" fn drop_buffer( + _env: sys::napi_env, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, +) { + let length_ptr = finalize_hint as *mut (usize, usize); + let (length, cap) = *Box::from_raw(length_ptr); + mem::drop(Vec::from_raw_parts(finalize_data as *mut u8, length, cap)); +} diff --git a/crates/napi/src/bindgen_runtime/module_register.rs b/crates/napi/src/bindgen_runtime/module_register.rs new file mode 100644 index 00000000..5637e46e --- /dev/null +++ b/crates/napi/src/bindgen_runtime/module_register.rs @@ -0,0 +1,141 @@ +use std::{cell::RefCell, collections::HashMap, ffi::CString, ptr}; + +use crate::{check_status, check_status_or_throw, sys, JsError, Property, Result}; + +pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result; +pub type ModuleExportsCallback = + unsafe fn(env: sys::napi_env, exports: sys::napi_value) -> Result<()>; + +thread_local! { + static MODULE_REGISTER_CALLBACK: RefCell> = Default::default(); + static MODULE_CLASS_PROPERTIES: RefCell)>> = Default::default(); + static REGISTERED_CLASSES: RefCell> = Default::default(); + // compatibility for #[module_exports] + #[cfg(feature = "compat-mode")] + static MODULE_EXPORTS: std::cell::Cell> = Default::default(); +} + +pub fn get_class_constructor(js_name: &'static str) -> Option { + REGISTERED_CLASSES.with(|registered_classes| { + let classes = registered_classes.borrow(); + classes.get(js_name).copied() + }) +} + +#[cfg(feature = "compat-mode")] +// compatibility for #[module_exports] +pub fn register_module_exports(callback: ModuleExportsCallback) { + MODULE_EXPORTS.with(|cell| cell.set(vec![callback])); +} + +pub fn register_module_export(name: &'static str, cb: ExportRegisterCallback) { + MODULE_REGISTER_CALLBACK.with(|exports| { + let mut list = exports.borrow_mut(); + list.push((name, cb)); + }); +} + +pub fn register_class(rust_name: &'static str, js_name: &'static str, props: Vec) { + MODULE_CLASS_PROPERTIES.with(|map| { + let mut map = map.borrow_mut(); + let val = map.entry(rust_name).or_default(); + + val.0 = js_name; + val.1.extend(props.into_iter()); + }); +} + +#[no_mangle] +unsafe extern "C" fn napi_register_module_v1( + env: sys::napi_env, + exports: sys::napi_value, +) -> sys::napi_value { + MODULE_REGISTER_CALLBACK.with(|to_register_exports| { + to_register_exports + .take() + .into_iter() + .for_each(|(name, callback)| { + let js_name = CString::new(name).unwrap(); + unsafe { + if let Err(e) = callback(env).and_then(|v| { + check_status!( + sys::napi_set_named_property(env, exports, js_name.as_ptr(), v), + "Failed to register export `{}`", + name, + ) + }) { + JsError::from(e).throw_into(env) + } + } + }) + }); + + MODULE_CLASS_PROPERTIES.with(|to_register_classes| { + for (rust_name, (js_name, props)) in to_register_classes.take().into_iter() { + unsafe { + let (ctor, props): (Vec<_>, Vec<_>) = props.into_iter().partition(|prop| prop.is_ctor); + // one or more? + let ctor = ctor[0].raw().method.unwrap(); + let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect(); + + let js_class_name = CString::new(js_name).unwrap(); + let mut class_ptr = ptr::null_mut(); + + check_status_or_throw!( + env, + sys::napi_define_class( + env, + js_class_name.as_ptr(), + js_name.len(), + Some(ctor), + ptr::null_mut(), + raw_props.len(), + raw_props.as_ptr(), + &mut class_ptr, + ), + "Failed to register class `{}` generate by struct `{}`", + &js_name, + &rust_name + ); + + let mut ctor_ref = ptr::null_mut(); + sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref); + + REGISTERED_CLASSES.with(|registered_classes| { + let mut registered_class = registered_classes.borrow_mut(); + registered_class.insert(js_name, ctor_ref); + }); + + check_status_or_throw!( + env, + sys::napi_set_named_property(env, exports, js_class_name.as_ptr(), class_ptr), + "Failed to register class `{}` generate by struct `{}`", + &js_name, + &rust_name + ); + } + } + }); + + #[cfg(feature = "compat-mode")] + MODULE_EXPORTS.with(|callbacks| { + for callback in callbacks.take().into_iter() { + if let Err(e) = callback(env, exports) { + JsError::from(e).throw_into(env); + } + } + }); + + #[cfg(all(feature = "tokio_rt", feature = "napi4"))] + if let Err(e) = check_status!( + sys::napi_add_env_cleanup_hook(env, Some(crate::shutdown_tokio_rt), ptr::null_mut()), + "Failed to initialize module", + ) { + JsError::from(e).throw_into(env); + } + + exports +} diff --git a/napi/src/call_context.rs b/crates/napi/src/call_context.rs similarity index 96% rename from napi/src/call_context.rs rename to crates/napi/src/call_context.rs index c46b8ca3..a2b34bd8 100644 --- a/napi/src/call_context.rs +++ b/crates/napi/src/call_context.rs @@ -25,12 +25,10 @@ impl<'env> CallContext<'env> { /// /// If `.length > .arg_len`, then truncation has happened and some args have /// been lost. - #[inline] fn arg_len(&self) -> usize { self.args.len() } - #[inline] pub fn new( env: &'env mut Env, callback_info: sys::napi_callback_info, @@ -47,7 +45,6 @@ impl<'env> CallContext<'env> { } } - #[inline] pub fn get(&self, index: usize) -> Result { if index >= self.arg_len() { Err(Error { @@ -59,7 +56,6 @@ impl<'env> CallContext<'env> { } } - #[inline] pub fn try_get(&self, index: usize) -> Result> { if index >= self.arg_len() { Err(Error { @@ -73,7 +69,6 @@ impl<'env> CallContext<'env> { } } - #[inline] pub fn get_all(&self) -> Vec { /* (0 .. self.arg_len()).map(|i| self.get(i).unwrap()).collect() */ self @@ -83,7 +78,6 @@ impl<'env> CallContext<'env> { .collect() } - #[inline] pub fn get_new_target(&self) -> Result where V: NapiValue, @@ -93,12 +87,10 @@ impl<'env> CallContext<'env> { unsafe { V::from_raw(self.env.0, value) } } - #[inline] pub fn this(&self) -> Result { unsafe { T::from_raw(self.env.0, self.raw_this) } } - #[inline] pub fn this_unchecked(&self) -> T { unsafe { T::from_raw_unchecked(self.env.0, self.raw_this) } } diff --git a/napi/src/cleanup_env.rs b/crates/napi/src/cleanup_env.rs similarity index 100% rename from napi/src/cleanup_env.rs rename to crates/napi/src/cleanup_env.rs diff --git a/napi/src/env.rs b/crates/napi/src/env.rs similarity index 97% rename from napi/src/env.rs rename to crates/napi/src/env.rs index e68e3669..fe4ca2fb 100644 --- a/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -16,7 +16,7 @@ use crate::{ js_values::*, sys, task::Task, - Error, ExtendedErrorInfo, NodeVersion, Result, Status, + Error, ExtendedErrorInfo, JsError, NodeVersion, Result, Status, ValueType, }; #[cfg(feature = "napi8")] @@ -59,7 +59,7 @@ static RT: Lazy<(Handle, mpsc::Sender<()>)> = Lazy::new(|| { #[doc(hidden)] #[cfg(all(feature = "tokio_rt", feature = "napi4"))] #[inline(never)] -pub fn shutdown_tokio_rt() { +pub extern "C" fn shutdown_tokio_rt(_arg: *mut c_void) { let sender = &RT.1; sender .clone() @@ -80,13 +80,11 @@ pub fn shutdown_tokio_rt() { pub struct Env(pub(crate) sys::napi_env); impl Env { - #[inline] #[allow(clippy::missing_safety_doc)] pub unsafe fn from_raw(env: sys::napi_env) -> Self { Env(env) } - #[inline] /// Get [JsUndefined](./struct.JsUndefined.html) value pub fn get_undefined(&self) -> Result { let mut raw_value = ptr::null_mut(); @@ -94,21 +92,18 @@ impl Env { Ok(unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn get_null(&self) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?; Ok(unsafe { JsNull::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn get_boolean(&self, value: bool) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) })?; Ok(unsafe { JsBoolean::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn create_int32(&self, int: i32) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { @@ -117,7 +112,6 @@ impl Env { Ok(unsafe { JsNumber::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn create_int64(&self, int: i64) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { @@ -126,14 +120,12 @@ impl Env { Ok(unsafe { JsNumber::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn create_uint32(&self, number: u32) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_create_uint32(self.0, number, &mut raw_value) })?; Ok(unsafe { JsNumber::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn create_double(&self, double: f64) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { @@ -144,7 +136,6 @@ impl Env { /// [n_api_napi_create_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_int64) #[cfg(feature = "napi6")] - #[inline] pub fn create_bigint_from_i64(&self, value: i64) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_create_bigint_int64(self.0, value, &mut raw_value) })?; @@ -152,7 +143,6 @@ impl Env { } #[cfg(feature = "napi6")] - #[inline] pub fn create_bigint_from_u64(&self, value: u64) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_create_bigint_uint64(self.0, value, &mut raw_value) })?; @@ -160,7 +150,6 @@ impl Env { } #[cfg(feature = "napi6")] - #[inline] pub fn create_bigint_from_i128(&self, value: i128) -> Result { let mut raw_value = ptr::null_mut(); let sign_bit = if value > 0 { 0 } else { 1 }; @@ -172,7 +161,6 @@ impl Env { } #[cfg(feature = "napi6")] - #[inline] pub fn create_bigint_from_u128(&self, value: u128) -> Result { let mut raw_value = ptr::null_mut(); let words = &value as *const u128 as *const u64; @@ -184,7 +172,6 @@ impl Env { /// /// The resulting BigInt will be negative when sign_bit is true. #[cfg(feature = "napi6")] - #[inline] pub fn create_bigint_from_words(&self, sign_bit: bool, words: Vec) -> Result { let mut raw_value = ptr::null_mut(); let len = words.len(); @@ -203,17 +190,14 @@ impl Env { Ok(JsBigint::from_raw_unchecked(self.0, raw_value, len)) } - #[inline] pub fn create_string(&self, s: &str) -> Result { unsafe { self.create_string_from_c_char(s.as_ptr() as *const c_char, s.len()) } } - #[inline] pub fn create_string_from_std(&self, s: String) -> Result { unsafe { self.create_string_from_c_char(s.as_ptr() as *const c_char, s.len()) } } - #[inline] /// This API is used for C ffi scenario. /// Convert raw *const c_char into JsString /// @@ -235,7 +219,6 @@ impl Env { Ok(JsString::from_raw_unchecked(self.0, raw_value)) } - #[inline] pub fn create_string_utf16(&self, chars: &[u16]) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { @@ -244,7 +227,6 @@ impl Env { Ok(unsafe { JsString::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn create_string_latin1(&self, chars: &[u8]) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { @@ -258,14 +240,12 @@ impl Env { Ok(unsafe { JsString::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn create_symbol_from_js_string(&self, description: JsString) -> Result { let mut result = ptr::null_mut(); check_status!(unsafe { sys::napi_create_symbol(self.0, description.0.value, &mut result) })?; Ok(unsafe { JsSymbol::from_raw_unchecked(self.0, result) }) } - #[inline] pub fn create_symbol(&self, description: Option<&str>) -> Result { let mut result = ptr::null_mut(); check_status!(unsafe { @@ -281,28 +261,24 @@ impl Env { Ok(unsafe { JsSymbol::from_raw_unchecked(self.0, result) }) } - #[inline] pub fn create_object(&self) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_create_object(self.0, &mut raw_value) })?; Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn create_array(&self) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_create_array(self.0, &mut raw_value) })?; Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) }) } - #[inline] pub fn create_array_with_length(&self, length: usize) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_create_array_with_length(self.0, length, &mut raw_value) })?; Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) }) } - #[inline] /// This API allocates a node::Buffer object. While this is still a fully-supported data structure, in most cases using a TypedArray will suffice. pub fn create_buffer(&self, length: usize) -> Result { let mut raw_value = ptr::null_mut(); @@ -322,7 +298,6 @@ impl Env { )) } - #[inline] /// This API allocates a node::Buffer object and initializes it with data backed by the passed in buffer. /// /// While this is still a fully-supported data structure, in most cases using a TypedArray will suffice. @@ -350,7 +325,6 @@ impl Env { )) } - #[inline] /// # Safety /// Mostly the same with `create_buffer_with_data` /// @@ -393,7 +367,6 @@ impl Env { )) } - #[inline] /// This function gives V8 an indication of the amount of externally allocated memory that is kept alive by JavaScript objects (i.e. a JavaScript object that points to its own memory allocated by a native module). /// /// Registering externally allocated memory will trigger global garbage collections more often than it would otherwise. @@ -405,7 +378,6 @@ impl Env { Ok(changed) } - #[inline] /// This API allocates a node::Buffer object and initializes it with data copied from the passed-in buffer. /// /// While this is still a fully-supported data structure, in most cases using a TypedArray will suffice. @@ -436,7 +408,6 @@ impl Env { )) } - #[inline] pub fn create_arraybuffer(&self, length: usize) -> Result { let mut raw_value = ptr::null_mut(); let mut data: Vec = Vec::with_capacity(length as usize); @@ -452,7 +423,6 @@ impl Env { )) } - #[inline] pub fn create_arraybuffer_with_data(&self, data: Vec) -> Result { let length = data.len(); let mut raw_value = ptr::null_mut(); @@ -480,7 +450,6 @@ impl Env { )) } - #[inline] /// # Safety /// Mostly the same with `create_arraybuffer_with_data` /// @@ -524,7 +493,6 @@ impl Env { )) } - #[inline] /// This API allows an add-on author to create a function object in native code. /// /// This is the primary mechanism to allow calling into the add-on's native code from JavaScript. @@ -615,7 +583,7 @@ impl Env { .cast::() .as_ref() .expect("`napi_get_cb_info` should have yielded non-`NULL` assoc data"); - let ref mut env = Env::from_raw(raw_env); + let env = &mut Env::from_raw(raw_env); let ctx = CallContext::new(env, cb_info, raw_this, raw_args, raw_args.len()); closure(ctx).map(|ret: R| ret.raw()) })) @@ -677,7 +645,6 @@ impl Env { Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) }) } - #[inline] /// This API retrieves a napi_extended_error_info structure with information about the last error that occurred. /// /// The content of the napi_extended_error_info returned is only valid up until an n-api function is called on the same env. @@ -691,7 +658,6 @@ impl Env { unsafe { ptr::read(raw_extended_error) }.try_into() } - #[inline] /// This API throws a JavaScript Error with the text provided. pub fn throw_error(&self, msg: &str, code: Option<&str>) -> Result<()> { check_status!(unsafe { @@ -706,7 +672,6 @@ impl Env { }) } - #[inline] /// This API throws a JavaScript RangeError with the text provided. pub fn throw_range_error(&self, msg: &str, code: Option<&str>) -> Result<()> { check_status!(unsafe { @@ -721,7 +686,6 @@ impl Env { }) } - #[inline] /// This API throws a JavaScript TypeError with the text provided. pub fn throw_type_error(&self, msg: &str, code: Option<&str>) -> Result<()> { check_status!(unsafe { @@ -736,7 +700,6 @@ impl Env { }) } - #[inline] #[allow(clippy::expect_fun_call)] /// In the event of an unrecoverable error in a native module /// @@ -760,7 +723,6 @@ impl Env { } #[cfg(feature = "napi3")] - #[inline] /// Trigger an 'uncaughtException' in JavaScript. /// /// Useful if an async callback throws an exception with no way to recover. @@ -771,7 +733,6 @@ impl Env { }; } - #[inline] /// Create JavaScript class pub fn define_class( &self, @@ -801,7 +762,6 @@ impl Env { Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) }) } - #[inline] pub fn wrap(&self, js_object: &mut JsObject, native_object: T) -> Result<()> { check_status!(unsafe { sys::napi_wrap( @@ -815,7 +775,6 @@ impl Env { }) } - #[inline] pub fn unwrap(&self, js_object: &JsObject) -> Result<&mut T> { unsafe { let mut unknown_tagged_object: *mut c_void = ptr::null_mut(); @@ -841,7 +800,6 @@ impl Env { } } - #[inline] pub fn unwrap_from_ref(&self, js_ref: &Ref<()>) -> Result<&'static mut T> { unsafe { let mut unknown_tagged_object: *mut c_void = ptr::null_mut(); @@ -867,7 +825,6 @@ impl Env { } } - #[inline] pub fn drop_wrapped(&self, js_object: &mut JsObject) -> Result<()> { unsafe { let mut unknown_tagged_object = ptr::null_mut(); @@ -889,7 +846,6 @@ impl Env { } } - #[inline] /// This API create a new reference with the specified reference count to the Object passed in. pub fn create_reference(&self, value: T) -> Result> where @@ -909,7 +865,6 @@ impl Env { }) } - #[inline] /// Get reference value from `Ref` with type check /// /// Return error if the type of `reference` provided is mismatched with `T` @@ -924,7 +879,6 @@ impl Env { unsafe { T::from_raw(self.0, js_value) } } - #[inline] /// Get reference value from `Ref` without type check /// /// Using this API if you are sure the type of `T` is matched with provided `Ref<()>`. @@ -941,7 +895,6 @@ impl Env { Ok(unsafe { T::from_raw_unchecked(self.0, js_value) }) } - #[inline] /// If `size_hint` provided, `Env::adjust_external_memory` will be called under the hood. /// /// If no `size_hint` provided, global garbage collections will be triggered less times than expected. @@ -971,7 +924,6 @@ impl Env { Ok(unsafe { JsExternal::from_raw_unchecked(self.0, object_value) }) } - #[inline] pub fn get_value_external(&self, js_external: &JsExternal) -> Result<&mut T> { unsafe { let mut unknown_tagged_object = ptr::null_mut(); @@ -997,7 +949,6 @@ impl Env { } } - #[inline] pub fn create_error(&self, e: Error) -> Result { let reason = e.reason; let reason_string = self.create_string(reason.as_str())?; @@ -1008,13 +959,11 @@ impl Env { Ok(unsafe { JsObject::from_raw_unchecked(self.0, result) }) } - #[inline] /// Run [Task](./trait.Task.html) in libuv thread pool, return [AsyncWorkPromise](./struct.AsyncWorkPromise.html) pub fn spawn(&self, task: T) -> Result { async_work::run(self, task) } - #[inline] pub fn run_in_scope(&self, executor: F) -> Result where F: FnOnce() -> Result, @@ -1028,14 +977,12 @@ impl Env { result } - #[inline] pub fn get_global(&self) -> Result { let mut raw_global = ptr::null_mut(); check_status!(unsafe { sys::napi_get_global(self.0, &mut raw_global) })?; Ok(unsafe { JsGlobal::from_raw_unchecked(self.0, raw_global) }) } - #[inline] pub fn get_napi_version(&self) -> Result { let global = self.get_global()?; let process: JsObject = global.get_named_property("process")?; @@ -1049,7 +996,6 @@ impl Env { } #[cfg(feature = "napi2")] - #[inline] pub fn get_uv_event_loop(&self) -> Result<*mut sys::uv_loop_s> { let mut uv_loop: *mut sys::uv_loop_s = ptr::null_mut(); check_status!(unsafe { sys::napi_get_uv_event_loop(self.0, &mut uv_loop) })?; @@ -1057,7 +1003,6 @@ impl Env { } #[cfg(feature = "napi3")] - #[inline] pub fn add_env_cleanup_hook( &mut self, cleanup_data: T, @@ -1083,7 +1028,6 @@ impl Env { } #[cfg(feature = "napi3")] - #[inline] pub fn remove_env_cleanup_hook(&mut self, hook: CleanupEnvHook) -> Result<()> where T: 'static, @@ -1094,7 +1038,6 @@ impl Env { } #[cfg(feature = "napi4")] - #[inline] pub fn create_threadsafe_function< T: Send, V: NapiRaw, @@ -1109,7 +1052,6 @@ impl Env { } #[cfg(all(feature = "tokio_rt", feature = "napi4"))] - #[inline] pub fn execute_tokio_future< T: 'static + Send, V: 'static + NapiValue, @@ -1135,13 +1077,12 @@ impl Env { Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_promise) }) } - #[cfg(feature = "napi5")] - #[inline] /// This API does not observe leap seconds; they are ignored, as ECMAScript aligns with POSIX time specification. /// /// This API allocates a JavaScript Date object. /// /// JavaScript Date objects are described in [Section 20.3](https://tc39.github.io/ecma262/#sec-date-objects) of the ECMAScript Language Specification. + #[cfg(feature = "napi5")] pub fn create_date(&self, time: f64) -> Result { let mut js_value = ptr::null_mut(); check_status!(unsafe { sys::napi_create_date(self.0, time, &mut js_value) })?; @@ -1149,7 +1090,7 @@ impl Env { } #[cfg(feature = "napi6")] - #[inline] + /// This API associates data with the currently running Agent. data can later be retrieved using `Env::get_instance_data()`. /// /// Any existing data associated with the currently running Agent which was set by means of a previous call to `Env::set_instance_data()` will be overwritten. @@ -1179,11 +1120,10 @@ impl Env { }) } - #[cfg(feature = "napi6")] - #[inline] /// This API retrieves data that was previously associated with the currently running Agent via `Env::set_instance_data()`. /// /// If no data is set, the call will succeed and data will be set to NULL. + #[cfg(feature = "napi6")] pub fn get_instance_data(&self) -> Result> where T: 'static, @@ -1213,12 +1153,12 @@ impl Env { } } - #[cfg(feature = "napi8")] /// Registers hook, which is a function of type `FnOnce(Arg)`, as a function to be run with the `arg` parameter once the current Node.js environment exits. /// /// Unlike [`add_env_cleanup_hook`](https://docs.rs/napi/latest/napi/struct.Env.html#method.add_env_cleanup_hook), the hook is allowed to be asynchronous. /// /// Otherwise, behavior generally matches that of [`add_env_cleanup_hook`](https://docs.rs/napi/latest/napi/struct.Env.html#method.add_env_cleanup_hook). + #[cfg(feature = "napi8")] pub fn add_removable_async_cleanup_hook( &self, arg: Arg, @@ -1243,10 +1183,10 @@ impl Env { Ok(AsyncCleanupHook(handle)) } - #[cfg(feature = "napi8")] /// This API is very similar to [`add_removable_async_cleanup_hook`](https://docs.rs/napi/latest/napi/struct.Env.html#method.add_removable_async_cleanup_hook) /// /// Use this one if you don't want remove the cleanup hook anymore. + #[cfg(feature = "napi8")] pub fn add_async_cleanup_hook(&self, arg: Arg, cleanup_fn: F) -> Result<()> where F: FnOnce(Arg), @@ -1283,7 +1223,6 @@ impl Env { /// ``` #[cfg(feature = "serde-json")] #[allow(clippy::wrong_self_convention)] - #[inline] pub fn to_js_value(&self, node: &T) -> Result where T: Serialize, @@ -1309,7 +1248,6 @@ impl Env { /// } /// #[cfg(feature = "serde-json")] - #[inline] pub fn from_js_value(&self, value: V) -> Result where T: DeserializeOwned + ?Sized, @@ -1324,7 +1262,6 @@ impl Env { T::deserialize(&mut de) } - #[inline] /// This API represents the invocation of the Strict Equality algorithm as defined in [Section 7.2.14](https://tc39.es/ecma262/#sec-strict-equality-comparison) of the ECMAScript Language Specification. pub fn strict_equals(&self, a: A, b: B) -> Result { let mut result = false; @@ -1332,7 +1269,6 @@ impl Env { Ok(result) } - #[inline] pub fn get_node_version(&self) -> Result { let mut result = ptr::null(); check_status!(unsafe { sys::napi_get_node_version(self.0, &mut result) })?; @@ -1341,7 +1277,6 @@ impl Env { } /// get raw env ptr - #[inline] pub fn raw(&self) -> sys::napi_env { self.0 } diff --git a/crates/napi/src/error.rs b/crates/napi/src/error.rs new file mode 100644 index 00000000..3d6201ce --- /dev/null +++ b/crates/napi/src/error.rs @@ -0,0 +1,227 @@ +use std::convert::{From, TryFrom}; +use std::error; +use std::ffi::CString; +use std::fmt; +#[cfg(feature = "serde-json")] +use std::fmt::Display; +use std::os::raw::{c_char, c_void}; +use std::ptr; + +#[cfg(feature = "serde-json")] +use serde::{de, ser}; +#[cfg(feature = "serde-json")] +use serde_json::Error as SerdeJSONError; + +use crate::{check_status, sys, Status}; + +pub type Result = std::result::Result; + +/// Represent `JsError`. +/// Return this Error in `js_function`, **napi-rs** will throw it as `JsError` for you. +/// If you want throw it as `TypeError` or `RangeError`, you can use `JsTypeError/JsRangeError::from(Error).throw_into(env)` +#[derive(Debug, Clone)] +pub struct Error { + pub status: Status, + pub reason: String, +} + +impl error::Error for Error {} + +#[cfg(feature = "serde-json")] +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error::new(Status::InvalidArg, msg.to_string()) + } +} + +#[cfg(feature = "serde-json")] +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::new(Status::InvalidArg, msg.to_string()) + } +} + +#[cfg(feature = "serde-json")] +impl From for Error { + fn from(value: SerdeJSONError) -> Self { + Error::new(Status::InvalidArg, format!("{}", value)) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.reason.is_empty() { + write!(f, "{:?}, {}", self.status, self.reason) + } else { + write!(f, "{:?}", self.status) + } + } +} + +impl Error { + pub fn new(status: Status, reason: String) -> Self { + Error { status, reason } + } + + pub fn from_status(status: Status) -> Self { + Error { + status, + reason: "".to_owned(), + } + } + + pub fn from_reason(reason: String) -> Self { + Error { + status: Status::GenericFailure, + reason, + } + } +} + +impl From for Error { + fn from(error: std::ffi::NulError) -> Self { + Error { + status: Status::GenericFailure, + reason: format!("{}", error), + } + } +} + +impl From for Error { + fn from(error: std::io::Error) -> Self { + Error { + status: Status::GenericFailure, + reason: format!("{}", error), + } + } +} + +#[derive(Clone, Debug)] +pub struct ExtendedErrorInfo { + pub message: String, + pub engine_reserved: *mut c_void, + pub engine_error_code: u32, + pub error_code: Status, +} + +impl TryFrom for ExtendedErrorInfo { + type Error = Error; + + fn try_from(value: sys::napi_extended_error_info) -> Result { + Ok(Self { + message: unsafe { + CString::from_raw(value.error_message as *mut c_char) + .into_string() + .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))? + }, + engine_error_code: value.engine_error_code, + engine_reserved: value.engine_reserved, + error_code: Status::from(value.error_code), + }) + } +} + +pub struct JsError(Error); + +pub struct JsTypeError(Error); + +pub struct JsRangeError(Error); + +macro_rules! impl_object_methods { + ($js_value:ident, $kind:expr) => { + impl $js_value { + /// # Safety + /// + /// This function is safety if env is not null ptr. + pub unsafe fn into_value(self, env: sys::napi_env) -> sys::napi_value { + let error_status = format!("{:?}", self.0.status); + let status_len = error_status.len(); + let error_code_string = CString::new(error_status).unwrap(); + let reason_len = self.0.reason.len(); + let reason = CString::new(self.0.reason).unwrap(); + let mut error_code = ptr::null_mut(); + let mut reason_string = ptr::null_mut(); + let mut js_error = ptr::null_mut(); + let create_code_status = sys::napi_create_string_utf8( + env, + error_code_string.as_ptr(), + status_len, + &mut error_code, + ); + debug_assert!(create_code_status == sys::Status::napi_ok); + let create_reason_status = + sys::napi_create_string_utf8(env, reason.as_ptr(), reason_len, &mut reason_string); + debug_assert!(create_reason_status == sys::Status::napi_ok); + let create_error_status = $kind(env, error_code, reason_string, &mut js_error); + debug_assert!(create_error_status == sys::Status::napi_ok); + js_error + } + + /// # Safety + /// + /// This function is safety if env is not null ptr. + pub unsafe fn throw_into(self, env: sys::napi_env) { + let js_error = self.into_value(env); + let throw_status = sys::napi_throw(env, js_error); + debug_assert!(throw_status == sys::Status::napi_ok); + } + + pub fn throw(&self, env: sys::napi_env) -> Result<()> { + let error_status = format!("{:?}", self.0.status); + let status_len = error_status.len(); + let error_code_string = CString::new(error_status).unwrap(); + let reason_len = self.0.reason.len(); + let reason = CString::new(self.0.reason.clone()).unwrap(); + let mut error_code = ptr::null_mut(); + let mut reason_string = ptr::null_mut(); + let mut js_error = ptr::null_mut(); + check_status!(unsafe { + sys::napi_create_string_utf8(env, error_code_string.as_ptr(), status_len, &mut error_code) + })?; + check_status!(unsafe { + sys::napi_create_string_utf8(env, reason.as_ptr(), reason_len, &mut reason_string) + })?; + check_status!(unsafe { $kind(env, error_code, reason_string, &mut js_error) })?; + check_status!(unsafe { sys::napi_throw(env, js_error) }) + } + } + + impl From for $js_value { + fn from(err: Error) -> Self { + Self(err) + } + } + }; +} + +impl_object_methods!(JsError, sys::napi_create_error); +impl_object_methods!(JsTypeError, sys::napi_create_type_error); +impl_object_methods!(JsRangeError, sys::napi_create_range_error); + +#[doc(hidden)] +#[macro_export] +macro_rules! error { + ($status:expr, $($msg:tt)*) => { + $crate::Error::new($status, format!($($msg)*)) + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! check_status { + ($code:expr) => {{ + let c = $code; + match c { + $crate::sys::Status::napi_ok => Ok(()), + _ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())), + } + }}; + + ($code:expr, $($msg:tt)*) => {{ + let c = $code; + match c { + $crate::sys::Status::napi_ok => Ok(()), + _ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))), + } + }}; +} diff --git a/napi/src/js_values/arraybuffer.rs b/crates/napi/src/js_values/arraybuffer.rs similarity index 98% rename from napi/src/js_values/arraybuffer.rs rename to crates/napi/src/js_values/arraybuffer.rs index 8b0303d7..467b47fc 100644 --- a/napi/src/js_values/arraybuffer.rs +++ b/crates/napi/src/js_values/arraybuffer.rs @@ -83,13 +83,11 @@ impl From for sys::napi_typedarray_type { impl JsArrayBuffer { #[cfg(feature = "napi7")] - #[inline] pub fn detach(self) -> Result<()> { check_status!(unsafe { sys::napi_detach_arraybuffer(self.0.env, self.0.value) }) } #[cfg(feature = "napi7")] - #[inline] pub fn is_detached(&self) -> Result { let mut is_detached = false; check_status!(unsafe { @@ -98,7 +96,6 @@ impl JsArrayBuffer { Ok(is_detached) } - #[inline] pub fn into_value(self) -> Result { let mut data = ptr::null_mut(); let mut len: usize = 0; @@ -112,7 +109,6 @@ impl JsArrayBuffer { }) } - #[inline] pub fn into_typedarray( self, typedarray_type: TypedArrayType, @@ -137,7 +133,6 @@ impl JsArrayBuffer { })) } - #[inline] pub fn into_dataview(self, length: usize, byte_offset: usize) -> Result { let mut dataview_value = ptr::null_mut(); check_status!(unsafe { @@ -156,24 +151,20 @@ impl JsArrayBuffer { })) } - #[inline] pub fn into_ref(self) -> Result> { Ref::new(self.0, 1, self.into_value()?) } } impl JsArrayBufferValue { - #[inline] pub fn new(value: JsArrayBuffer, data: *mut c_void, len: usize) -> Self { JsArrayBufferValue { value, len, data } } - #[inline] pub fn into_raw(self) -> JsArrayBuffer { self.value } - #[inline] pub fn into_unknown(self) -> JsUnknown { unsafe { JsUnknown::from_raw_unchecked(self.value.0.env, self.value.0.value) } } @@ -210,7 +201,6 @@ impl JsTypedArray { /// https://nodejs.org/api/n-api.html#n_api_napi_get_typedarray_info /// /// ***Warning***: Use caution while using this API since the underlying data buffer is managed by the VM. - #[inline] pub fn into_value(self) -> Result { let mut typedarray_type = 0; let mut len = 0u64; @@ -269,7 +259,6 @@ impl_as_ref!(i64); impl_as_ref!(u64); impl JsDataView { - #[inline] pub fn into_value(self) -> Result { let mut length = 0u64; let mut byte_offset = 0u64; diff --git a/napi/src/js_values/bigint.rs b/crates/napi/src/js_values/bigint.rs similarity index 96% rename from napi/src/js_values/bigint.rs rename to crates/napi/src/js_values/bigint.rs index 8adaa0ee..2ce3fdb6 100644 --- a/napi/src/js_values/bigint.rs +++ b/crates/napi/src/js_values/bigint.rs @@ -11,7 +11,6 @@ pub struct JsBigint { } impl JsBigint { - #[inline] pub(crate) fn from_raw_unchecked( env: sys::napi_env, value: sys::napi_value, @@ -27,12 +26,10 @@ impl JsBigint { } } - #[inline] pub fn into_unknown(self) -> Result { unsafe { JsUnknown::from_raw(self.raw.env, self.raw.value) } } - #[inline] pub fn coerce_to_number(self) -> Result { let mut new_raw_value = ptr::null_mut(); check_status!(unsafe { @@ -45,7 +42,6 @@ impl JsBigint { })) } - #[inline] pub fn coerce_to_string(self) -> Result { let mut new_raw_value = ptr::null_mut(); check_status!(unsafe { @@ -57,7 +53,7 @@ impl JsBigint { value_type: ValueType::String, })) } - #[inline] + pub fn coerce_to_object(self) -> Result { let mut new_raw_value = ptr::null_mut(); check_status!(unsafe { @@ -70,7 +66,6 @@ impl JsBigint { })) } - #[inline] #[cfg(feature = "napi5")] pub fn is_date(&self) -> Result { let mut is_date = true; @@ -78,42 +73,36 @@ impl JsBigint { Ok(is_date) } - #[inline] pub fn is_error(&self) -> Result { let mut result = false; check_status!(unsafe { sys::napi_is_error(self.raw.env, self.raw.value, &mut result) })?; Ok(result) } - #[inline] pub fn is_typedarray(&self) -> Result { let mut result = false; check_status!(unsafe { sys::napi_is_typedarray(self.raw.env, self.raw.value, &mut result) })?; Ok(result) } - #[inline] pub fn is_dataview(&self) -> Result { let mut result = false; check_status!(unsafe { sys::napi_is_dataview(self.raw.env, self.raw.value, &mut result) })?; Ok(result) } - #[inline] pub fn is_array(&self) -> Result { let mut is_array = false; check_status!(unsafe { sys::napi_is_array(self.raw.env, self.raw.value, &mut is_array) })?; Ok(is_array) } - #[inline] pub fn is_buffer(&self) -> Result { let mut is_buffer = false; check_status!(unsafe { sys::napi_is_buffer(self.raw.env, self.raw.value, &mut is_buffer) })?; Ok(is_buffer) } - #[inline] pub fn instanceof(&self, constructor: Constructor) -> Result { let mut result = false; check_status!(unsafe { @@ -199,7 +188,6 @@ impl TryFrom for u64 { impl JsBigint { /// https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words - #[inline] pub fn get_words(&mut self) -> Result<(bool, Vec)> { let mut words: Vec = Vec::with_capacity(self.word_count as usize); let word_count = &mut self.word_count; @@ -221,7 +209,6 @@ impl JsBigint { Ok((sign_bit == 1, words)) } - #[inline] pub fn get_u64(&self) -> Result<(u64, bool)> { let mut val: u64 = 0; let mut loss = false; @@ -232,7 +219,6 @@ impl JsBigint { Ok((val, loss)) } - #[inline] pub fn get_i64(&self) -> Result<(i64, bool)> { let mut val: i64 = 0; let mut loss: bool = false; @@ -242,7 +228,6 @@ impl JsBigint { Ok((val, loss)) } - #[inline] pub fn get_i128(&mut self) -> Result<(i128, bool)> { let (signed, words) = self.get_words()?; let len = words.len(); @@ -254,7 +239,6 @@ impl JsBigint { Ok((val, len > 2)) } - #[inline] pub fn get_u128(&mut self) -> Result<(bool, u128, bool)> { let (signed, words) = self.get_words()?; let len = words.len(); diff --git a/napi/src/js_values/boolean.rs b/crates/napi/src/js_values/boolean.rs similarity index 97% rename from napi/src/js_values/boolean.rs rename to crates/napi/src/js_values/boolean.rs index 55ef7f3b..1b0a12bb 100644 --- a/napi/src/js_values/boolean.rs +++ b/crates/napi/src/js_values/boolean.rs @@ -8,7 +8,6 @@ use crate::{sys, Error, Result}; pub struct JsBoolean(pub(crate) Value); impl JsBoolean { - #[inline] pub fn get_value(&self) -> Result { let mut result = false; check_status!(unsafe { sys::napi_get_value_bool(self.0.env, self.0.value, &mut result) })?; diff --git a/napi/src/js_values/buffer.rs b/crates/napi/src/js_values/buffer.rs similarity index 96% rename from napi/src/js_values/buffer.rs rename to crates/napi/src/js_values/buffer.rs index a57c3b33..82a97a9f 100644 --- a/napi/src/js_values/buffer.rs +++ b/crates/napi/src/js_values/buffer.rs @@ -16,7 +16,6 @@ pub struct JsBufferValue { } impl JsBuffer { - #[inline] pub fn into_value(self) -> Result { let mut data = ptr::null_mut(); let mut len: usize = 0; @@ -29,7 +28,6 @@ impl JsBuffer { }) } - #[inline] pub fn into_ref(self) -> Result> { Ref::new(self.0, 1, self.into_value()?) } @@ -37,7 +35,6 @@ impl JsBuffer { impl JsBufferValue { #[cfg(feature = "serde-json")] - #[inline] pub(crate) fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result { let mut data = ptr::null_mut(); let mut len = 0usize; @@ -54,17 +51,14 @@ impl JsBufferValue { }) } - #[inline] pub fn new(value: JsBuffer, data: mem::ManuallyDrop>) -> Self { JsBufferValue { value, data } } - #[inline] pub fn into_raw(self) -> JsBuffer { self.value } - #[inline] pub fn into_unknown(self) -> JsUnknown { unsafe { JsUnknown::from_raw_unchecked(self.value.0.env, self.value.0.value) } } diff --git a/napi/src/js_values/date.rs b/crates/napi/src/js_values/date.rs similarity index 96% rename from napi/src/js_values/date.rs rename to crates/napi/src/js_values/date.rs index 74489ad8..b88669fe 100644 --- a/napi/src/js_values/date.rs +++ b/crates/napi/src/js_values/date.rs @@ -4,7 +4,6 @@ use crate::{sys, Result, Value}; pub struct JsDate(pub(crate) Value); impl JsDate { - #[inline] pub fn value_of(&self) -> Result { let mut timestamp: f64 = 0.0; check_status!(unsafe { sys::napi_get_date_value(self.0.env, self.0.value, &mut timestamp) })?; diff --git a/napi/src/js_values/de.rs b/crates/napi/src/js_values/de.rs similarity index 100% rename from napi/src/js_values/de.rs rename to crates/napi/src/js_values/de.rs diff --git a/napi/src/js_values/either.rs b/crates/napi/src/js_values/either.rs similarity index 100% rename from napi/src/js_values/either.rs rename to crates/napi/src/js_values/either.rs diff --git a/napi/src/js_values/escapable_handle_scope.rs b/crates/napi/src/js_values/escapable_handle_scope.rs similarity index 98% rename from napi/src/js_values/escapable_handle_scope.rs rename to crates/napi/src/js_values/escapable_handle_scope.rs index a224ea09..e035609c 100644 --- a/napi/src/js_values/escapable_handle_scope.rs +++ b/crates/napi/src/js_values/escapable_handle_scope.rs @@ -10,7 +10,6 @@ pub struct EscapableHandleScope { } impl EscapableHandleScope { - #[inline] pub fn open(env: sys::napi_env, value: T) -> Result { let mut handle_scope = ptr::null_mut(); check_status!(unsafe { sys::napi_open_escapable_handle_scope(env, &mut handle_scope) })?; diff --git a/napi/src/js_values/function.rs b/crates/napi/src/js_values/function.rs similarity index 98% rename from napi/src/js_values/function.rs rename to crates/napi/src/js_values/function.rs index 9de510d9..59117862 100644 --- a/napi/src/js_values/function.rs +++ b/crates/napi/src/js_values/function.rs @@ -22,7 +22,6 @@ pub struct JsFunction(pub(crate) Value); /// ``` impl JsFunction { /// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function) - #[inline] pub fn call(&self, this: Option<&JsObject>, args: &[V]) -> Result where V: NapiRaw, @@ -57,7 +56,6 @@ impl JsFunction { /// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function) /// The same with `call`, but without arguments - #[inline] pub fn call_without_args(&self, this: Option<&JsObject>) -> Result { let raw_this = this .map(|v| unsafe { v.raw() }) @@ -87,7 +85,6 @@ impl JsFunction { /// /// This method is used to instantiate a new `JavaScript` value using a given `JsFunction` that represents the constructor for the object. #[allow(clippy::new_ret_no_self)] - #[inline] pub fn new(&self, args: &[V]) -> Result where V: NapiRaw, diff --git a/napi/src/js_values/global.rs b/crates/napi/src/js_values/global.rs similarity index 97% rename from napi/src/js_values/global.rs rename to crates/napi/src/js_values/global.rs index 9f86134e..aff3cbad 100644 --- a/napi/src/js_values/global.rs +++ b/crates/napi/src/js_values/global.rs @@ -8,7 +8,6 @@ pub struct JsGlobal(pub(crate) Value); pub struct JsTimeout(pub(crate) Value); impl JsGlobal { - #[inline] pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result { let func: JsFunction = self.get_named_property("setInterval")?; func @@ -31,7 +30,6 @@ impl JsGlobal { .and_then(|ret| ret.try_into()) } - #[inline] pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result { let func: JsFunction = self.get_named_property("setTimeout")?; func @@ -47,7 +45,6 @@ impl JsGlobal { .and_then(|ret| ret.try_into()) } - #[inline] pub fn clear_timeout(&self, timer: JsTimeout) -> Result { let func: JsFunction = self.get_named_property("clearTimeout")?; func diff --git a/napi/src/js_values/mod.rs b/crates/napi/src/js_values/mod.rs similarity index 93% rename from napi/src/js_values/mod.rs rename to crates/napi/src/js_values/mod.rs index 6df707ed..34259bf8 100644 --- a/napi/src/js_values/mod.rs +++ b/crates/napi/src/js_values/mod.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use std::ffi::CString; use std::ptr; -use crate::{check_status, sys, Callback, Error, Result, Status}; +use crate::{check_status, sys, type_of, Callback, Error, Result, Status, ValueType}; #[cfg(feature = "serde-json")] mod de; @@ -17,7 +17,6 @@ mod buffer; #[cfg(feature = "napi5")] mod date; mod either; -mod error; mod escapable_handle_scope; mod function; mod global; @@ -29,7 +28,6 @@ mod tagged_object; mod undefined; mod value; mod value_ref; -mod value_type; pub use arraybuffer::*; #[cfg(feature = "napi6")] @@ -41,7 +39,6 @@ pub use date::*; #[cfg(feature = "serde-json")] pub(crate) use de::De; pub use either::Either; -pub use error::*; pub use escapable_handle_scope::EscapableHandleScope; pub use function::JsFunction; pub use global::*; @@ -55,7 +52,6 @@ pub(crate) use tagged_object::TaggedObject; pub use undefined::JsUndefined; pub(crate) use value::Value; pub use value_ref::*; -pub use value_type::ValueType; // Value types @@ -69,19 +65,6 @@ pub struct JsSymbol(pub(crate) Value); pub struct JsExternal(pub(crate) Value); -#[doc(hidden)] -#[macro_export(local_inner_macros)] -macro_rules! type_of { - ($env:expr, $value:expr) => {{ - use std::convert::TryFrom; - use $crate::sys; - let mut value_type = 0; - let status = sys::napi_typeof($env, $value, &mut value_type); - check_status!(status) - .and_then(|_| ValueType::try_from(value_type).or_else(|_| Ok(ValueType::Unknown))) - }}; -} - macro_rules! impl_napi_value_trait { ($js_value:ident, $value_type:ident) => { impl NapiValue for $js_value { @@ -134,12 +117,10 @@ macro_rules! impl_napi_value_trait { macro_rules! impl_js_value_methods { ($js_value:ident) => { impl $js_value { - #[inline] pub fn into_unknown(self) -> JsUnknown { unsafe { JsUnknown::from_raw_unchecked(self.0.env, self.0.value) } } - #[inline] pub fn coerce_to_number(self) -> Result { let mut new_raw_value = ptr::null_mut(); check_status!(unsafe { @@ -152,7 +133,6 @@ macro_rules! impl_js_value_methods { })) } - #[inline] pub fn coerce_to_string(self) -> Result { let mut new_raw_value = ptr::null_mut(); check_status!(unsafe { @@ -165,7 +145,6 @@ macro_rules! impl_js_value_methods { })) } - #[inline] pub fn coerce_to_object(self) -> Result { let mut new_raw_value = ptr::null_mut(); check_status!(unsafe { @@ -179,56 +158,49 @@ macro_rules! impl_js_value_methods { } #[cfg(feature = "napi5")] - #[inline] + pub fn is_date(&self) -> Result { let mut is_date = true; check_status!(unsafe { sys::napi_is_date(self.0.env, self.0.value, &mut is_date) })?; Ok(is_date) } - #[inline] pub fn is_promise(&self) -> Result { let mut is_promise = true; check_status!(unsafe { sys::napi_is_promise(self.0.env, self.0.value, &mut is_promise) })?; Ok(is_promise) } - #[inline] pub fn is_error(&self) -> Result { let mut result = false; check_status!(unsafe { sys::napi_is_error(self.0.env, self.0.value, &mut result) })?; Ok(result) } - #[inline] pub fn is_typedarray(&self) -> Result { let mut result = false; check_status!(unsafe { sys::napi_is_typedarray(self.0.env, self.0.value, &mut result) })?; Ok(result) } - #[inline] pub fn is_dataview(&self) -> Result { let mut result = false; check_status!(unsafe { sys::napi_is_dataview(self.0.env, self.0.value, &mut result) })?; Ok(result) } - #[inline] pub fn is_array(&self) -> Result { let mut is_array = false; check_status!(unsafe { sys::napi_is_array(self.0.env, self.0.value, &mut is_array) })?; Ok(is_array) } - #[inline] pub fn is_buffer(&self) -> Result { let mut is_buffer = false; check_status!(unsafe { sys::napi_is_buffer(self.0.env, self.0.value, &mut is_buffer) })?; Ok(is_buffer) } - #[inline] pub fn instanceof(&self, constructor: Constructor) -> Result where Constructor: NapiRaw, @@ -241,13 +213,11 @@ macro_rules! impl_js_value_methods { } #[cfg(feature = "napi8")] - #[inline] pub fn freeze(&mut self) -> Result<()> { check_status!(unsafe { sys::napi_object_freeze(self.0.env, self.0.value) }) } #[cfg(feature = "napi8")] - #[inline] pub fn seal(&mut self) -> Result<()> { check_status!(unsafe { sys::napi_object_seal(self.0.env, self.0.value) }) } @@ -258,7 +228,6 @@ macro_rules! impl_js_value_methods { macro_rules! impl_object_methods { ($js_value:ident) => { impl $js_value { - #[inline] pub fn set_property(&mut self, key: JsString, value: V) -> Result<()> where V: NapiRaw, @@ -268,7 +237,6 @@ macro_rules! impl_object_methods { }) } - #[inline] pub fn get_property(&self, key: K) -> Result where K: NapiRaw, @@ -281,7 +249,6 @@ macro_rules! impl_object_methods { unsafe { T::from_raw(self.0.env, raw_value) } } - #[inline] pub fn get_property_unchecked(&self, key: K) -> Result where K: NapiRaw, @@ -294,7 +261,6 @@ macro_rules! impl_object_methods { Ok(unsafe { T::from_raw_unchecked(self.0.env, raw_value) }) } - #[inline] pub fn set_named_property(&mut self, name: &str, value: T) -> Result<()> where T: NapiRaw, @@ -305,7 +271,6 @@ macro_rules! impl_object_methods { }) } - #[inline] pub fn create_named_method(&mut self, name: &str, function: Callback) -> Result<()> { let mut js_function = ptr::null_mut(); let len = name.len(); @@ -325,7 +290,6 @@ macro_rules! impl_object_methods { }) } - #[inline] pub fn get_named_property(&self, name: &str) -> Result where T: NapiValue, @@ -338,7 +302,6 @@ macro_rules! impl_object_methods { unsafe { T::from_raw(self.0.env, raw_value) } } - #[inline] pub fn get_named_property_unchecked(&self, name: &str) -> Result where T: NapiValue, @@ -351,7 +314,6 @@ macro_rules! impl_object_methods { Ok(unsafe { T::from_raw_unchecked(self.0.env, raw_value) }) } - #[inline] pub fn has_named_property(&self, name: &str) -> Result { let mut result = false; let key = CString::new(name)?; @@ -361,7 +323,6 @@ macro_rules! impl_object_methods { Ok(result) } - #[inline] pub fn delete_property(&mut self, name: S) -> Result where S: NapiRaw, @@ -373,7 +334,6 @@ macro_rules! impl_object_methods { Ok(result) } - #[inline] pub fn delete_named_property(&mut self, name: &str) -> Result { let mut result = false; let key_str = CString::new(name)?; @@ -387,7 +347,6 @@ macro_rules! impl_object_methods { Ok(result) } - #[inline] pub fn has_own_property(&self, key: &str) -> Result { let mut result = false; let string = CString::new(key)?; @@ -401,7 +360,6 @@ macro_rules! impl_object_methods { Ok(result) } - #[inline] pub fn has_own_property_js(&self, key: K) -> Result where K: NapiRaw, @@ -413,7 +371,6 @@ macro_rules! impl_object_methods { Ok(result) } - #[inline] pub fn has_property(&self, name: &str) -> Result { let string = CString::new(name)?; let mut js_key = ptr::null_mut(); @@ -427,7 +384,6 @@ macro_rules! impl_object_methods { Ok(result) } - #[inline] pub fn has_property_js(&self, name: K) -> Result where K: NapiRaw, @@ -439,7 +395,6 @@ macro_rules! impl_object_methods { Ok(result) } - #[inline] pub fn get_property_names(&self) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { @@ -451,7 +406,6 @@ macro_rules! impl_object_methods { /// https://nodejs.org/api/n-api.html#n_api_napi_get_all_property_names /// return `Array` of property names #[cfg(feature = "napi6")] - #[inline] pub fn get_all_property_names( &self, mode: KeyCollectionMode, @@ -473,7 +427,6 @@ macro_rules! impl_object_methods { } /// This returns the equivalent of `Object.getPrototypeOf` (which is not the same as the function's prototype property). - #[inline] pub fn get_prototype(&self) -> Result where T: NapiValue, @@ -483,7 +436,6 @@ macro_rules! impl_object_methods { unsafe { T::from_raw(self.0.env, result) } } - #[inline] pub fn get_prototype_unchecked(&self) -> Result where T: NapiValue, @@ -493,7 +445,6 @@ macro_rules! impl_object_methods { Ok(unsafe { T::from_raw_unchecked(self.0.env, result) }) } - #[inline] pub fn set_element(&mut self, index: u32, value: T) -> Result<()> where T: NapiRaw, @@ -503,7 +454,6 @@ macro_rules! impl_object_methods { }) } - #[inline] pub fn has_element(&self, index: u32) -> Result { let mut result = false; check_status!(unsafe { @@ -512,7 +462,6 @@ macro_rules! impl_object_methods { Ok(result) } - #[inline] pub fn delete_element(&mut self, index: u32) -> Result { let mut result = false; check_status!(unsafe { @@ -521,7 +470,6 @@ macro_rules! impl_object_methods { Ok(result) } - #[inline] pub fn get_element(&self, index: u32) -> Result where T: NapiValue, @@ -533,7 +481,6 @@ macro_rules! impl_object_methods { unsafe { T::from_raw(self.0.env, raw_value) } } - #[inline] pub fn get_element_unchecked(&self, index: u32) -> Result where T: NapiValue, @@ -546,7 +493,6 @@ macro_rules! impl_object_methods { } /// This method allows the efficient definition of multiple properties on a given object. - #[inline] pub fn define_properties(&mut self, properties: &[Property]) -> Result<()> { check_status!(unsafe { sys::napi_define_properties( @@ -564,7 +510,6 @@ macro_rules! impl_object_methods { /// Perform `is_array` check before get the length /// if `Object` is not array, `ArrayExpected` error returned - #[inline] pub fn get_array_length(&self) -> Result { if self.is_array()? != true { return Err(Error::new( @@ -576,7 +521,6 @@ macro_rules! impl_object_methods { } /// use this API if you can ensure this `Object` is `Array` - #[inline] pub fn get_array_length_unchecked(&self) -> Result { let mut length: u32 = 0; check_status!(unsafe { @@ -648,7 +592,6 @@ impl_napi_value_trait!(JsExternal, External); impl_napi_value_trait!(JsSymbol, Symbol); impl NapiValue for JsUnknown { - #[inline] unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result { Ok(JsUnknown(Value { env, @@ -657,7 +600,6 @@ impl NapiValue for JsUnknown { })) } - #[inline] unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self { JsUnknown(Value { env, @@ -669,7 +611,6 @@ impl NapiValue for JsUnknown { impl NapiRaw for JsUnknown { /// get raw js value ptr - #[inline] unsafe fn raw(&self) -> sys::napi_value { self.0.value } @@ -677,19 +618,16 @@ impl NapiRaw for JsUnknown { impl<'env> NapiRaw for &'env JsUnknown { /// get raw js value ptr - #[inline] unsafe fn raw(&self) -> sys::napi_value { self.0.value } } impl JsUnknown { - #[inline] pub fn get_type(&self) -> Result { unsafe { type_of!(self.0.env, self.0.value) } } - #[inline] /// # Safety /// /// This function should be called after `JsUnknown::get_type` diff --git a/napi/src/js_values/number.rs b/crates/napi/src/js_values/number.rs similarity index 97% rename from napi/src/js_values/number.rs rename to crates/napi/src/js_values/number.rs index 6e04c2cc..1b5782b4 100644 --- a/napi/src/js_values/number.rs +++ b/crates/napi/src/js_values/number.rs @@ -8,28 +8,24 @@ use crate::{sys, Error, Result}; pub struct JsNumber(pub(crate) Value); impl JsNumber { - #[inline] pub fn get_uint32(&self) -> Result { let mut result = 0; check_status!(unsafe { sys::napi_get_value_uint32(self.0.env, self.0.value, &mut result) })?; Ok(result) } - #[inline] pub fn get_int32(&self) -> Result { let mut result = 0; check_status!(unsafe { sys::napi_get_value_int32(self.0.env, self.0.value, &mut result) })?; Ok(result) } - #[inline] pub fn get_int64(&self) -> Result { let mut result = 0; check_status!(unsafe { sys::napi_get_value_int64(self.0.env, self.0.value, &mut result) })?; Ok(result) } - #[inline] pub fn get_double(&self) -> Result { let mut result = 0_f64; check_status!(unsafe { sys::napi_get_value_double(self.0.env, self.0.value, &mut result) })?; diff --git a/napi/src/js_values/object.rs b/crates/napi/src/js_values/object.rs similarity index 99% rename from napi/src/js_values/object.rs rename to crates/napi/src/js_values/object.rs index 431d8cbd..59fbab1a 100644 --- a/napi/src/js_values/object.rs +++ b/crates/napi/src/js_values/object.rs @@ -76,7 +76,7 @@ unsafe extern "C" fn finalize_callback( let (value, callback, raw_ref) = *Box::from_raw(finalize_data as *mut (T, F, sys::napi_ref)); let hint = *Box::from_raw(finalize_hint as *mut Hint); let env = Env::from_raw(raw_env); - callback(FinalizeContext { value, hint, env }); + callback(FinalizeContext { env, value, hint }); if !raw_ref.is_null() { let status = sys::napi_delete_reference(raw_env, raw_ref); debug_assert!( diff --git a/napi/src/js_values/object_property.rs b/crates/napi/src/js_values/object_property.rs similarity index 54% rename from napi/src/js_values/object_property.rs rename to crates/napi/src/js_values/object_property.rs index 8a68b745..0b824738 100644 --- a/napi/src/js_values/object_property.rs +++ b/crates/napi/src/js_values/object_property.rs @@ -2,12 +2,16 @@ use std::convert::From; use std::ffi::CString; use std::ptr; -use crate::{check_status, sys, Callback, Env, NapiRaw, Result}; +use crate::{sys, Callback, Result}; -#[derive(Clone, Copy)] -pub struct Property<'env> { - pub name: &'env str, - pub(crate) raw_descriptor: sys::napi_property_descriptor, +#[derive(Clone, Default)] +pub struct Property { + pub name: CString, + getter: sys::napi_callback, + setter: sys::napi_callback, + method: sys::napi_callback, + attrs: PropertyAttributes, + pub(crate) is_ctor: bool, } #[repr(u32)] @@ -20,6 +24,12 @@ pub enum PropertyAttributes { Static = sys::napi_property_attributes::napi_static as _, } +impl Default for PropertyAttributes { + fn default() -> Self { + PropertyAttributes::Default + } +} + impl From for sys::napi_property_attributes { fn from(value: PropertyAttributes) -> Self { match value { @@ -32,61 +42,55 @@ impl From for sys::napi_property_attributes { } } -impl<'env> Property<'env> { - #[inline] - pub fn new(env: &'env Env, name: &'env str) -> Result { - let string_value = CString::new(name)?; - let mut result = ptr::null_mut(); - check_status!(unsafe { - sys::napi_create_string_utf8(env.0, string_value.as_ptr(), name.len(), &mut result) - })?; +impl Property { + pub fn new(name: &str) -> Result { Ok(Property { - name, - raw_descriptor: sys::napi_property_descriptor { - utf8name: ptr::null_mut(), - name: result, - method: None, - getter: None, - setter: None, - value: ptr::null_mut(), - attributes: sys::napi_property_attributes::napi_default, - data: ptr::null_mut(), - }, + name: CString::new(name)?, + ..Default::default() }) } - #[inline] - pub fn with_value(mut self, value: T) -> Self { - self.raw_descriptor.value = unsafe { T::raw(&value) }; + pub fn with_name(mut self, name: &str) -> Self { + self.name = CString::new(name).unwrap(); self } - #[inline] pub fn with_method(mut self, callback: Callback) -> Self { - self.raw_descriptor.method = Some(callback); + self.method = Some(callback); self } - #[inline] pub fn with_getter(mut self, callback: Callback) -> Self { - self.raw_descriptor.getter = Some(callback); + self.getter = Some(callback); self } - #[inline] pub fn with_setter(mut self, callback: Callback) -> Self { - self.raw_descriptor.setter = Some(callback); + self.setter = Some(callback); self } - #[inline] pub fn with_property_attributes(mut self, attributes: PropertyAttributes) -> Self { - self.raw_descriptor.attributes = attributes.into(); + self.attrs = attributes; self } - #[inline] pub(crate) fn raw(&self) -> sys::napi_property_descriptor { - self.raw_descriptor + sys::napi_property_descriptor { + utf8name: self.name.as_ptr(), + name: ptr::null_mut(), + method: self.method, + getter: self.getter, + setter: self.setter, + value: ptr::null_mut(), + attributes: self.attrs.into(), + data: ptr::null_mut(), + } + } + + pub fn with_ctor(mut self, callback: Callback) -> Self { + self.method = Some(callback); + self.is_ctor = true; + self } } diff --git a/napi/src/js_values/ser.rs b/crates/napi/src/js_values/ser.rs similarity index 95% rename from napi/src/js_values/ser.rs rename to crates/napi/src/js_values/ser.rs index 3bc30316..237c17ab 100644 --- a/napi/src/js_values/ser.rs +++ b/crates/napi/src/js_values/ser.rs @@ -27,12 +27,10 @@ impl<'env> Serializer for Ser<'env> { type SerializeStruct = StructSerializer; type SerializeStructVariant = StructSerializer; - #[inline] fn serialize_bool(self, v: bool) -> Result { self.0.get_boolean(v).map(|js_value| js_value.0) } - #[inline] fn serialize_bytes(self, v: &[u8]) -> Result { self .0 @@ -40,54 +38,44 @@ impl<'env> Serializer for Ser<'env> { .map(|js_value| js_value.value.0) } - #[inline] fn serialize_char(self, v: char) -> Result { let mut b = [0; 4]; let result = v.encode_utf8(&mut b); self.0.create_string(result).map(|js_string| js_string.0) } - #[inline] fn serialize_f32(self, v: f32) -> Result { self.0.create_double(v as _).map(|js_number| js_number.0) } - #[inline] fn serialize_f64(self, v: f64) -> Result { self.0.create_double(v).map(|js_number| js_number.0) } - #[inline] fn serialize_i16(self, v: i16) -> Result { self.0.create_int32(v as _).map(|js_number| js_number.0) } - #[inline] fn serialize_i32(self, v: i32) -> Result { self.0.create_int32(v).map(|js_number| js_number.0) } - #[inline] fn serialize_i64(self, v: i64) -> Result { self.0.create_int64(v).map(|js_number| js_number.0) } - #[inline] fn serialize_i8(self, v: i8) -> Result { self.0.create_int32(v as _).map(|js_number| js_number.0) } - #[inline] fn serialize_u8(self, v: u8) -> Result { self.0.create_uint32(v as _).map(|js_number| js_number.0) } - #[inline] fn serialize_u16(self, v: u16) -> Result { self.0.create_uint32(v as _).map(|js_number| js_number.0) } - #[inline] fn serialize_u32(self, v: u32) -> Result { self.0.create_uint32(v).map(|js_number| js_number.0) } @@ -101,13 +89,11 @@ impl<'env> Serializer for Ser<'env> { ), not(feature = "napi6") ))] - #[inline] fn serialize_u64(self, v: u64) -> Result { self.0.create_int64(v as _).map(|js_number| js_number.0) } #[cfg(feature = "napi6")] - #[inline] fn serialize_u64(self, v: u64) -> Result { self .0 @@ -124,13 +110,11 @@ impl<'env> Serializer for Ser<'env> { ), not(feature = "napi6") ))] - #[inline] fn serialize_u128(self, v: u128) -> Result { self.0.create_string(v.to_string().as_str()).map(|v| v.0) } #[cfg(feature = "napi6")] - #[inline] fn serialize_u128(self, v: u128) -> Result { let words_ref = &v as *const _; let words = unsafe { slice::from_raw_parts(words_ref as *const u64, 2) }; @@ -149,13 +133,11 @@ impl<'env> Serializer for Ser<'env> { ), not(feature = "napi6") ))] - #[inline] fn serialize_i128(self, v: i128) -> Result { self.0.create_string(v.to_string().as_str()).map(|v| v.0) } #[cfg(feature = "napi6")] - #[inline] fn serialize_i128(self, v: i128) -> Result { let words_ref = &(v as u128) as *const _; let words = unsafe { slice::from_raw_parts(words_ref as *const u64, 2) }; @@ -165,22 +147,18 @@ impl<'env> Serializer for Ser<'env> { .map(|v| v.raw) } - #[inline] fn serialize_unit(self) -> Result { self.0.get_null().map(|null| null.0) } - #[inline] fn serialize_none(self) -> Result { self.0.get_null().map(|null| null.0) } - #[inline] fn serialize_str(self, v: &str) -> Result { self.0.create_string(v).map(|string| string.0) } - #[inline] fn serialize_some(self, value: &T) -> Result where T: Serialize, @@ -188,7 +166,6 @@ impl<'env> Serializer for Ser<'env> { value.serialize(self) } - #[inline] fn serialize_map(self, _len: Option) -> Result { let env = self.0; let key = env.create_string("")?; @@ -196,7 +173,6 @@ impl<'env> Serializer for Ser<'env> { Ok(MapSerializer { key, obj }) } - #[inline] fn serialize_seq(self, len: Option) -> Result { let array = self.0.create_array_with_length(len.unwrap_or(0))?; Ok(SeqSerializer { @@ -205,7 +181,6 @@ impl<'env> Serializer for Ser<'env> { }) } - #[inline] fn serialize_tuple_variant( self, _name: &'static str, @@ -230,12 +205,10 @@ impl<'env> Serializer for Ser<'env> { }) } - #[inline] fn serialize_unit_struct(self, _name: &'static str) -> Result { self.0.get_null().map(|null| null.0) } - #[inline] fn serialize_unit_variant( self, _name: &'static str, @@ -245,7 +218,6 @@ impl<'env> Serializer for Ser<'env> { self.0.create_string(variant).map(|string| string.0) } - #[inline] fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result where T: Serialize, @@ -253,7 +225,6 @@ impl<'env> Serializer for Ser<'env> { value.serialize(self) } - #[inline] fn serialize_newtype_variant( self, _name: &'static str, @@ -269,7 +240,6 @@ impl<'env> Serializer for Ser<'env> { Ok(obj.0) } - #[inline] fn serialize_tuple(self, len: usize) -> Result { Ok(SeqSerializer { array: self.0.create_array_with_length(len)?, @@ -277,7 +247,6 @@ impl<'env> Serializer for Ser<'env> { }) } - #[inline] fn serialize_tuple_struct( self, _name: &'static str, @@ -289,14 +258,12 @@ impl<'env> Serializer for Ser<'env> { }) } - #[inline] fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { Ok(StructSerializer { obj: self.0.create_object()?, }) } - #[inline] fn serialize_struct_variant( self, _name: &'static str, @@ -352,7 +319,6 @@ impl ser::SerializeTuple for SeqSerializer { type Ok = Value; type Error = Error; - #[inline] fn serialize_element(&mut self, value: &T) -> StdResult<(), Self::Error> where T: Serialize, @@ -366,7 +332,6 @@ impl ser::SerializeTuple for SeqSerializer { Ok(()) } - #[inline] fn end(self) -> StdResult { Ok(self.array.0) } @@ -377,7 +342,6 @@ impl ser::SerializeTupleStruct for SeqSerializer { type Ok = Value; type Error = Error; - #[inline] fn serialize_field(&mut self, value: &T) -> StdResult<(), Self::Error> where T: Serialize, @@ -391,7 +355,6 @@ impl ser::SerializeTupleStruct for SeqSerializer { Ok(()) } - #[inline] fn end(self) -> StdResult { Ok(self.array.0) } @@ -402,7 +365,6 @@ impl ser::SerializeTupleVariant for SeqSerializer { type Ok = Value; type Error = Error; - #[inline] fn serialize_field(&mut self, value: &T) -> StdResult<(), Self::Error> where T: Serialize, @@ -416,7 +378,6 @@ impl ser::SerializeTupleVariant for SeqSerializer { Ok(()) } - #[inline] fn end(self) -> Result { Ok(self.array.0) } @@ -432,7 +393,6 @@ impl ser::SerializeMap for MapSerializer { type Ok = Value; type Error = Error; - #[inline] fn serialize_key(&mut self, key: &T) -> StdResult<(), Self::Error> where T: Serialize, @@ -442,7 +402,6 @@ impl ser::SerializeMap for MapSerializer { Ok(()) } - #[inline] fn serialize_value(&mut self, value: &T) -> StdResult<(), Self::Error> where T: Serialize, @@ -459,7 +418,6 @@ impl ser::SerializeMap for MapSerializer { Ok(()) } - #[inline] fn serialize_entry( &mut self, key: &K, @@ -477,7 +435,6 @@ impl ser::SerializeMap for MapSerializer { Ok(()) } - #[inline] fn end(self) -> Result { Ok(self.obj.0) } @@ -492,7 +449,6 @@ impl ser::SerializeStruct for StructSerializer { type Ok = Value; type Error = Error; - #[inline] fn serialize_field(&mut self, key: &'static str, value: &T) -> StdResult<(), Error> where T: Serialize, @@ -504,7 +460,6 @@ impl ser::SerializeStruct for StructSerializer { Ok(()) } - #[inline] fn end(self) -> Result { Ok(self.obj.0) } @@ -515,7 +470,6 @@ impl ser::SerializeStructVariant for StructSerializer { type Ok = Value; type Error = Error; - #[inline] fn serialize_field(&mut self, key: &'static str, value: &T) -> StdResult<(), Error> where T: Serialize, @@ -527,7 +481,6 @@ impl ser::SerializeStructVariant for StructSerializer { Ok(()) } - #[inline] fn end(self) -> Result { Ok(self.obj.0) } diff --git a/napi/src/js_values/string/latin1.rs b/crates/napi/src/js_values/string/latin1.rs similarity index 92% rename from napi/src/js_values/string/latin1.rs rename to crates/napi/src/js_values/string/latin1.rs index d39f5736..681544b1 100644 --- a/napi/src/js_values/string/latin1.rs +++ b/crates/napi/src/js_values/string/latin1.rs @@ -11,33 +11,27 @@ pub struct JsStringLatin1 { } impl JsStringLatin1 { - #[inline] pub fn as_slice(&self) -> &[u8] { &self.buf } - #[inline] pub fn len(&self) -> usize { self.buf.len() } - #[inline] pub fn is_empty(&self) -> bool { self.buf.is_empty() } - #[inline] pub fn take(self) -> Vec { self.as_slice().to_vec() } - #[inline] pub fn into_value(self) -> JsString { self.inner } #[cfg(feature = "latin1")] - #[inline] pub fn into_latin1_string(self) -> Result { let mut dst_str = unsafe { String::from_utf8_unchecked(vec![0; self.len() * 2 + 1]) }; encoding_rs::mem::convert_latin1_to_str(self.buf.as_slice(), dst_str.as_mut_str()); diff --git a/napi/src/js_values/string/mod.rs b/crates/napi/src/js_values/string/mod.rs similarity index 97% rename from napi/src/js_values/string/mod.rs rename to crates/napi/src/js_values/string/mod.rs index 88ddf2e6..726c5b2c 100644 --- a/napi/src/js_values/string/mod.rs +++ b/crates/napi/src/js_values/string/mod.rs @@ -15,7 +15,6 @@ mod utf8; pub struct JsString(pub(crate) Value); impl JsString { - #[inline] pub fn utf8_len(&self) -> Result { let mut length = 0; check_status!(unsafe { @@ -24,7 +23,6 @@ impl JsString { Ok(length as usize) } - #[inline] pub fn utf16_len(&self) -> Result { let mut length = 0; check_status!(unsafe { @@ -33,7 +31,6 @@ impl JsString { Ok(length as usize) } - #[inline] pub fn latin1_len(&self) -> Result { let mut length = 0; check_status!(unsafe { @@ -42,7 +39,6 @@ impl JsString { Ok(length as usize) } - #[inline] pub fn into_utf8(self) -> Result { let mut written_char_count = 0; let len = self.utf8_len()? + 1; @@ -64,7 +60,6 @@ impl JsString { }) } - #[inline] pub fn into_utf16(self) -> Result { let mut written_char_count = 0usize; let len = self.utf16_len()? + 1; @@ -86,7 +81,6 @@ impl JsString { }) } - #[inline] pub fn into_latin1(self) -> Result { let mut written_char_count = 0usize; let len = self.latin1_len()? + 1; diff --git a/napi/src/js_values/string/utf16.rs b/crates/napi/src/js_values/string/utf16.rs similarity index 95% rename from napi/src/js_values/string/utf16.rs rename to crates/napi/src/js_values/string/utf16.rs index 3d51a921..4fc94426 100644 --- a/napi/src/js_values/string/utf16.rs +++ b/crates/napi/src/js_values/string/utf16.rs @@ -9,7 +9,6 @@ pub struct JsStringUtf16 { } impl JsStringUtf16 { - #[inline] pub fn as_str(&self) -> Result { if let Some((_, prefix)) = self.as_slice().split_last() { String::from_utf16(prefix).map_err(|e| Error::new(Status::InvalidArg, format!("{}", e))) @@ -18,22 +17,18 @@ impl JsStringUtf16 { } } - #[inline] pub fn as_slice(&self) -> &[u16] { self.buf.as_slice() } - #[inline] pub fn len(&self) -> usize { self.buf.len() } - #[inline] pub fn is_empty(&self) -> bool { self.buf.is_empty() } - #[inline] pub fn into_value(self) -> JsString { self.inner } diff --git a/napi/src/js_values/string/utf8.rs b/crates/napi/src/js_values/string/utf8.rs similarity index 92% rename from napi/src/js_values/string/utf8.rs rename to crates/napi/src/js_values/string/utf8.rs index 435d2720..56f18f61 100644 --- a/napi/src/js_values/string/utf8.rs +++ b/crates/napi/src/js_values/string/utf8.rs @@ -11,39 +11,32 @@ pub struct JsStringUtf8 { } impl JsStringUtf8 { - #[inline] pub fn as_str(&self) -> Result<&str> { unsafe { CStr::from_ptr(self.buf.as_ptr()) } .to_str() .map_err(|e| Error::new(Status::InvalidArg, format!("{}", e))) } - #[inline] pub fn as_slice(&self) -> &[u8] { unsafe { CStr::from_ptr(self.buf.as_ptr()) }.to_bytes() } - #[inline] pub fn len(&self) -> usize { self.buf.len() } - #[inline] pub fn is_empty(&self) -> bool { self.buf.is_empty() } - #[inline] pub fn into_owned(self) -> Result { Ok(self.as_str()?.to_owned()) } - #[inline] pub fn take(self) -> Vec { self.as_slice().to_vec() } - #[inline] pub fn into_value(self) -> JsString { self.inner } diff --git a/napi/src/js_values/tagged_object.rs b/crates/napi/src/js_values/tagged_object.rs similarity index 100% rename from napi/src/js_values/tagged_object.rs rename to crates/napi/src/js_values/tagged_object.rs diff --git a/napi/src/js_values/undefined.rs b/crates/napi/src/js_values/undefined.rs similarity index 100% rename from napi/src/js_values/undefined.rs rename to crates/napi/src/js_values/undefined.rs diff --git a/napi/src/js_values/value.rs b/crates/napi/src/js_values/value.rs similarity index 100% rename from napi/src/js_values/value.rs rename to crates/napi/src/js_values/value.rs diff --git a/napi/src/js_values/value_ref.rs b/crates/napi/src/js_values/value_ref.rs similarity index 97% rename from napi/src/js_values/value_ref.rs rename to crates/napi/src/js_values/value_ref.rs index f5da1d42..198e0293 100644 --- a/napi/src/js_values/value_ref.rs +++ b/crates/napi/src/js_values/value_ref.rs @@ -15,7 +15,6 @@ unsafe impl Send for Ref {} unsafe impl Sync for Ref {} impl Ref { - #[inline] pub(crate) fn new(js_value: Value, ref_count: u32, inner: T) -> Result> { let mut raw_ref = ptr::null_mut(); assert_ne!(ref_count, 0, "Initial `ref_count` must be > 0"); @@ -30,13 +29,11 @@ impl Ref { }) } - #[inline] pub fn reference(&mut self, env: &Env) -> Result { check_status!(unsafe { sys::napi_reference_ref(env.0, self.raw_ref, &mut self.count) })?; Ok(self.count) } - #[inline] pub fn unref(mut self, env: Env) -> Result { check_status!(unsafe { sys::napi_reference_unref(env.0, self.raw_ref, &mut self.count) })?; diff --git a/napi/src/lib.rs b/crates/napi/src/lib.rs similarity index 55% rename from napi/src/lib.rs rename to crates/napi/src/lib.rs index 0e8dc012..e97f3c8f 100644 --- a/napi/src/lib.rs +++ b/crates/napi/src/lib.rs @@ -1,6 +1,6 @@ #![deny(clippy::all)] -//! High level NodeJS [N-API](https://nodejs.org/api/n-api.html) binding +//! High level Node.js [N-API](https://nodejs.org/api/n-api.html) binding //! //! **napi-rs** provides minimal overhead to write N-API modules in `Rust`. //! @@ -8,7 +8,7 @@ //! //! ### napi1 ~ napi8 //! -//! Because `NodeJS` N-API has versions. So there are feature flags to choose what version of `N-API` you want to build for. +//! Because `Node.js` N-API has versions. So there are feature flags to choose what version of `N-API` you want to build for. //! For example, if you want build a library which can be used by `node@10.17.0`, you should choose the `napi5` or lower. //! //! The details of N-API versions and support matrix: [n_api_version_matrix](https://nodejs.org/api/n-api.html#n_api_n_api_version_matrix) @@ -77,20 +77,23 @@ #[cfg(feature = "napi8")] mod async_cleanup_hook; + #[cfg(feature = "napi8")] pub use async_cleanup_hook::AsyncCleanupHook; mod async_work; +mod bindgen_runtime; mod call_context; #[cfg(feature = "napi3")] mod cleanup_env; mod env; mod error; mod js_values; -mod module; + #[cfg(all(feature = "tokio_rt", feature = "napi4"))] mod promise; mod status; mod task; +mod value_type; #[cfg(feature = "napi3")] pub use cleanup_env::CleanupEnvHook; #[cfg(feature = "napi4")] @@ -104,88 +107,55 @@ pub use napi_sys as sys; pub use async_work::AsyncWorkPromise; pub use call_context::CallContext; + pub use env::*; -pub use error::{Error, ExtendedErrorInfo, Result}; +pub use error::*; pub use js_values::*; -pub use module::Module; pub use status::Status; pub use task::Task; +pub use value_type::*; pub use version::NodeVersion; - #[cfg(feature = "serde-json")] #[macro_use] extern crate serde; pub type ContextlessResult = Result>; -/// Deprecated -/// register nodejs module -/// -/// ## Example -/// ``` -/// register_module!(test_module, init); -/// -/// fn init(module: &mut Module) -> Result<()> { -/// module.create_named_method("nativeFunction", native_function)?; -/// } -/// ``` +#[doc(hidden)] +#[macro_export(local_inner_macros)] +macro_rules! type_of { + ($env:expr, $value:expr) => {{ + let mut value_type = 0; + check_status!($crate::sys::napi_typeof($env, $value, &mut value_type)) + .and_then(|_| Ok($crate::ValueType::from(value_type))) + }}; +} + +#[doc(hidden)] #[macro_export] -#[deprecated(since = "1.0.0", note = "[module_exports] macro instead")] -macro_rules! register_module { - ($module_name:ident, $init:ident) => { - #[inline] - #[cfg(all(feature = "tokio_rt", feature = "napi4"))] - fn check_status(code: $crate::sys::napi_status) -> Result<()> { - use $crate::{Error, Status}; - let status = Status::from(code); - match status { - Status::Ok => Ok(()), - _ => Err(Error::from_status(status)), +macro_rules! assert_type_of { + ($env: expr, $value:expr, $value_ty: expr) => { + $crate::type_of!($env, $value).and_then(|received_type| { + if received_type == $value_ty { + Ok(()) + } else { + Err($crate::Error::new( + $crate::Status::InvalidArg, + format!( + "Expect value to be {}, but received {}", + $value_ty, received_type + ), + )) } - } - - #[no_mangle] - unsafe extern "C" fn napi_register_module_v1( - raw_env: $crate::sys::napi_env, - raw_exports: $crate::sys::napi_value, - ) -> $crate::sys::napi_value { - use std::ffi::CString; - use std::io::Write; - use std::os::raw::c_char; - use std::ptr; - use $crate::{Env, JsObject, NapiValue}; - - #[cfg(all(feature = "tokio_rt", feature = "napi4"))] - use $crate::shutdown_tokio_rt; - - if cfg!(debug_assertions) { - println!("`register_module` macro will deprecate soon, please migrate to [module_exports]"); - } - - let env = Env::from_raw(raw_env); - let mut exports: JsObject = JsObject::from_raw_unchecked(raw_env, raw_exports); - let mut cjs_module = Module { env, exports }; - let result = $init(&mut cjs_module); - #[cfg(all(feature = "tokio_rt", feature = "napi4"))] - let hook_result = check_status(unsafe { - $crate::sys::napi_add_env_cleanup_hook(raw_env, Some(shutdown_tokio_rt), ptr::null_mut()) - }); - #[cfg(not(all(feature = "tokio_rt", feature = "napi4")))] - let hook_result = Ok(()); - match hook_result.and_then(move |_| result) { - Ok(_) => cjs_module.exports.raw(), - Err(e) => { - unsafe { - $crate::sys::napi_throw_error( - raw_env, - ptr::null(), - CString::from_vec_unchecked(format!("Error initializing module: {}", e).into()) - .as_ptr(), - ) - }; - ptr::null_mut() - } - } - } + }) + }; +} + +pub mod bindgen_prelude { + #[cfg(feature = "compat-mode")] + pub use crate::bindgen_runtime::register_module_exports; + pub use crate::{ + assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys, + type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType, }; } diff --git a/napi/src/promise.rs b/crates/napi/src/promise.rs similarity index 98% rename from napi/src/promise.rs rename to crates/napi/src/promise.rs index 63e538ee..4b068347 100644 --- a/napi/src/promise.rs +++ b/crates/napi/src/promise.rs @@ -18,7 +18,6 @@ pub struct FuturePromise Result> { unsafe impl Result> Send for FuturePromise {} impl Result> FuturePromise { - #[inline] pub fn create(env: sys::napi_env, raw_deferred: sys::napi_deferred, resolver: F) -> Result { let mut async_resource_name = ptr::null_mut(); let s = "napi_resolve_promise_from_future"; @@ -42,7 +41,6 @@ impl Result> FuturePromise }) } - #[inline] pub(crate) fn start(self) -> Result { let mut tsfn_value = ptr::null_mut(); let async_resource_name = self.async_resource_name; @@ -72,7 +70,6 @@ pub(crate) struct TSFNValue(sys::napi_threadsafe_function); unsafe impl Send for TSFNValue {} -#[inline(always)] pub(crate) async fn resolve_from_future>>( tsfn_value: TSFNValue, fut: F, diff --git a/napi/src/status.rs b/crates/napi/src/status.rs similarity index 100% rename from napi/src/status.rs rename to crates/napi/src/status.rs diff --git a/napi/src/task.rs b/crates/napi/src/task.rs similarity index 100% rename from napi/src/task.rs rename to crates/napi/src/task.rs diff --git a/napi/src/threadsafe_function.rs b/crates/napi/src/threadsafe_function.rs similarity index 99% rename from napi/src/threadsafe_function.rs rename to crates/napi/src/threadsafe_function.rs index 607ce3fb..f68a08d1 100644 --- a/napi/src/threadsafe_function.rs +++ b/crates/napi/src/threadsafe_function.rs @@ -181,7 +181,6 @@ unsafe impl Sync for ThreadsafeFunction {} impl ThreadsafeFunction { /// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function) /// for more information. - #[inline] pub fn create< V: NapiRaw, R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, diff --git a/napi/src/js_values/value_type.rs b/crates/napi/src/value_type.rs similarity index 100% rename from napi/src/js_values/value_type.rs rename to crates/napi/src/value_type.rs diff --git a/napi/src/version.rs b/crates/napi/src/version.rs similarity index 100% rename from napi/src/version.rs rename to crates/napi/src/version.rs diff --git a/napi/src/win_delay_load_hook.rs b/crates/napi/src/win_delay_load_hook.rs similarity index 100% rename from napi/src/win_delay_load_hook.rs rename to crates/napi/src/win_delay_load_hook.rs diff --git a/sys/Cargo.toml b/crates/sys/Cargo.toml similarity index 100% rename from sys/Cargo.toml rename to crates/sys/Cargo.toml diff --git a/sys/README.md b/crates/sys/README.md similarity index 85% rename from sys/README.md rename to crates/sys/README.md index 49a6e024..1c3ae4f7 100644 --- a/sys/README.md +++ b/crates/sys/README.md @@ -9,4 +9,4 @@ Low-level N-API bindings for Node.js addons written in Rust. -See the [napi](https://github.com/napi-rs/napi-rs) for the high-level API. +See the [napi](https://nodejs.org/api/n-api.html) for the high-level API. diff --git a/sys/src/lib.rs b/crates/sys/src/lib.rs similarity index 99% rename from sys/src/lib.rs rename to crates/sys/src/lib.rs index 9f6a2f2d..d45c8535 100644 --- a/sys/src/lib.rs +++ b/crates/sys/src/lib.rs @@ -58,7 +58,7 @@ pub struct uv_loop_s { pub type napi_deferred = *mut napi_deferred__; #[repr(C)] -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum napi_property_attributes { napi_default = 0, napi_writable = 1 << 0, @@ -137,7 +137,7 @@ pub type napi_finalize = Option< unsafe extern "C" fn(env: napi_env, finalize_data: *mut c_void, finalize_hint: *mut c_void), >; #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct napi_property_descriptor { pub utf8name: *const c_char, pub name: napi_value, diff --git a/test_module/Cargo.toml b/examples/napi-compat-mode/Cargo.toml similarity index 58% rename from test_module/Cargo.toml rename to examples/napi-compat-mode/Cargo.toml index ade169e5..e4f1bc4f 100644 --- a/test_module/Cargo.toml +++ b/examples/napi-compat-mode/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["LongYinan "] edition = "2018" -name = "napi-test-module" +name = "napi-compat-mode-examples" version = "0.1.0" [lib] @@ -13,8 +13,8 @@ napi3 = ["napi/napi3"] [dependencies] futures = "0.3" -napi = {path = "../napi", features = ["tokio_rt", "serde-json", "latin1"]} -napi-derive = {path = "../napi-derive"} +napi = {path = "../../crates/napi", features = ["tokio_rt", "serde-json", "latin1", "compat-mode"]} +napi-derive = {path = "../../crates/macro", features = ["compat-mode"]} serde = "1" serde_bytes = "0.11" serde_derive = "1" @@ -22,4 +22,4 @@ serde_json = "1" tokio = {version = "1", features = ["default", "fs"]} [build-dependencies] -napi-build = {path = "../build"} +napi-build = {path = "../../crates/build"} diff --git a/test_module/__test__/array.spec.ts b/examples/napi-compat-mode/__test__/array.spec.ts similarity index 100% rename from test_module/__test__/array.spec.ts rename to examples/napi-compat-mode/__test__/array.spec.ts diff --git a/test_module/__test__/arraybuffer.spec.ts b/examples/napi-compat-mode/__test__/arraybuffer.spec.ts similarity index 100% rename from test_module/__test__/arraybuffer.spec.ts rename to examples/napi-compat-mode/__test__/arraybuffer.spec.ts diff --git a/test_module/__test__/buffer.spec.ts b/examples/napi-compat-mode/__test__/buffer.spec.ts similarity index 100% rename from test_module/__test__/buffer.spec.ts rename to examples/napi-compat-mode/__test__/buffer.spec.ts diff --git a/test_module/__test__/class.spec.ts b/examples/napi-compat-mode/__test__/class.spec.ts similarity index 100% rename from test_module/__test__/class.spec.ts rename to examples/napi-compat-mode/__test__/class.spec.ts diff --git a/test_module/__test__/cleanup-env.spec.ts b/examples/napi-compat-mode/__test__/cleanup-env.spec.ts similarity index 100% rename from test_module/__test__/cleanup-env.spec.ts rename to examples/napi-compat-mode/__test__/cleanup-env.spec.ts diff --git a/test_module/__test__/create-external.spec.ts b/examples/napi-compat-mode/__test__/create-external.spec.ts similarity index 100% rename from test_module/__test__/create-external.spec.ts rename to examples/napi-compat-mode/__test__/create-external.spec.ts diff --git a/test_module/__test__/either.spec.ts b/examples/napi-compat-mode/__test__/either.spec.ts similarity index 100% rename from test_module/__test__/either.spec.ts rename to examples/napi-compat-mode/__test__/either.spec.ts diff --git a/test_module/__test__/env.spec.ts b/examples/napi-compat-mode/__test__/env.spec.ts similarity index 100% rename from test_module/__test__/env.spec.ts rename to examples/napi-compat-mode/__test__/env.spec.ts diff --git a/test_module/__test__/function.spec.ts b/examples/napi-compat-mode/__test__/function.spec.ts similarity index 100% rename from test_module/__test__/function.spec.ts rename to examples/napi-compat-mode/__test__/function.spec.ts diff --git a/test_module/__test__/get-napi-version.spec.ts b/examples/napi-compat-mode/__test__/get-napi-version.spec.ts similarity index 100% rename from test_module/__test__/get-napi-version.spec.ts rename to examples/napi-compat-mode/__test__/get-napi-version.spec.ts diff --git a/test_module/__test__/global.spec.ts b/examples/napi-compat-mode/__test__/global.spec.ts similarity index 100% rename from test_module/__test__/global.spec.ts rename to examples/napi-compat-mode/__test__/global.spec.ts diff --git a/test_module/__test__/js-value.spec.ts b/examples/napi-compat-mode/__test__/js-value.spec.ts similarity index 100% rename from test_module/__test__/js-value.spec.ts rename to examples/napi-compat-mode/__test__/js-value.spec.ts diff --git a/test_module/__test__/napi-version.ts b/examples/napi-compat-mode/__test__/napi-version.ts similarity index 100% rename from test_module/__test__/napi-version.ts rename to examples/napi-compat-mode/__test__/napi-version.ts diff --git a/test_module/__test__/napi4/example.txt b/examples/napi-compat-mode/__test__/napi4/example.txt similarity index 100% rename from test_module/__test__/napi4/example.txt rename to examples/napi-compat-mode/__test__/napi4/example.txt diff --git a/test_module/__test__/napi4/threadsafe_function.spec.ts b/examples/napi-compat-mode/__test__/napi4/threadsafe_function.spec.ts similarity index 100% rename from test_module/__test__/napi4/threadsafe_function.spec.ts rename to examples/napi-compat-mode/__test__/napi4/threadsafe_function.spec.ts diff --git a/test_module/__test__/napi4/tokio_readfile.spec.ts b/examples/napi-compat-mode/__test__/napi4/tokio_readfile.spec.ts similarity index 100% rename from test_module/__test__/napi4/tokio_readfile.spec.ts rename to examples/napi-compat-mode/__test__/napi4/tokio_readfile.spec.ts diff --git a/test_module/__test__/napi4/tokio_rt.spec.ts b/examples/napi-compat-mode/__test__/napi4/tokio_rt.spec.ts similarity index 100% rename from test_module/__test__/napi4/tokio_rt.spec.ts rename to examples/napi-compat-mode/__test__/napi4/tokio_rt.spec.ts diff --git a/test_module/__test__/napi4/tsfn-dua-instance.js b/examples/napi-compat-mode/__test__/napi4/tsfn-dua-instance.js similarity index 100% rename from test_module/__test__/napi4/tsfn-dua-instance.js rename to examples/napi-compat-mode/__test__/napi4/tsfn-dua-instance.js diff --git a/test_module/__test__/napi4/tsfn-throw.js b/examples/napi-compat-mode/__test__/napi4/tsfn-throw.js similarity index 100% rename from test_module/__test__/napi4/tsfn-throw.js rename to examples/napi-compat-mode/__test__/napi4/tsfn-throw.js diff --git a/test_module/__test__/napi4/tsfn_error.spec.ts b/examples/napi-compat-mode/__test__/napi4/tsfn_error.spec.ts similarity index 100% rename from test_module/__test__/napi4/tsfn_error.spec.ts rename to examples/napi-compat-mode/__test__/napi4/tsfn_error.spec.ts diff --git a/test_module/__test__/napi5/date.spec.ts b/examples/napi-compat-mode/__test__/napi5/date.spec.ts similarity index 100% rename from test_module/__test__/napi5/date.spec.ts rename to examples/napi-compat-mode/__test__/napi5/date.spec.ts diff --git a/test_module/__test__/napi6/bigint.spec.ts b/examples/napi-compat-mode/__test__/napi6/bigint.spec.ts similarity index 100% rename from test_module/__test__/napi6/bigint.spec.ts rename to examples/napi-compat-mode/__test__/napi6/bigint.spec.ts diff --git a/test_module/__test__/napi6/instance-data.spec.ts b/examples/napi-compat-mode/__test__/napi6/instance-data.spec.ts similarity index 100% rename from test_module/__test__/napi6/instance-data.spec.ts rename to examples/napi-compat-mode/__test__/napi6/instance-data.spec.ts diff --git a/test_module/__test__/napi7/arraybuffer.spec.ts b/examples/napi-compat-mode/__test__/napi7/arraybuffer.spec.ts similarity index 100% rename from test_module/__test__/napi7/arraybuffer.spec.ts rename to examples/napi-compat-mode/__test__/napi7/arraybuffer.spec.ts diff --git a/test_module/__test__/napi8/async-cleanup.spec.ts b/examples/napi-compat-mode/__test__/napi8/async-cleanup.spec.ts similarity index 100% rename from test_module/__test__/napi8/async-cleanup.spec.ts rename to examples/napi-compat-mode/__test__/napi8/async-cleanup.spec.ts diff --git a/test_module/__test__/napi8/object.spec.ts b/examples/napi-compat-mode/__test__/napi8/object.spec.ts similarity index 100% rename from test_module/__test__/napi8/object.spec.ts rename to examples/napi-compat-mode/__test__/napi8/object.spec.ts diff --git a/test_module/__test__/napi8/sub-process-removable.js b/examples/napi-compat-mode/__test__/napi8/sub-process-removable.js similarity index 100% rename from test_module/__test__/napi8/sub-process-removable.js rename to examples/napi-compat-mode/__test__/napi8/sub-process-removable.js diff --git a/test_module/__test__/napi8/sub-process.js b/examples/napi-compat-mode/__test__/napi8/sub-process.js similarity index 100% rename from test_module/__test__/napi8/sub-process.js rename to examples/napi-compat-mode/__test__/napi8/sub-process.js diff --git a/test_module/__test__/object.spec.ts b/examples/napi-compat-mode/__test__/object.spec.ts similarity index 100% rename from test_module/__test__/object.spec.ts rename to examples/napi-compat-mode/__test__/object.spec.ts diff --git a/test_module/__test__/object.spec.ts.md b/examples/napi-compat-mode/__test__/object.spec.ts.md similarity index 87% rename from test_module/__test__/object.spec.ts.md rename to examples/napi-compat-mode/__test__/object.spec.ts.md index 326e4412..991ad250 100644 --- a/test_module/__test__/object.spec.ts.md +++ b/examples/napi-compat-mode/__test__/object.spec.ts.md @@ -1,4 +1,4 @@ -# Snapshot report for `test_module/__test__/object.spec.ts` +# Snapshot report for `examples/napi-compat-mode/__test__/object.spec.ts` The actual snapshot is saved in `object.spec.ts.snap`. diff --git a/test_module/__test__/object.spec.ts.snap b/examples/napi-compat-mode/__test__/object.spec.ts.snap similarity index 100% rename from test_module/__test__/object.spec.ts.snap rename to examples/napi-compat-mode/__test__/object.spec.ts.snap diff --git a/test_module/__test__/serde/de.spec.ts b/examples/napi-compat-mode/__test__/serde/de.spec.ts similarity index 100% rename from test_module/__test__/serde/de.spec.ts rename to examples/napi-compat-mode/__test__/serde/de.spec.ts diff --git a/test_module/__test__/serde/ser.spec.ts b/examples/napi-compat-mode/__test__/serde/ser.spec.ts similarity index 100% rename from test_module/__test__/serde/ser.spec.ts rename to examples/napi-compat-mode/__test__/serde/ser.spec.ts diff --git a/test_module/__test__/serde/ser.spec.ts.md b/examples/napi-compat-mode/__test__/serde/ser.spec.ts.md similarity index 95% rename from test_module/__test__/serde/ser.spec.ts.md rename to examples/napi-compat-mode/__test__/serde/ser.spec.ts.md index 1b511474..acd41102 100644 --- a/test_module/__test__/serde/ser.spec.ts.md +++ b/examples/napi-compat-mode/__test__/serde/ser.spec.ts.md @@ -1,4 +1,4 @@ -# Snapshot report for `test_module/__test__/serde/ser.spec.ts` +# Snapshot report for `examples/napi-compat-mode/__test__/serde/ser.spec.ts` The actual snapshot is saved in `ser.spec.ts.snap`. diff --git a/test_module/__test__/serde/ser.spec.ts.snap b/examples/napi-compat-mode/__test__/serde/ser.spec.ts.snap similarity index 100% rename from test_module/__test__/serde/ser.spec.ts.snap rename to examples/napi-compat-mode/__test__/serde/ser.spec.ts.snap diff --git a/test_module/__test__/serde/serde-json.spec.ts b/examples/napi-compat-mode/__test__/serde/serde-json.spec.ts similarity index 100% rename from test_module/__test__/serde/serde-json.spec.ts rename to examples/napi-compat-mode/__test__/serde/serde-json.spec.ts diff --git a/test_module/__test__/spawn.spec.ts b/examples/napi-compat-mode/__test__/spawn.spec.ts similarity index 100% rename from test_module/__test__/spawn.spec.ts rename to examples/napi-compat-mode/__test__/spawn.spec.ts diff --git a/test_module/__test__/string.spec.ts b/examples/napi-compat-mode/__test__/string.spec.ts similarity index 100% rename from test_module/__test__/string.spec.ts rename to examples/napi-compat-mode/__test__/string.spec.ts diff --git a/test_module/__test__/string.spec.ts.md b/examples/napi-compat-mode/__test__/string.spec.ts.md similarity index 86% rename from test_module/__test__/string.spec.ts.md rename to examples/napi-compat-mode/__test__/string.spec.ts.md index 1669b39d..c3baff29 100644 Binary files a/test_module/__test__/string.spec.ts.md and b/examples/napi-compat-mode/__test__/string.spec.ts.md differ diff --git a/test_module/__test__/string.spec.ts.snap b/examples/napi-compat-mode/__test__/string.spec.ts.snap similarity index 100% rename from test_module/__test__/string.spec.ts.snap rename to examples/napi-compat-mode/__test__/string.spec.ts.snap diff --git a/test_module/__test__/symbol.spec.ts b/examples/napi-compat-mode/__test__/symbol.spec.ts similarity index 100% rename from test_module/__test__/symbol.spec.ts rename to examples/napi-compat-mode/__test__/symbol.spec.ts diff --git a/test_module/__test__/throw.spec.ts b/examples/napi-compat-mode/__test__/throw.spec.ts similarity index 100% rename from test_module/__test__/throw.spec.ts rename to examples/napi-compat-mode/__test__/throw.spec.ts diff --git a/test_module/build.rs b/examples/napi-compat-mode/build.rs similarity index 100% rename from test_module/build.rs rename to examples/napi-compat-mode/build.rs diff --git a/examples/napi-compat-mode/package.json b/examples/napi-compat-mode/package.json new file mode 100644 index 00000000..734142b6 --- /dev/null +++ b/examples/napi-compat-mode/package.json @@ -0,0 +1,14 @@ +{ + "name": "test-module", + "version": "1.0.0", + "scripts": { + "build": "node ../../cli/scripts/index.js build --features \"latest\"", + "build-napi3": "node ../../cli/scripts/index.js build --features \"napi3\"", + "build-aarch64": "node ../../cli/scripts/index.js build --features \"latest\" --target aarch64-unknown-linux-gnu", + "build-armv7": "node ../../cli/scripts/index.js build --features \"latest\" --target armv7-unknown-linux-gnueabihf", + "build-i686": "node ../../cli/scripts/index.js build --features \"latest\" --target i686-pc-windows-msvc", + "build-i686-release": "node ../../cli/scripts/index.js build --release --features \"latest\" --target i686-pc-windows-msvc", + "build-release": "node ../../cli/scripts/index.js build --features \"latest\" --release", + "test": "node ./index.js" + } +} diff --git a/test_module/src/array.rs b/examples/napi-compat-mode/src/array.rs similarity index 100% rename from test_module/src/array.rs rename to examples/napi-compat-mode/src/array.rs diff --git a/test_module/src/arraybuffer.rs b/examples/napi-compat-mode/src/arraybuffer.rs similarity index 100% rename from test_module/src/arraybuffer.rs rename to examples/napi-compat-mode/src/arraybuffer.rs diff --git a/test_module/src/buffer.rs b/examples/napi-compat-mode/src/buffer.rs similarity index 100% rename from test_module/src/buffer.rs rename to examples/napi-compat-mode/src/buffer.rs diff --git a/test_module/src/class.rs b/examples/napi-compat-mode/src/class.rs similarity index 82% rename from test_module/src/class.rs rename to examples/napi-compat-mode/src/class.rs index df34bde4..51c5befc 100644 --- a/test_module/src/class.rs +++ b/examples/napi-compat-mode/src/class.rs @@ -8,9 +8,9 @@ struct NativeClass { #[js_function(1)] fn create_test_class(ctx: CallContext) -> Result { - let add_count_method = Property::new(ctx.env, "addCount")?.with_method(add_count); - let add_native_count = Property::new(ctx.env, "addNativeCount")?.with_method(add_native_count); - let renew_wrapped = Property::new(ctx.env, "renewWrapped")?.with_method(renew_wrapped); + let add_count_method = Property::new("addCount")?.with_method(add_count); + let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count); + let renew_wrapped = Property::new("renewWrapped")?.with_method(renew_wrapped); ctx.env.define_class( "TestClass", test_class_constructor, @@ -57,8 +57,8 @@ fn renew_wrapped(ctx: CallContext) -> Result { #[js_function(1)] fn new_test_class(ctx: CallContext) -> Result { - let add_count_method = Property::new(ctx.env, "addCount")?.with_method(add_count); - let add_native_count = Property::new(ctx.env, "addNativeCount")?.with_method(add_native_count); + let add_count_method = Property::new("addCount")?.with_method(add_count); + let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count); let properties = vec![add_count_method, add_native_count]; let test_class = ctx diff --git a/test_module/src/cleanup_env.rs b/examples/napi-compat-mode/src/cleanup_env.rs similarity index 100% rename from test_module/src/cleanup_env.rs rename to examples/napi-compat-mode/src/cleanup_env.rs diff --git a/test_module/src/either.rs b/examples/napi-compat-mode/src/either.rs similarity index 100% rename from test_module/src/either.rs rename to examples/napi-compat-mode/src/either.rs diff --git a/test_module/src/env.rs b/examples/napi-compat-mode/src/env.rs similarity index 100% rename from test_module/src/env.rs rename to examples/napi-compat-mode/src/env.rs diff --git a/test_module/src/error.rs b/examples/napi-compat-mode/src/error.rs similarity index 100% rename from test_module/src/error.rs rename to examples/napi-compat-mode/src/error.rs diff --git a/test_module/src/external.rs b/examples/napi-compat-mode/src/external.rs similarity index 100% rename from test_module/src/external.rs rename to examples/napi-compat-mode/src/external.rs diff --git a/test_module/src/function.rs b/examples/napi-compat-mode/src/function.rs similarity index 100% rename from test_module/src/function.rs rename to examples/napi-compat-mode/src/function.rs diff --git a/test_module/src/global.rs b/examples/napi-compat-mode/src/global.rs similarity index 100% rename from test_module/src/global.rs rename to examples/napi-compat-mode/src/global.rs diff --git a/test_module/src/lib.rs b/examples/napi-compat-mode/src/lib.rs similarity index 100% rename from test_module/src/lib.rs rename to examples/napi-compat-mode/src/lib.rs diff --git a/test_module/src/napi4/mod.rs b/examples/napi-compat-mode/src/napi4/mod.rs similarity index 100% rename from test_module/src/napi4/mod.rs rename to examples/napi-compat-mode/src/napi4/mod.rs diff --git a/test_module/src/napi4/tsfn.rs b/examples/napi-compat-mode/src/napi4/tsfn.rs similarity index 95% rename from test_module/src/napi4/tsfn.rs rename to examples/napi-compat-mode/src/napi4/tsfn.rs index 9358fa89..70f18e18 100644 --- a/test_module/src/napi4/tsfn.rs +++ b/examples/napi-compat-mode/src/napi4/tsfn.rs @@ -6,7 +6,6 @@ use napi::{ CallContext, Error, JsBoolean, JsFunction, JsNumber, JsObject, JsString, JsUndefined, Ref, Result, Status, }; -use tokio; #[js_function(1)] pub fn test_threadsafe_function(ctx: CallContext) -> Result { @@ -28,13 +27,13 @@ pub fn test_threadsafe_function(ctx: CallContext) -> Result { thread::spawn(move || { let output: Vec = vec![0, 1, 2, 3]; // It's okay to call a threadsafe function multiple times. - tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking); + tsfn.call(Ok(output), ThreadsafeFunctionCallMode::Blocking); }); thread::spawn(move || { let output: Vec = vec![3, 2, 1, 0]; // It's okay to call a threadsafe function multiple times. - tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking); + tsfn_cloned.call(Ok(output), ThreadsafeFunctionCallMode::NonBlocking); }); ctx.env.get_undefined() @@ -143,7 +142,7 @@ pub fn test_tokio_readfile(ctx: CallContext) -> Result { .map_err(|e| Error::from_reason(format!("Create tokio runtime failed {}", e)))?; rt.block_on(async move { - let ret = read_file_content(&Path::new(&path_str)).await; + let ret = read_file_content(Path::new(&path_str)).await; tsfn.call(ret, ThreadsafeFunctionCallMode::Blocking); }); diff --git a/test_module/src/napi4/tsfn_dua_instance.rs b/examples/napi-compat-mode/src/napi4/tsfn_dua_instance.rs similarity index 96% rename from test_module/src/napi4/tsfn_dua_instance.rs rename to examples/napi-compat-mode/src/napi4/tsfn_dua_instance.rs index 6b50006c..39e0a236 100644 --- a/test_module/src/napi4/tsfn_dua_instance.rs +++ b/examples/napi-compat-mode/src/napi4/tsfn_dua_instance.rs @@ -23,7 +23,7 @@ pub fn constructor(ctx: CallContext) -> napi::Result { .map(|js_string| vec![js_string]) })?; - cb.unref(&ctx.env)?; + cb.unref(ctx.env)?; let mut this: JsObject = ctx.this_unchecked(); let obj = A { cb }; diff --git a/test_module/src/napi5/date.rs b/examples/napi-compat-mode/src/napi5/date.rs similarity index 100% rename from test_module/src/napi5/date.rs rename to examples/napi-compat-mode/src/napi5/date.rs diff --git a/test_module/src/napi5/mod.rs b/examples/napi-compat-mode/src/napi5/mod.rs similarity index 100% rename from test_module/src/napi5/mod.rs rename to examples/napi-compat-mode/src/napi5/mod.rs diff --git a/test_module/src/napi6/bigint.rs b/examples/napi-compat-mode/src/napi6/bigint.rs similarity index 100% rename from test_module/src/napi6/bigint.rs rename to examples/napi-compat-mode/src/napi6/bigint.rs diff --git a/test_module/src/napi6/instance.rs b/examples/napi-compat-mode/src/napi6/instance.rs similarity index 100% rename from test_module/src/napi6/instance.rs rename to examples/napi-compat-mode/src/napi6/instance.rs diff --git a/test_module/src/napi6/mod.rs b/examples/napi-compat-mode/src/napi6/mod.rs similarity index 100% rename from test_module/src/napi6/mod.rs rename to examples/napi-compat-mode/src/napi6/mod.rs diff --git a/test_module/src/napi7/buffer.rs b/examples/napi-compat-mode/src/napi7/buffer.rs similarity index 100% rename from test_module/src/napi7/buffer.rs rename to examples/napi-compat-mode/src/napi7/buffer.rs diff --git a/test_module/src/napi7/mod.rs b/examples/napi-compat-mode/src/napi7/mod.rs similarity index 100% rename from test_module/src/napi7/mod.rs rename to examples/napi-compat-mode/src/napi7/mod.rs diff --git a/test_module/src/napi8/async_cleanup.rs b/examples/napi-compat-mode/src/napi8/async_cleanup.rs similarity index 100% rename from test_module/src/napi8/async_cleanup.rs rename to examples/napi-compat-mode/src/napi8/async_cleanup.rs diff --git a/test_module/src/napi8/mod.rs b/examples/napi-compat-mode/src/napi8/mod.rs similarity index 100% rename from test_module/src/napi8/mod.rs rename to examples/napi-compat-mode/src/napi8/mod.rs diff --git a/test_module/src/napi8/object.rs b/examples/napi-compat-mode/src/napi8/object.rs similarity index 100% rename from test_module/src/napi8/object.rs rename to examples/napi-compat-mode/src/napi8/object.rs diff --git a/test_module/src/napi_version.rs b/examples/napi-compat-mode/src/napi_version.rs similarity index 100% rename from test_module/src/napi_version.rs rename to examples/napi-compat-mode/src/napi_version.rs diff --git a/test_module/src/object.rs b/examples/napi-compat-mode/src/object.rs similarity index 97% rename from test_module/src/object.rs rename to examples/napi-compat-mode/src/object.rs index 4728f072..a8f62ca6 100644 --- a/test_module/src/object.rs +++ b/examples/napi-compat-mode/src/object.rs @@ -135,8 +135,8 @@ fn test_delete_element(ctx: CallContext) -> Result { #[js_function(1)] fn test_define_properties(ctx: CallContext) -> Result { let mut obj = ctx.get::(0)?; - let add_method = Property::new(ctx.env, "add")?.with_method(add); - let readonly_property = Property::new(ctx.env, "ro")?.with_getter(readonly_getter); + let add_method = Property::new("add")?.with_method(add); + let readonly_property = Property::new("ro")?.with_getter(readonly_getter); let properties = vec![add_method, readonly_property]; obj.define_properties(&properties)?; obj.set_named_property("count", ctx.env.create_int32(0)?)?; diff --git a/test_module/src/serde.rs b/examples/napi-compat-mode/src/serde.rs similarity index 100% rename from test_module/src/serde.rs rename to examples/napi-compat-mode/src/serde.rs diff --git a/test_module/src/string.rs b/examples/napi-compat-mode/src/string.rs similarity index 100% rename from test_module/src/string.rs rename to examples/napi-compat-mode/src/string.rs diff --git a/test_module/src/symbol.rs b/examples/napi-compat-mode/src/symbol.rs similarity index 100% rename from test_module/src/symbol.rs rename to examples/napi-compat-mode/src/symbol.rs diff --git a/test_module/src/task.rs b/examples/napi-compat-mode/src/task.rs similarity index 99% rename from test_module/src/task.rs rename to examples/napi-compat-mode/src/task.rs index 9e58be08..485befd2 100644 --- a/test_module/src/task.rs +++ b/examples/napi-compat-mode/src/task.rs @@ -27,7 +27,6 @@ impl Task for ComputeFib { } } -#[inline] fn fibonacci_native(n: u32) -> u32 { match n { 1 | 2 => 1, diff --git a/test_module/src/tokio_rt/mod.rs b/examples/napi-compat-mode/src/tokio_rt/mod.rs similarity index 100% rename from test_module/src/tokio_rt/mod.rs rename to examples/napi-compat-mode/src/tokio_rt/mod.rs diff --git a/test_module/src/tokio_rt/read_file.rs b/examples/napi-compat-mode/src/tokio_rt/read_file.rs similarity index 99% rename from test_module/src/tokio_rt/read_file.rs rename to examples/napi-compat-mode/src/tokio_rt/read_file.rs index 847305ef..a3395433 100644 --- a/test_module/src/tokio_rt/read_file.rs +++ b/examples/napi-compat-mode/src/tokio_rt/read_file.rs @@ -1,6 +1,5 @@ use futures::prelude::*; use napi::{CallContext, Error, JsObject, JsString, Result, Status}; -use tokio; #[js_function(1)] pub fn test_execute_tokio_readfile(ctx: CallContext) -> Result { diff --git a/examples/napi/.gitignore b/examples/napi/.gitignore new file mode 100644 index 00000000..d849c504 --- /dev/null +++ b/examples/napi/.gitignore @@ -0,0 +1 @@ +*.node \ No newline at end of file diff --git a/examples/napi/Cargo.toml b/examples/napi/Cargo.toml new file mode 100644 index 00000000..3911e133 --- /dev/null +++ b/examples/napi/Cargo.toml @@ -0,0 +1,19 @@ +[package] +authors = ["LongYinan "] +edition = "2018" +name = "napi-examples" +version = "0.1.0" + +[lib] +crate-type = ["cdylib"] + +[features] +latest = ["napi/napi8"] +napi3 = ["napi/napi3"] + +[dependencies] +napi-derive = { path = "../../crates/macro", features = ["type-def"] } +napi = { path = "../../crates/napi", features = ["latin1"] } + +[build-dependencies] +napi-build = { path = "../../crates/build" } \ No newline at end of file diff --git a/examples/napi/__test__/typegen.spec.ts b/examples/napi/__test__/typegen.spec.ts new file mode 100644 index 00000000..3dc2009c --- /dev/null +++ b/examples/napi/__test__/typegen.spec.ts @@ -0,0 +1,8 @@ +import { readFileSync } from 'fs' +import { join } from 'path' + +import test from 'ava' + +test('should generate correct type def file', (t) => { + t.snapshot(readFileSync(join(__dirname, '..', 'type.d.ts'), 'utf8')) +}) diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md new file mode 100644 index 00000000..24d4b7e7 --- /dev/null +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -0,0 +1,34 @@ +# Snapshot report for `examples/napi/__test__/typegen.spec.ts` + +The actual snapshot is saved in `typegen.spec.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## should generate correct type def file + +> Snapshot 1 + + `export function getWords(): Array␊ + export function getNums(): Array␊ + export function sumNums(nums: Array): number␊ + export function getCwd(callback: (arg0: string) => void): void␊ + 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␊ + export function mapOption(val: number | undefined): number | undefined␊ + export function add(a: number, b: number): number␊ + export function fibonacci(n: number): number␊ + export function listObjKeys(obj: object): Array␊ + export function createObj(): object␊ + export function contains(source: string, target: string): boolean␊ + export function concatStr(mutS: string): string␊ + export function concatUtf16(s: Utf16String): Utf16String␊ + export function concatLatin1(s: Latin1String): string␊ + export class Animal {␊ + readonly kind: Kind␊ + name: string␊ + constructor(kind: Kind, name: string)␊ + static new(kind: Kind, name: string): Animal␊ + whoami(): string␊ + }␊ + ` diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap new file mode 100644 index 00000000..12512286 Binary files /dev/null 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 new file mode 100644 index 00000000..f2aed855 --- /dev/null +++ b/examples/napi/__test__/values.spec.ts @@ -0,0 +1,91 @@ +import test from 'ava' + +import { + add, + fibonacci, + contains, + concatLatin1, + concatStr, + concatUtf16, + getNums, + getWords, + sumNums, + getCwd, + Animal, + Kind, + CustomNumEnum, + enumToI32, + listObjKeys, + createObj, + mapOption, +} from '../' + +test('number', (t) => { + t.is(add(1, 2), 3) + t.is(fibonacci(5), 5) + + t.throws( + // @ts-expect-error + () => fibonacci(''), + null, + 'Expect value to be Number, but received String', + ) +}) + +test('string', (t) => { + t.true(contains('hello', 'ell')) + t.false(contains('John', 'jn')) + + t.is(concatStr('涽¾DEL'), '涽¾DEL + Rust 🦀 string!') + t.is(concatLatin1('涽¾DEL'), '涽¾DEL + Rust 🦀 string!') + t.is( + concatUtf16('JavaScript 🌳 你好 napi'), + 'JavaScript 🌳 你好 napi + Rust 🦀 string!', + ) +}) + +test('array', (t) => { + t.deepEqual(getNums(), [1, 1, 2, 3, 5, 8]) + t.deepEqual(getWords(), ['foo', 'bar']) + + t.is(sumNums([1, 2, 3, 4, 5]), 15) +}) + +test('enum', (t) => { + t.deepEqual([Kind.Dog, Kind.Cat, Kind.Duck], [0, 1, 2]) + t.is(enumToI32(CustomNumEnum.Eight), 8) +}) + +test('class', (t) => { + const dog = new Animal(Kind.Dog, '旺财') + + t.is(dog.name, '旺财') + t.is(dog.kind, Kind.Dog) + t.is(dog.whoami(), 'Dog: 旺财') + + dog.name = '可乐' + t.is(dog.name, '可乐') +}) + +test('callback', (t) => { + getCwd((cwd) => { + t.is(cwd, process.cwd()) + }) + + t.throws( + // @ts-expect-error + () => getCwd(), + null, + 'Expect value to be Function, but received Undefined', + ) +}) + +test('object', (t) => { + t.deepEqual(listObjKeys({ name: 'John Doe', age: 20 }), ['name', 'age']) + t.deepEqual(createObj(), { test: 1 }) +}) + +test('Option', (t) => { + t.is(mapOption(undefined), undefined) + t.is(mapOption(3), 4) +}) diff --git a/napi-derive-example/build.rs b/examples/napi/build.rs similarity index 66% rename from napi-derive-example/build.rs rename to examples/napi/build.rs index 985e8b9b..c7c74b6d 100644 --- a/napi-derive-example/build.rs +++ b/examples/napi/build.rs @@ -1,5 +1,3 @@ -extern crate napi_build; - fn main() { use napi_build::setup; diff --git a/examples/napi/package.json b/examples/napi/package.json new file mode 100644 index 00000000..fff1c381 --- /dev/null +++ b/examples/napi/package.json @@ -0,0 +1,15 @@ +{ + "name": "napi-examples", + "private": true, + "main": "./index.node", + "types": "./type.d.ts", + "scripts": { + "build": "node ../../cli/scripts/index.js build --features \"latest\"", + "build-napi3": "node ../../cli/scripts/index.js build --features \"napi3\"", + "build-aarch64": "node ../../cli/scripts/index.js build --features \"latest\" --target aarch64-unknown-linux-gnu", + "build-armv7": "node ../../cli/scripts/index.js build --features \"latest\" --target armv7-unknown-linux-gnueabihf", + "build-i686": "node ../../cli/scripts/index.js build --features \"latest\" --target i686-pc-windows-msvc", + "build-i686-release": "node ../../cli/scripts/index.js build --release --features \"latest\" --target i686-pc-windows-msvc", + "build-release": "node ../../cli/scripts/index.js build --features \"latest\" --release" + } +} diff --git a/examples/napi/src/array.rs b/examples/napi/src/array.rs new file mode 100644 index 00000000..7315fd42 --- /dev/null +++ b/examples/napi/src/array.rs @@ -0,0 +1,16 @@ +use napi::bindgen_prelude::*; + +#[napi] +fn get_words() -> Vec<&'static str> { + vec!["foo", "bar"] +} + +#[napi] +fn get_nums() -> Vec { + vec![1, 1, 2, 3, 5, 8] +} + +#[napi] +fn sum_nums(nums: Vec) -> u32 { + nums.iter().sum() +} diff --git a/examples/napi/src/callback.rs b/examples/napi/src/callback.rs new file mode 100644 index 00000000..ceff82b4 --- /dev/null +++ b/examples/napi/src/callback.rs @@ -0,0 +1,7 @@ +use napi::bindgen_prelude::*; +use std::env; + +#[napi] +fn get_cwd Result<()>>(callback: T) { + callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap(); +} diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs new file mode 100644 index 00000000..bfd7bff3 --- /dev/null +++ b/examples/napi/src/class.rs @@ -0,0 +1,29 @@ +use napi::bindgen_prelude::*; + +use crate::r#enum::Kind; + +#[napi(constructor)] +pub struct Animal { + #[napi(readonly)] + pub kind: Kind, + pub name: String, +} + +#[napi] +impl Animal { + #[napi] + pub fn new(kind: Kind, name: String) -> Self { + Animal { kind, name } + } + + #[napi] + pub fn whoami(&self) -> String { + match self.kind { + Kind::Dog => { + format!("Dog: {}", self.name) + } + Kind::Cat => format!("Cat: {}", self.name), + Kind::Duck => format!("Duck: {}", self.name), + } + } +} diff --git a/examples/napi/src/enum.rs b/examples/napi/src/enum.rs new file mode 100644 index 00000000..4f76f99b --- /dev/null +++ b/examples/napi/src/enum.rs @@ -0,0 +1,27 @@ +use napi::bindgen_prelude::*; + +/// default enum values are continuos i32s start from 0 +#[napi] +pub enum Kind { + Dog, + Cat, + Duck, +} + +/// You could break the step and for an new continuous value. +#[napi] +pub enum CustomNumEnum { + One = 1, + Two, + Three = 3, + Four, + Six = 6, + Eight = 8, + Nine, // would be 9 + Ten, // 10 +} + +#[napi] +fn enum_to_i32(e: CustomNumEnum) -> i32 { + e as i32 +} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs new file mode 100644 index 00000000..e982cfba --- /dev/null +++ b/examples/napi/src/lib.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate napi_derive; + +mod array; +mod callback; +mod class; +mod r#enum; +mod nullable; +mod number; +mod object; +mod string; diff --git a/examples/napi/src/nullable.rs b/examples/napi/src/nullable.rs new file mode 100644 index 00000000..2f4745c5 --- /dev/null +++ b/examples/napi/src/nullable.rs @@ -0,0 +1,6 @@ +use napi::bindgen_prelude::*; + +#[napi] +fn map_option(val: Option) -> Option { + val.map(|v| v + 1) +} diff --git a/examples/napi/src/number.rs b/examples/napi/src/number.rs new file mode 100644 index 00000000..557ffe90 --- /dev/null +++ b/examples/napi/src/number.rs @@ -0,0 +1,14 @@ +use napi::bindgen_prelude::*; + +#[napi] +fn add(a: u32, b: u32) -> u32 { + a + b +} + +#[napi(strict)] +fn fibonacci(n: u32) -> u32 { + match n { + 1 | 2 => 1, + _ => fibonacci(n - 1) + fibonacci(n - 2), + } +} diff --git a/examples/napi/src/object.rs b/examples/napi/src/object.rs new file mode 100644 index 00000000..4f4cac2b --- /dev/null +++ b/examples/napi/src/object.rs @@ -0,0 +1,14 @@ +use napi::bindgen_prelude::*; + +#[napi] +fn list_obj_keys(obj: Object) -> Vec { + Object::keys(&obj).unwrap() +} + +#[napi] +fn create_obj(env: Env) -> Object { + let mut obj = env.create_object().unwrap(); + obj.set("test".to_owned(), 1).unwrap(); + + obj +} diff --git a/examples/napi/src/string.rs b/examples/napi/src/string.rs new file mode 100644 index 00000000..ee2e49df --- /dev/null +++ b/examples/napi/src/string.rs @@ -0,0 +1,22 @@ +use napi::bindgen_prelude::*; + +#[napi] +fn contains(source: String, target: String) -> bool { + source.contains(&target) +} + +#[napi] +fn concat_str(mut s: String) -> String { + s.push_str(" + Rust 🦀 string!"); + s +} + +#[napi] +fn concat_utf16(s: Utf16String) -> Utf16String { + Utf16String::from(format!("{} + Rust 🦀 string!", s)) +} + +#[napi] +fn concat_latin1(s: Latin1String) -> String { + format!("{} + Rust 🦀 string!", s) +} diff --git a/test_module/tsconfig.json b/examples/napi/tsconfig.json similarity index 100% rename from test_module/tsconfig.json rename to examples/napi/tsconfig.json diff --git a/examples/napi/type.d.ts b/examples/napi/type.d.ts new file mode 100644 index 00000000..a869439a --- /dev/null +++ b/examples/napi/type.d.ts @@ -0,0 +1,23 @@ +export function getWords(): Array +export function getNums(): Array +export function sumNums(nums: Array): number +export function getCwd(callback: (arg0: string) => void): void +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 +export function mapOption(val: number | undefined): number | undefined +export function add(a: number, b: number): number +export function fibonacci(n: number): number +export function listObjKeys(obj: object): Array +export function createObj(): object +export function contains(source: string, target: string): boolean +export function concatStr(mutS: string): string +export function concatUtf16(s: Utf16String): Utf16String +export function concatLatin1(s: Latin1String): string +export class Animal { + readonly kind: Kind + name: string + constructor(kind: Kind, name: string) + static new(kind: Kind, name: string): Animal + whoami(): string +} diff --git a/examples/tsconfig.json b/examples/tsconfig.json new file mode 100644 index 00000000..8989757c --- /dev/null +++ b/examples/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "include": ["."], + "compilerOptions": { + "outDir": "./dist", + "rootDir": ".", + "target": "ES2015" + }, + "exclude": ["dist"] +} diff --git a/memory-testing/Cargo.toml b/memory-testing/Cargo.toml index 35163ae7..b508ba04 100644 --- a/memory-testing/Cargo.toml +++ b/memory-testing/Cargo.toml @@ -9,8 +9,8 @@ crate-type = ["cdylib"] [dependencies] futures = "0.3" -napi = {path = "../napi", features = ["tokio_rt", "serde-json", "latin1"]} -napi-derive = {path = "../napi-derive"} +napi = {path = "../crates/napi", features = ["tokio_rt", "serde-json", "latin1", "compat-mode"]} +napi-derive = {path = "../crates/macro", features = ["compat-mode"]} serde = "1" serde_bytes = "0.11" serde_derive = "1" @@ -18,4 +18,4 @@ serde_json = "1" tokio = {version = "1", features = ["default", "fs"]} [build-dependencies] -napi-build = {path = "../build"} +napi-build = {path = "../crates/build"} diff --git a/napi-derive-example/Cargo.toml b/napi-derive-example/Cargo.toml deleted file mode 100644 index cb789b9f..00000000 --- a/napi-derive-example/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -authors = ["LongYinan "] -edition = "2018" -name = "napi-derive-example" -version = "0.1.0" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -napi = {path = "../napi"} -napi-derive = {path = "../napi-derive"} - -[build-dependencies] -napi-build = {path = "../build"} diff --git a/napi-derive-example/src/lib.rs b/napi-derive-example/src/lib.rs deleted file mode 100644 index 35034288..00000000 --- a/napi-derive-example/src/lib.rs +++ /dev/null @@ -1,32 +0,0 @@ -#[macro_use] -extern crate napi_derive; - -use napi::{CallContext, Error, JsNumber, JsObject, JsUnknown, Result, Status}; -use std::convert::TryInto; - -#[module_exports] -fn init(mut exports: JsObject) -> Result<()> { - exports.create_named_method("testThrow", test_throw)?; - - exports.create_named_method("fibonacci", fibonacci)?; - Ok(()) -} - -#[js_function] -fn test_throw(_ctx: CallContext) -> Result { - Err(Error::from_status(Status::GenericFailure)) -} - -#[js_function(1)] -fn fibonacci(ctx: CallContext) -> Result { - let n = ctx.get::(0)?.try_into()?; - ctx.env.create_int64(fibonacci_native(n)) -} - -#[inline] -fn fibonacci_native(n: i64) -> i64 { - match n { - 1 | 2 => 1, - _ => fibonacci_native(n - 1) + fibonacci_native(n - 2), - } -} diff --git a/napi-derive/Cargo.toml b/napi-derive/Cargo.toml deleted file mode 100644 index e79886fc..00000000 --- a/napi-derive/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -authors = ["LongYinan "] -description = "N-API procedural macros" -edition = "2018" -keywords = ["NodeJS", "FFI", "NAPI", "n-api"] -license = "MIT" -name = "napi-derive" -readme = "README.md" -repository = "https://github.com/napi-rs/napi-rs" -version = "1.1.0" - -[dependencies] -proc-macro2 = "1.0" -quote = "1.0" -syn = {version = "1.0", features = ["fold", "full"]} - -[lib] -proc-macro = true diff --git a/napi-derive/README.md b/napi-derive/README.md deleted file mode 100644 index 9a35693a..00000000 --- a/napi-derive/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# napi-derive - - - - -chat - - -## js_function - -```rust -#[macro_use] -extern crate napi_derive; - -use napi::{CallContext, Error, JsNumber, JsUnknown, Module, Result, Status}; -use std::convert::TryInto; - -#[module_exports] -fn init(mut exports: JsObject) -> Result<()> { - exports.create_named_method("testThrow", test_throw)?; - exports.create_named_method("fibonacci", fibonacci)?; - Ok(()) -} - -#[js_function] -fn test_throw(_ctx: CallContext) -> Result { - Err(Error::from_status(Status::GenericFailure)) -} - -#[js_function(1)] -fn fibonacci(ctx: CallContext) -> Result { - let n = ctx.get::(0)?.try_into()?; - ctx.env.create_int64(fibonacci_native(n)) -} - -#[inline] -fn fibonacci_native(n: i64) -> i64 { - match n { - 1 | 2 => 1, - _ => fibonacci_native(n - 1) + fibonacci_native(n - 2), - } -} -``` diff --git a/napi-derive/src/lib.rs b/napi-derive/src/lib.rs deleted file mode 100644 index 88a18f02..00000000 --- a/napi-derive/src/lib.rs +++ /dev/null @@ -1,261 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; -use proc_macro2::{Ident, Literal}; -use quote::{format_ident, quote}; -use syn::fold::{fold_fn_arg, fold_signature, Fold}; -use syn::parse::{Parse, ParseStream, Result}; -use syn::punctuated::Punctuated; -use syn::{parse_macro_input, Block, FnArg, ItemFn, Signature, Token, Visibility}; - -struct ArgLength { - length: Literal, -} - -impl Parse for ArgLength { - fn parse(input: ParseStream) -> Result { - let vars = Punctuated::::parse_terminated(input)?; - Ok(ArgLength { - length: vars - .first() - .cloned() - .unwrap_or_else(|| Literal::usize_unsuffixed(0)), - }) - } -} - -struct JsFunction { - args: Vec, - name: Option, - signature: Option, - signature_raw: Option, - block: Vec, - visibility: Visibility, -} - -impl JsFunction { - pub fn new() -> Self { - JsFunction { - args: vec![], - name: None, - signature: None, - signature_raw: None, - visibility: Visibility::Inherited, - block: vec![], - } - } -} - -impl Fold for JsFunction { - fn fold_fn_arg(&mut self, arg: FnArg) -> FnArg { - self.args.push(arg.clone()); - fold_fn_arg(self, arg) - } - - fn fold_signature(&mut self, signature: Signature) -> Signature { - self.name = Some(format_ident!("{}", signature.ident)); - let mut new_signature = signature.clone(); - new_signature.ident = format_ident!("_generated_{}_generated_", signature.ident); - self.signature = Some(new_signature); - self.signature_raw = Some(signature.clone()); - fold_signature(self, signature) - } - - fn fold_visibility(&mut self, v: Visibility) -> Visibility { - self.visibility = v.clone(); - v - } - - fn fold_block(&mut self, node: Block) -> Block { - self.block.push(node.clone()); - node - } -} - -#[proc_macro_attribute] -pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream { - let arg_len = parse_macro_input!(attr as ArgLength); - let arg_len_span = arg_len.length; - let input = parse_macro_input!(input as ItemFn); - let mut js_fn = JsFunction::new(); - js_fn.fold_item_fn(input); - let fn_name = js_fn.name.unwrap(); - let fn_block = js_fn.block; - let signature = js_fn.signature.unwrap(); - let visibility = js_fn.visibility; - let new_fn_name = signature.ident.clone(); - let execute_js_function = get_execute_js_code(new_fn_name, FunctionKind::JsFunction); - let expanded = quote! { - #[inline(always)] - #signature #(#fn_block)* - - #visibility extern "C" fn #fn_name( - raw_env: napi::sys::napi_env, - cb_info: napi::sys::napi_callback_info, - ) -> napi::sys::napi_value { - use std::ptr; - use std::panic::{self, AssertUnwindSafe}; - use std::ffi::CString; - use napi::{Env, Error, Status, NapiValue, NapiRaw, CallContext}; - let mut argc = #arg_len_span as usize; - let mut raw_args: [napi::sys::napi_value; #arg_len_span] = [ptr::null_mut(); #arg_len_span]; - let mut raw_this = ptr::null_mut(); - - unsafe { - let status = napi::sys::napi_get_cb_info( - raw_env, - cb_info, - &mut argc, - raw_args.as_mut_ptr(), - &mut raw_this, - ptr::null_mut(), - ); - debug_assert!(Status::from(status) == Status::Ok, "napi_get_cb_info failed"); - } - - let mut env = unsafe { Env::from_raw(raw_env) }; - let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, argc); - #execute_js_function - } - }; - // Hand the output tokens back to the compiler - TokenStream::from(expanded) -} - -#[proc_macro_attribute] -pub fn contextless_function(_attr: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as ItemFn); - let mut js_fn = JsFunction::new(); - js_fn.fold_item_fn(input); - let fn_name = js_fn.name.unwrap(); - let fn_block = js_fn.block; - let signature = js_fn.signature.unwrap(); - let visibility = js_fn.visibility; - let new_fn_name = signature.ident.clone(); - let execute_js_function = get_execute_js_code(new_fn_name, FunctionKind::Contextless); - - let expanded = quote! { - #[inline(always)] - #signature #(#fn_block)* - - #visibility extern "C" fn #fn_name( - raw_env: napi::sys::napi_env, - cb_info: napi::sys::napi_callback_info, - ) -> napi::sys::napi_value { - use std::ptr; - use std::panic::{self, AssertUnwindSafe}; - use std::ffi::CString; - use napi::{Env, NapiValue, NapiRaw, Error, Status}; - - let ctx = unsafe { Env::from_raw(raw_env) }; - #execute_js_function - } - }; - // Hand the output tokens back to the compiler - TokenStream::from(expanded) -} - -enum FunctionKind { - Contextless, - JsFunction, -} - -#[inline] -fn get_execute_js_code( - new_fn_name: Ident, - function_kind: FunctionKind, -) -> proc_macro2::TokenStream { - let return_token_stream = match function_kind { - FunctionKind::Contextless => { - quote! { - Ok(Some(v)) => unsafe { v.raw() }, - Ok(None) => ptr::null_mut(), - } - } - FunctionKind::JsFunction => { - quote! { - Ok(v) => unsafe { v.raw() }, - } - } - }; - quote! { - match panic::catch_unwind(AssertUnwindSafe(move || #new_fn_name(ctx))).map_err(|e| { - let message = { - if let Some(string) = e.downcast_ref::() { - string.clone() - } else if let Some(string) = e.downcast_ref::<&str>() { - string.to_string() - } else { - format!("panic from Rust code: {:?}", e) - } - }; - Error::from_reason(message) - }).and_then(|v| v) { - #return_token_stream - Err(e) => { - unsafe { napi::JsError::from(e).throw_into(raw_env) }; - ptr::null_mut() - } - } - } -} - -#[proc_macro_attribute] -pub fn module_exports(_attr: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as ItemFn); - let mut js_fn = JsFunction::new(); - js_fn.fold_item_fn(input); - let fn_block = js_fn.block; - let fn_name = js_fn.name.unwrap(); - let signature = js_fn.signature_raw.unwrap(); - let args_len = js_fn.args.len(); - let call_expr = if args_len == 1 { - quote! { #fn_name(exports) } - } else if args_len == 2 { - quote! { #fn_name(exports, env) } - } else { - panic!("Arguments length of #[module_exports] function must be 1 or 2"); - }; - let expanded = quote! { - #[inline] - #signature #(#fn_block)* - - #[no_mangle] - unsafe extern "C" fn napi_register_module_v1( - raw_env: napi::sys::napi_env, - raw_exports: napi::sys::napi_value, - ) -> napi::sys::napi_value { - use std::ffi::CString; - use std::ptr; - use napi::{Env, JsObject, NapiValue, NapiRaw}; - - #[cfg(all(feature = "tokio_rt", feature = "napi4"))] - use napi::shutdown_tokio_rt; - let env = Env::from_raw(raw_env); - let exports = JsObject::from_raw_unchecked(raw_env, raw_exports); - let result: napi::Result<()> = #call_expr; - #[cfg(all(feature = "tokio_rt", feature = "napi4"))] - let hook_result = napi::check_status!(unsafe { - napi::sys::napi_add_env_cleanup_hook(raw_env, Some(shutdown_tokio_rt), ptr::null_mut()) - }); - #[cfg(not(all(feature = "tokio_rt", feature = "napi4")))] - let hook_result = Ok(()); - match hook_result.and_then(move |_| result) { - Ok(_) => raw_exports, - Err(e) => { - unsafe { - napi::sys::napi_throw_error( - raw_env, - ptr::null(), - CString::from_vec_unchecked(format!("Error initializing module: {}", e).into()) - .as_ptr(), - ) - }; - ptr::null_mut() - } - } - } - }; - // Hand the output tokens back to the compiler - TokenStream::from(expanded) -} diff --git a/napi/README.md b/napi/README.md deleted file mode 100644 index 8deb2094..00000000 --- a/napi/README.md +++ /dev/null @@ -1,304 +0,0 @@ -# napi-rs - -Stake to support us - -chat - - -> This project was initialized from [xray](https://github.com/atom/xray) - -A minimal library for building compiled `Node.js` add-ons in `Rust`. - -

- - - -

- -## Ecosystem - -

- - Prisma - -   -   - - swc - -   -   - - Parcel - -   - - next.js -   - nextjs.svg - -

- -## Platform Support - -![Lint](https://github.com/napi-rs/napi-rs/workflows/Lint/badge.svg) -![Linux N-API@3](https://github.com/napi-rs/napi-rs/workflows/Linux%20N-API@3/badge.svg) -![Linux musl](https://github.com/napi-rs/napi-rs/workflows/Linux%20musl/badge.svg) -![macOS/Windows/Linux x64](https://github.com/napi-rs/napi-rs/workflows/macOS/Windows/Linux%20x64/badge.svg) -![Linux-aarch64](https://github.com/napi-rs/napi-rs/workflows/Linux-aarch64/badge.svg) -![Linux-armv7](https://github.com/napi-rs/napi-rs/workflows/Linux-armv7/badge.svg) -![macOS-Android](https://github.com/napi-rs/napi-rs/workflows/macOS-Android/badge.svg) -![Windows i686](https://github.com/napi-rs/napi-rs/workflows/Windows%20i686/badge.svg) -[![Windows arm64](https://github.com/napi-rs/napi-rs/actions/workflows/windows-arm.yml/badge.svg)](https://github.com/napi-rs/napi-rs/actions/workflows/windows-arm.yml) -[![FreeBSD](https://api.cirrus-ci.com/github/napi-rs/napi-rs.svg)](https://cirrus-ci.com/github/napi-rs/napi-rs?branch=main) - -| | node12 | node14 | node16 | -| --------------------- | ------ | ------ | ------ | -| Windows x64 | ✓ | ✓ | ✓ | -| Windows x86 | ✓ | ✓ | ✓ | -| Windows arm64 | ✓ | ✓ | ✓ | -| macOS x64 | ✓ | ✓ | ✓ | -| macOS aarch64 | ✓ | ✓ | ✓ | -| Linux x64 gnu | ✓ | ✓ | ✓ | -| Linux x64 musl | ✓ | ✓ | ✓ | -| Linux aarch64 gnu | ✓ | ✓ | ✓ | -| Linux aarch64 musl | ✓ | ✓ | ✓ | -| Linux arm gnueabihf | ✓ | ✓ | ✓ | -| Linux aarch64 android | ✓ | ✓ | ✓ | -| FreeBSD x64 | ✓ | ✓ | ✓ | - -This library depends on Node-API and requires `Node@10.0.0` or later. - -We already have some packages written by `napi-rs`: [node-rs](https://github.com/napi-rs/node-rs) - -One nice feature is that this crate allows you to build add-ons purely with the `Rust/JavaScript` toolchain and without involving `node-gyp`. - -## Taste - -> You can start from [package-template](https://github.com/napi-rs/package-template) to play with `napi-rs` - -### Define JavaScript functions - -```rust -#[js_function(1)] // ------> arguments length -fn fibonacci(ctx: CallContext) -> Result { - let n = ctx.get::(0)?.try_into()?; - ctx.env.create_int64(fibonacci_native(n)) -} - -#[inline(always)] -fn fibonacci_native(n: i64) -> i64 { - match n { - 1 | 2 => 1, - _ => fibonacci_native(n - 1) + fibonacci_native(n - 2), - } -} -``` - -### Register module - -```rust -#[macro_use] -extern crate napi_derive; - -use napi::{JsObject, Result}; - -/// `exports` is `module.exports` object in NodeJS -#[module_exports] -fn init(mut exports: JsObject) -> Result<()> { - exports.create_named_method("fibonacci", fibonacci)?; - Ok(()) -} -``` - -And you can also create `JavaScript` value while registering module: - -```rust -#[macro_use] -extern crate napi_derive; - -use napi::{JsObject, Result, Env}; - -#[module_exports] -fn init(mut exports: JsObject, env: Env) -> Result<()> { - exports.create_named_method("fibonacci", fibonacci)?; - exports.set_named_property("DEFAULT_VALUE", env.create_int64(100)?)?; - Ok(()) -} -``` - -## Building - -This repository is a `Cargo` crate. Any napi-based add-on should contain `Cargo.toml` to make it a Cargo crate. - -In your `Cargo.toml` you need to set the `crate-type` to `"cdylib"` so that cargo builds a C-style shared library that can be dynamically loaded by the Node executable. You'll also need to add this crate as a dependency. - -```toml -[package] -name = "awesome" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -napi = "1" -napi-derive = "1" - -[build-dependencies] -napi-build = "1" -``` - -And create `build.rs` in your own project: - -```rust -// build.rs -extern crate napi_build; - -fn main() { - napi_build::setup(); -} -``` - -So far, the `napi` build script has only been tested on `macOS` `Linux` `Windows x64 MSVC` and `FreeBSD`. - -See the included [test_module](./test_module) for an example add-on. - -Install the `@napi-rs/cli` to help you build your `Rust` codes and copy `Dynamic lib` file to `.node` file in case you can `require` it in your program. - -```js -{ - "package": "awesome-package", - "devDependencies": { - "@napi-rs/cli": "^1.0.0" - }, - "napi": { - "name": "jarvis" // <----------- Config the name of native addon, or the napi command will use the name of `Cargo.toml` for the binary file name. - }, - "scripts": { - "build": "napi build --release", - "build:debug": "napi build" - } -} -``` - -Then you can require your native binding: - -```js -require('./jarvis.node') -``` - -The `module_name` would be your `package` name in your `Cargo.toml`. - -`xxx => ./xxx.node` - -`xxx-yyy => ./xxx_yyy.node` - -You can also copy `Dynamic lib` file to an appointed location: - -```bash -napi build [--release] ./dll -napi build [--release] ./artifacts -``` - -There are [documents](./cli) which contains more details about the `@napi-rs/cli` usage. - -## Testing - -Because libraries that depend on this crate must be loaded into a Node executable in order to resolve symbols, all tests are written in JavaScript in the `test_module` subdirectory. - -To run tests: - -```sh -yarn build:test -yarn test -``` - -## Related projects - -- [neon](https://www.neon-bindings.com) -- [node-bindgen](https://github.com/infinyon/node-bindgen) - -## Features table - -### Create JavaScript values - -| NAPI | NAPI Version | Minimal Node version | Status | -| ------------------------------------------------------------------------------------------------------------ | ------------ | -------------------- | ------ | -| [napi_create_array](https://nodejs.org/api/n-api.html#n_api_napi_create_array) | 1 | v8.0.0 | ✅ | -| [napi_create_array_with_length](https://nodejs.org/api/n-api.html#n_api_napi_create_array_with_length) | 1 | v8.0.0 | ✅ | -| [napi_create_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_create_arraybuffer) | 1 | v8.0.0 | ✅ | -| [napi_create_buffer](https://nodejs.org/api/n-api.html#n_api_napi_create_buffer) | 1 | v8.0.0 | ✅ | -| [napi_create_buffer_copy](https://nodejs.org/api/n-api.html#n_api_napi_create_buffer_copy) | 1 | v8.0.0 | ✅ | -| [napi_create_date](https://nodejs.org/api/n-api.html#n_api_napi_create_date) | 5 | v11.11.0 | ✅ | -| [napi_create_external](https://nodejs.org/api/n-api.html#n_api_napi_create_external) | 1 | v8.0.0 | ✅ | -| [napi_create_external_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_create_external_arraybuffer) | 1 | v8.0.0 | ✅ | -| [napi_create_external_buffer](https://nodejs.org/api/n-api.html#n_api_napi_create_external_buffer) | 1 | v8.0.0 | ✅ | -| [napi_create_object](https://nodejs.org/api/n-api.html#n_api_napi_create_object) | 1 | v8.0.0 | ✅ | -| [napi_create_symbol](https://nodejs.org/api/n-api.html#n_api_napi_create_symbol) | 1 | v8.0.0 | ✅ | -| [napi_create_typedarray](https://nodejs.org/api/n-api.html#n_api_napi_create_typedarray) | 1 | v8.0.0 | ✅ | -| [napi_create_dataview](https://nodejs.org/api/n-api.html#n_api_napi_create_dataview) | 1 | v8.3.0 | ✅ | -| [napi_create_int32](https://nodejs.org/api/n-api.html#n_api_napi_create_int32) | 1 | v8.4.0 | ✅ | -| [napi_create_uint32](https://nodejs.org/api/n-api.html#n_api_napi_create_uint32) | 1 | v8.4.0 | ✅ | -| [napi_create_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_int64) | 1 | v8.4.0 | ✅ | -| [napi_create_double](https://nodejs.org/api/n-api.html#n_api_napi_create_double) | 1 | v8.4.0 | ✅ | -| [napi_create_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_int64) | 6 | v10.7.0 | ✅ | -| [napi_create_bigint_uint64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_uint64) | 6 | v10.7.0 | ✅ | -| [napi_create_bigint_words](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_words) | 6 | v10.7.0 | ✅ | -| [napi_create_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_create_string_latin1) | 1 | v8.0.0 | ✅ | -| [napi_create_string_utf16](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf16) | 1 | v8.0.0 | ✅ | -| [napi_create_string_utf8](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf8) | 1 | v8.0.0 | ✅ | -| [napi_type_tag](https://nodejs.org/api/n-api.html#n_api_napi_type_tag) | 8 | v14.8.0, v12.19.0 | ⚠️ | - -> I have no plan to implement `nape_type_tag` and related API in `napi-rs`, because we have implemented a `rust` replacement in [TaggedObject](https://github.com/napi-rs/napi-rs/blob/main/napi/src/js_values/tagged_object.rs) which is more convenient and more compatible. - -### [Functions to convert from Node-API to C types](https://nodejs.org/api/n-api.html#n_api_functions_to_convert_from_node_api_to_c_types) - -| NAPI | NAPI Version | Minimal Node Version | Status | -| ---------------------------------------------------------------------------------------------------- | ------------ | -------------------- | ------ | -| [napi_get_array_length](https://nodejs.org/api/n-api.html#n_api_napi_get_array_length) | 1 | v8.0.0 | ✅ | -| [napi_get_arraybuffer_info](https://nodejs.org/api/n-api.html#n_api_napi_get_arraybuffer_info) | 1 | v8.0.0 | ✅ | -| [napi_get_buffer_info](https://nodejs.org/api/n-api.html#n_api_napi_get_buffer_info) | 1 | v8.0.0 | ✅ | -| [napi_get_prototype](https://nodejs.org/api/n-api.html#n_api_napi_get_prototype) | 1 | v8.0.0 | ✅ | -| [napi_get_typedarray_info](https://nodejs.org/api/n-api.html#n_api_napi_get_typedarray_info) | 1 | v8.0.0 | ✅ | -| [napi_get_dataview_info](https://nodejs.org/api/n-api.html#n_api_napi_get_dataview_info) | 1 | v8.3.0 | ✅ | -| [napi_get_date_value](https://nodejs.org/api/n-api.html#n_api_napi_get_date_value) | 5 | v11.11.0 | ✅ | -| [napi_get_value_bool](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bool) | 1 | v8.0.0 | ✅ | -| [napi_get_value_double](https://nodejs.org/api/n-api.html#n_api_napi_get_value_double) | 1 | v8.0.0 | ✅ | -| [napi_get_value_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_int64) | 6 | v10.7.0 | ✅ | -| [napi_get_value_bigint_uint64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_uint64) | 6 | v10.7.0 | ✅ | -| [napi_get_value_bigint_words](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words) | 6 | v10.7.0 | ✅ | -| [napi_get_value_external](https://nodejs.org/api/n-api.html#n_api_napi_get_value_external) | 1 | v8.0.0 | ✅ | -| [napi_get_value_int32](https://nodejs.org/api/n-api.html#n_api_napi_get_value_int32) | 1 | v8.0.0 | ✅ | -| [napi_get_value_int64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_int64) | 1 | v8.0.0 | ✅ | -| [napi_get_value_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_latin1) | 1 | v8.0.0 | ✅ | -| [napi_get_value_string_utf8](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_utf8) | 1 | v8.0.0 | ✅ | -| [napi_get_value_string_utf16](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_utf16) | 1 | v8.0.0 | ✅ | -| [napi_get_value_uint32](https://nodejs.org/api/n-api.html#n_api_napi_get_value_uint32) | 1 | v8.0.0 | ✅ | -| [napi_get_boolean](https://nodejs.org/api/n-api.html#n_api_napi_get_boolean) | 1 | v8.0.0 | ✅ | -| [napi_get_global](https://nodejs.org/api/n-api.html#n_api_napi_get_global) | 1 | v8.0.0 | ✅ | -| [napi_get_null](https://nodejs.org/api/n-api.html#n_api_napi_get_null) | 1 | v8.0.0 | ✅ | -| [napi_get_undefined](https://nodejs.org/api/n-api.html#n_api_napi_get_undefined) | 1 | v8.0.0 | ✅ | - -### [Working with JavaScript Values and Abstract Operations](https://nodejs.org/api/n-api.html#n_api_working_with_javascript_values_and_abstract_operations) - -| NAPI | NAPI Version | Minimal Node Version | Status | -| ---------------------------------------------------------------------------------------------------- | ------------ | -------------------- | ------ | -| [napi_coerce_to_bool](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_bool) | 1 | v8.0.0 | ✅ | -| [napi_coerce_to_number](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_number) | 1 | v8.0.0 | ✅ | -| [napi_coerce_to_object](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_object) | 1 | v8.0.0 | ✅ | -| [napi_coerce_to_string](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_string) | 1 | v8.0.0 | ✅ | -| [napi_typeof](https://nodejs.org/api/n-api.html#n_api_napi_typeof) | 1 | v8.0.0 | ✅ | -| [napi_instanceof](https://nodejs.org/api/n-api.html#n_api_napi_instanceof) | 1 | v8.0.0 | ✅ | -| [napi_is_array](https://nodejs.org/api/n-api.html#n_api_napi_is_array) | 1 | v8.0.0 | ✅ | -| [napi_is_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_is_arraybuffer) | 1 | v8.0.0 | ✅ | -| [napi_is_buffer](https://nodejs.org/api/n-api.html#n_api_napi_is_buffer) | 1 | v8.0.0 | ✅ | -| [napi_is_date](https://nodejs.org/api/n-api.html#n_api_napi_is_date) | 1 | v8.0.0 | ✅ | -| [napi_is_error](https://nodejs.org/api/n-api.html#n_api_napi_is_error_1) | 1 | v8.0.0 | ✅ | -| [napi_is_typedarray](https://nodejs.org/api/n-api.html#n_api_napi_is_typedarray) | 1 | v8.0.0 | ✅ | -| [napi_is_dataview](https://nodejs.org/api/n-api.html#n_api_napi_is_dataview) | 1 | v8.3.0 | ✅ | -| [napi_strict_equals](https://nodejs.org/api/n-api.html#n_api_napi_strict_equals) | 1 | v8.0.0 | ✅ | -| [napi_detach_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_detach_arraybuffer) | 7 | v13.3.0 | ✅ | -| [napi_is_detached_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_is_detached_arraybuffer) | 7 | v13.3.0 | ✅ | -| [napi_object_freeze](https://nodejs.org/api/n-api.html#n_api_napi_object_freeze) | 8 | v14.14.0, v12.20.0 | ✅ | -| [napi_object_seal](https://nodejs.org/api/n-api.html#n_api_napi_object_seal) | 8 | v14.14.0, v12.20.0 | ✅ | diff --git a/napi/src/error.rs b/napi/src/error.rs deleted file mode 100644 index 98907c72..00000000 --- a/napi/src/error.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::convert::{From, TryFrom}; -use std::error; -use std::ffi::CString; -use std::fmt; -#[cfg(feature = "serde-json")] -use std::fmt::Display; -use std::os::raw::{c_char, c_void}; - -#[cfg(feature = "serde-json")] -use serde::{de, ser}; -#[cfg(feature = "serde-json")] -use serde_json::Error as SerdeJSONError; - -use crate::{sys, Status}; - -pub type Result = std::result::Result; - -/// Represent `JsError`. -/// Return this Error in `js_function`, **napi-rs** will throw it as `JsError` for you. -/// If you want throw it as `TypeError` or `RangeError`, you can use `JsTypeError/JsRangeError::from(Error).throw_into(env)` -#[derive(Debug, Clone)] -pub struct Error { - pub status: Status, - pub reason: String, -} - -impl error::Error for Error {} - -#[cfg(feature = "serde-json")] -impl ser::Error for Error { - fn custom(msg: T) -> Self { - Error::new(Status::InvalidArg, msg.to_string()) - } -} - -#[cfg(feature = "serde-json")] -impl de::Error for Error { - fn custom(msg: T) -> Self { - Error::new(Status::InvalidArg, msg.to_string()) - } -} - -#[cfg(feature = "serde-json")] -impl From for Error { - fn from(value: SerdeJSONError) -> Self { - Error::new(Status::InvalidArg, format!("{}", value)) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !self.reason.is_empty() { - write!(f, "{:?}, {}", self.status, self.reason) - } else { - write!(f, "{:?}", self.status) - } - } -} - -impl Error { - #[inline] - pub fn new(status: Status, reason: String) -> Self { - Error { status, reason } - } - - #[inline] - pub fn from_status(status: Status) -> Self { - Error { - status, - reason: "".to_owned(), - } - } - - #[inline] - pub fn from_reason(reason: String) -> Self { - Error { - status: Status::GenericFailure, - reason, - } - } -} - -impl From for Error { - fn from(error: std::ffi::NulError) -> Self { - Error { - status: Status::GenericFailure, - reason: format!("{}", error), - } - } -} - -impl From for Error { - fn from(error: std::io::Error) -> Self { - Error { - status: Status::GenericFailure, - reason: format!("{}", error), - } - } -} - -#[derive(Clone, Debug)] -pub struct ExtendedErrorInfo { - pub message: String, - pub engine_reserved: *mut c_void, - pub engine_error_code: u32, - pub error_code: Status, -} - -impl TryFrom for ExtendedErrorInfo { - type Error = Error; - - fn try_from(value: sys::napi_extended_error_info) -> Result { - Ok(Self { - message: unsafe { - CString::from_raw(value.error_message as *mut c_char) - .into_string() - .map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))? - }, - engine_error_code: value.engine_error_code, - engine_reserved: value.engine_reserved, - error_code: Status::from(value.error_code), - }) - } -} - -#[doc(hidden)] -#[macro_export(local_inner_macros)] -macro_rules! check_status { - ($code:expr) => {{ - let c = $code; - match c { - $crate::sys::Status::napi_ok => Ok(()), - _ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())), - } - }}; -} diff --git a/napi/src/js_values/error.rs b/napi/src/js_values/error.rs deleted file mode 100644 index 24819f5d..00000000 --- a/napi/src/js_values/error.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::ffi::CString; -use std::ptr; - -use crate::{check_status, sys, Env, Error}; - -pub struct JsError(Error); - -pub struct JsTypeError(Error); - -pub struct JsRangeError(Error); - -macro_rules! impl_object_methods { - ($js_value:ident, $kind:expr) => { - impl $js_value { - #[inline(always)] - /// # Safety - /// - /// This function is safety if env is not null ptr. - pub unsafe fn into_value(self, env: sys::napi_env) -> sys::napi_value { - let error_status = format!("{:?}", self.0.status); - let status_len = error_status.len(); - let error_code_string = CString::new(error_status).unwrap(); - let reason_len = self.0.reason.len(); - let reason = CString::new(self.0.reason).unwrap(); - let mut error_code = ptr::null_mut(); - let mut reason_string = ptr::null_mut(); - let mut js_error = ptr::null_mut(); - let create_code_status = sys::napi_create_string_utf8( - env, - error_code_string.as_ptr(), - status_len, - &mut error_code, - ); - debug_assert!(create_code_status == sys::Status::napi_ok); - let create_reason_status = - sys::napi_create_string_utf8(env, reason.as_ptr(), reason_len, &mut reason_string); - debug_assert!(create_reason_status == sys::Status::napi_ok); - let create_error_status = $kind(env, error_code, reason_string, &mut js_error); - debug_assert!(create_error_status == sys::Status::napi_ok); - js_error - } - - #[inline(always)] - /// # Safety - /// - /// This function is safety if env is not null ptr. - pub unsafe fn throw_into(self, env: sys::napi_env) { - let js_error = self.into_value(env); - let throw_status = sys::napi_throw(env, js_error); - debug_assert!(throw_status == sys::Status::napi_ok); - } - - #[inline(always)] - pub fn throw(&self, env: &Env) -> Result<(), Error> { - let error_status = format!("{:?}", self.0.status); - let status_len = error_status.len(); - let error_code_string = CString::new(error_status).unwrap(); - let reason_len = self.0.reason.len(); - let reason = CString::new(self.0.reason.clone()).unwrap(); - let mut error_code = ptr::null_mut(); - let mut reason_string = ptr::null_mut(); - let mut js_error = ptr::null_mut(); - check_status!(unsafe { - sys::napi_create_string_utf8( - env.0, - error_code_string.as_ptr(), - status_len, - &mut error_code, - ) - })?; - check_status!(unsafe { - sys::napi_create_string_utf8(env.0, reason.as_ptr(), reason_len, &mut reason_string) - })?; - check_status!(unsafe { $kind(env.0, error_code, reason_string, &mut js_error) })?; - check_status!(unsafe { sys::napi_throw(env.0, js_error) }) - } - } - - impl From for $js_value { - fn from(err: Error) -> Self { - Self(err) - } - } - }; -} - -impl_object_methods!(JsError, sys::napi_create_error); -impl_object_methods!(JsTypeError, sys::napi_create_type_error); -impl_object_methods!(JsRangeError, sys::napi_create_range_error); diff --git a/napi/src/module.rs b/napi/src/module.rs deleted file mode 100644 index 55041b07..00000000 --- a/napi/src/module.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::{Callback, Env, JsObject, Result}; - -pub struct Module { - pub env: Env, - pub exports: JsObject, -} - -#[deprecated(since = "1.0.0", note = "[module_exports] macro instead")] -impl Module { - pub fn create_named_method(&mut self, name: &str, function: Callback) -> Result<()> { - self - .exports - .set_named_property(name, self.env.create_function(name, function)?)?; - - Ok(()) - } -} diff --git a/package.json b/package.json index 08ef5439..e90b5e24 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,10 @@ "build": "tsc -p tsconfig.json --noEmit && yarn bundle && shx chmod 777 cli/scripts/index.js && node -r ts-node/register/transpile-only ./generate-triple-list.ts", "build:bench": "yarn --cwd ./bench build", "build:memory": "yarn --cwd ./memory-testing build", - "build:test": "yarn --cwd ./test_module build", - "build:test:aarch64": "yarn --cwd ./test_module build-aarch64", - "build:test:android": "yarn --cwd ./test_module build --target aarch64-linux-android", - "build:test:armv7": "yarn --cwd ./test_module build-armv7", + "build:test": "yarn --cwd ./examples/napi-compat-mode build && yarn --cwd ./examples/napi build", + "build:test:aarch64": "yarn --cwd ./examples/napi-compat-mode build-aarch64 && yarn --cwd ./examples/napi build-aarch64", + "build:test:android": "yarn --cwd ./examples/napi-compat-mode build --target aarch64-linux-android && yarn --cwd ./examples/napi build --target aarch64-linux-android", + "build:test:armv7": "yarn --cwd ./examples/napi-compat-mode build-armv7 && yarn --cwd ./examples/napi build-armv7", "bundle": "esbuild ./cli/src/index.ts --bundle --platform=node --outdir=cli/scripts --main-fields=\"module,main,browser\" --external:inquirer --external:iconv-lite", "format": "run-p format:md format:json format:yaml format:source format:rs", "format:md": "prettier --parser markdown --write ./**/*.md", diff --git a/test_module/package.json b/test_module/package.json deleted file mode 100644 index cdacb859..00000000 --- a/test_module/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "test-module", - "version": "1.0.0", - "scripts": { - "build": "node ../cli/scripts/index.js build --features \"latest\"", - "build-napi3": "node ../cli/scripts/index.js build --features \"napi3\"", - "build-aarch64": "node ../cli/scripts/index.js build --features \"latest\" --target aarch64-unknown-linux-gnu", - "build-armv7": "node ../cli/scripts/index.js build --features \"latest\" --target armv7-unknown-linux-gnueabihf", - "build-i686": "node ../cli/scripts/index.js build --features \"latest\" --target i686-pc-windows-msvc", - "build-i686-release": "node ../cli/scripts/index.js build --release --features \"latest\" --target i686-pc-windows-msvc", - "build-release": "node ../cli/scripts/index.js build --features \"latest\" --release", - "test": "node ./index.js" - } -} diff --git a/yarn.lock b/yarn.lock index 80f28433..03433a38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -889,7 +889,14 @@ node-gyp "^7.1.0" read-package-json-fast "^2.0.1" -"@octokit/auth-token@^2.4.0", "@octokit/auth-token@^2.4.4": +"@octokit/auth-token@^2.4.0": + version "2.5.0" + resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" + integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/auth-token@^2.4.4": version "2.4.5" resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== @@ -944,18 +951,30 @@ resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-10.0.0.tgz#db4335de99509021f501fc4e026e6ff495fe1e62" integrity sha512-k1iO2zKuEjjRS1EJb4FwSLk+iF6EGp+ZV0OMRViQoWhQ1fZTk9hg1xccZII5uyYoiqcbC73MRBmT45y1vp2PPg== +"@octokit/openapi-types@^10.2.2": + version "10.2.2" + resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-10.2.2.tgz#6c1c839d7d169feabaf1d2a69c79439c75d979cd" + integrity sha512-EVcXQ+ZrC04cg17AMg1ofocWMxHDn17cB66ZHgYc0eUwjFtxS0oBzkyw2VqIrHBwVgtfoYrq1WMQfQmMjUwthw== + "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== -"@octokit/plugin-paginate-rest@^2.16.0", "@octokit/plugin-paginate-rest@^2.2.0": +"@octokit/plugin-paginate-rest@^2.16.0": version "2.16.0" resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.0.tgz#09dbda2e5fbca022e3cdf76b63618f7b357c9f0c" integrity sha512-8YYzALPMvEZ35kgy5pdYvQ22Roz+BIuEaedO575GwE2vb/ACDqQn0xQrTJR4tnZCJn7pi8+AWPVjrFDaERIyXQ== dependencies: "@octokit/types" "^6.26.0" +"@octokit/plugin-paginate-rest@^2.2.0": + version "2.16.3" + resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.3.tgz#6dbf74a12a53e04da6ca731d4c93f20c0b5c6fe9" + integrity sha512-kdc65UEsqze/9fCISq6BxLzeB9qf0vKvKojIfzgwf4tEF+Wy6c9dXnPFE6vgpoDFB1Z5Jek5WFVU6vL1w22+Iw== + dependencies: + "@octokit/types" "^6.28.1" + "@octokit/plugin-request-log@^1.0.0", "@octokit/plugin-request-log@^1.0.4": version "1.0.4" resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" @@ -1039,6 +1058,13 @@ dependencies: "@octokit/openapi-types" "^10.0.0" +"@octokit/types@^6.28.1": + version "6.28.1" + resolved "https://registry.npmjs.org/@octokit/types/-/types-6.28.1.tgz#ab990d1fe952226055e81c7650480e6bacfb877c" + integrity sha512-XlxDoQLFO5JnFZgKVQTYTvXRsQFfr/GwDUU108NJ9R5yFPkA2qXhTJjYuul3vE4eLXP40FA2nysOu2zd6boE+w== + dependencies: + "@octokit/openapi-types" "^10.2.2" + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -1182,7 +1208,12 @@ resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== -"@types/node@*", "@types/node@>= 8", "@types/node@^16.9.4": +"@types/node@*", "@types/node@>= 8": + version "16.9.2" + resolved "https://registry.npmjs.org/@types/node/-/node-16.9.2.tgz#81f5a039d6ed1941f8cc57506c74e7c2b8fc64b9" + integrity sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w== + +"@types/node@^16.9.4": version "16.9.4" resolved "https://registry.npmjs.org/@types/node/-/node-16.9.4.tgz#a12f0ee7847cf17a97f6fdf1093cb7a9af23cca4" integrity sha512-KDazLNYAGIuJugdbULwFZULF9qQ13yNWEBFnfVpqlpgAAo6H/qnM9RjBgh0A0kmHf3XxAKLdN5mTIng9iUvVLA== @@ -2474,9 +2505,9 @@ dir-glob@^3.0.1: path-type "^4.0.0" docker-modem@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.0.tgz#cb912ad8daed42f858269fb3be6944df281ec12d" - integrity sha512-WwFajJ8I5geZ/dDZ5FDMDA6TBkWa76xWwGIGw8uzUjNUGCN0to83wJ8Oi1AxrJTC0JBn+7fvIxUctnawtlwXeg== + version "3.0.2" + resolved "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.2.tgz#39a52b36b94119a913c8eeba5e67e28993e84896" + integrity sha512-K6ahu0IaJXqRqiAUZYo01n/6MkHir1c5mVJx1//JpyRmePYoIOC7oPR2vSx8rCaxIt7qRc77v9ewxljl6Qatdg== dependencies: debug "^4.1.1" readable-stream "^3.5.0" @@ -6543,7 +6574,12 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typanion@^3.3.1, typanion@^3.5.0: +typanion@^3.3.1: + version "3.4.1" + resolved "https://registry.npmjs.org/typanion/-/typanion-3.4.1.tgz#d14b9cf056a592f59083a9ecff3a3ddd3855e706" + integrity sha512-/l41vTqe9s5FYi8nWLiTiB5pUbXWasvnUsmwwDMUc9PI/mUb0ojmWQ4EP8qnNYunPbFNQGjsqY61rQhNslQ+qQ== + +typanion@^3.5.0: version "3.5.0" resolved "https://registry.npmjs.org/typanion/-/typanion-3.5.0.tgz#17004bbe65c1947efcb0295ae2291d8dfaf6245c" integrity sha512-WTwtzR0EXFidHrrzyWYyb+jeFLxOVLW1VMaKOqN/JekdqJs6UiFLGv0QjKV2dzn1ub3VI7duDJ/ZXHM8UX/3dw==