From 4f9bab0ddb403ff4021ce16310ef41b6345c55f7 Mon Sep 17 00:00:00 2001 From: "K.J. Valencik" Date: Fri, 22 Nov 2024 12:53:30 -0500 Subject: [PATCH] feat(neon-macros): Convert snake_case to camelCase when exporting functions --- crates/neon-macros/src/export/function/mod.rs | 43 ++++++++++++++++++- test/napi/lib/export.js | 20 ++++----- test/napi/lib/extract.js | 6 +-- test/napi/lib/futures.js | 20 +++------ test/napi/src/js/export.rs | 8 ++-- 5 files changed, 66 insertions(+), 31 deletions(-) diff --git a/crates/neon-macros/src/export/function/mod.rs b/crates/neon-macros/src/export/function/mod.rs index 5ba8b57b1..c6783dea6 100644 --- a/crates/neon-macros/src/export/function/mod.rs +++ b/crates/neon-macros/src/export/function/mod.rs @@ -111,7 +111,10 @@ pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenS let export_name = meta .name .map(|name| quote::quote!(#name)) - .unwrap_or_else(|| quote::quote!(stringify!(#name))); + .unwrap_or_else(|| { + let name = to_camel_case(&name.to_string()); + quote::quote!(#name) + }); // Generate the function that is registered to create the function on addon initialization. // Braces are included to prevent names from polluting user code. @@ -145,6 +148,28 @@ pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenS .into() } +fn to_camel_case(name: &str) -> String { + let mut should_upper = false; + let mut out = String::with_capacity(name.len()); + + for c in name.chars() { + if c == '_' { + should_upper = true; + continue; + } + + if should_upper { + out.extend(c.to_uppercase()); + } else { + out.push(c); + }; + + should_upper = false; + } + + out +} + // Determine the number of arguments to the function fn count_args(sig: &syn::Signature, has_context: bool, has_this: bool) -> usize { let n = sig.inputs.len(); @@ -386,3 +411,19 @@ fn check_this(opts: &meta::Meta, sig: &syn::Signature, has_context: bool) -> boo _ => false, } } + +#[cfg(test)] +mod test { + #[test] + fn to_camel_case() { + use super::to_camel_case; + + assert_eq!(to_camel_case(""), ""); + assert_eq!(to_camel_case("one"), "one"); + assert_eq!(to_camel_case("two_words"), "twoWords"); + assert_eq!(to_camel_case("three_word_name"), "threeWordName"); + assert_eq!(to_camel_case("extra__underscore"), "extraUnderscore"); + assert_eq!(to_camel_case("PreserveCase"), "PreserveCase"); + assert_eq!(to_camel_case("PreServe_case"), "PreServeCase"); + } +} diff --git a/test/napi/lib/export.js b/test/napi/lib/export.js index 0af8f95f0..8deb8bbb6 100644 --- a/test/napi/lib/export.js +++ b/test/napi/lib/export.js @@ -22,16 +22,16 @@ function globals() { function functions() { it("void function", () => { - assert.strictEqual(addon.no_args_or_return(), undefined); + assert.strictEqual(addon.noArgsOrReturn(), undefined); }); it("add - sync", () => { - assert.strictEqual(addon.simple_add(1, 2), 3); + assert.strictEqual(addon.simpleAdd(1, 2), 3); assert.strictEqual(addon.renamedAdd(1, 2), 3); }); it("add - task", async () => { - const p1 = addon.add_task(1, 2); + const p1 = addon.addTask(1, 2); const p2 = addon.renamedAddTask(1, 2); assert.ok(p1 instanceof Promise); @@ -45,14 +45,14 @@ function functions() { const arr = ["b", "c", "a"]; const expected = [...arr].sort(); - assert.deepStrictEqual(addon.json_sort(arr), expected); + assert.deepStrictEqual(addon.jsonSort(arr), expected); assert.deepStrictEqual(addon.renamedJsonSort(arr), expected); }); it("json sort - task", async () => { const arr = ["b", "c", "a"]; const expected = [...arr].sort(); - const p1 = addon.json_sort_task(arr); + const p1 = addon.jsonSortTask(arr); const p2 = addon.renamedJsonSortTask(arr); assert.ok(p1 instanceof Promise); @@ -63,7 +63,7 @@ function functions() { }); it("can use context and handles", () => { - const actual = addon.concat_with_cx_and_handle("Hello,", " World!"); + const actual = addon.concatWithCxAndHandle("Hello,", " World!"); const expected = "Hello, World!"; assert.strictEqual(actual, expected); @@ -73,7 +73,7 @@ function functions() { const msg = "Oh, no!"; const expected = new Error(msg); - assert.throws(() => addon.fail_with_throw(msg), expected); + assert.throws(() => addon.failWithThrow(msg), expected); }); it("tasks are concurrent", async () => { @@ -81,12 +81,12 @@ function functions() { const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const start = process.hrtime.bigint(); - await Promise.all([addon.sleep_task(time), sleep(time)]); + await Promise.all([addon.sleepTask(time), sleep(time)]); const end = process.hrtime.bigint(); const duration = end - start; - // If `addon.sleep_task` blocks the thread, the tasks will run sequentially + // If `addon.sleepTask` blocks the thread, the tasks will run sequentially // and take a minimum of 2x `time`. Since they are run concurrently, we // expect the time to be closer to 1x `time`. const maxExpected = 2000000n * BigInt(time); @@ -95,6 +95,6 @@ function functions() { }); it("can use generic Cx in exported functions", () => { - assert.strictEqual(addon.number_with_cx(42), 42); + assert.strictEqual(addon.numberWithCx(42), 42); }); } diff --git a/test/napi/lib/extract.js b/test/napi/lib/extract.js index 35c0ef338..62926e3da 100644 --- a/test/napi/lib/extract.js +++ b/test/napi/lib/extract.js @@ -64,11 +64,11 @@ describe("Extractors", () => { }); it("Either", () => { - assert.strictEqual(addon.extract_either("hello"), "String: hello"); - assert.strictEqual(addon.extract_either(42), "Number: 42"); + assert.strictEqual(addon.extractEither("hello"), "String: hello"); + assert.strictEqual(addon.extractEither(42), "Number: 42"); assert.throws( - () => addon.extract_either({}), + () => addon.extractEither({}), (err) => { assert.match(err.message, /expected either.*String.*f64/); assert.match(err.left.message, /expected string/); diff --git a/test/napi/lib/futures.js b/test/napi/lib/futures.js index 2c3025a7e..d3e4558a9 100644 --- a/test/napi/lib/futures.js +++ b/test/napi/lib/futures.js @@ -58,29 +58,23 @@ describe("Futures", () => { describe("Exported Async Functions", () => { it("should be able to call `async fn`", async () => { - assert.strictEqual(await addon.async_fn_add(1, 2), 3); + assert.strictEqual(await addon.asyncFnAdd(1, 2), 3); }); it("should be able to call fn with async block", async () => { - assert.strictEqual(await addon.async_add(1, 2), 3); + assert.strictEqual(await addon.asyncAdd(1, 2), 3); }); it("should be able to call fallible `async fn`", async () => { - assert.strictEqual(await addon.async_fn_div(10, 2), 5); + assert.strictEqual(await addon.asyncFnDiv(10, 2), 5); - await assertRejects(() => addon.async_fn_div(10, 0), /Divide by zero/); - }); - - it("should be able to call fallible `async fn`", async () => { - assert.strictEqual(await addon.async_fn_div(10, 2), 5); - - await assertRejects(() => addon.async_fn_div(10, 0), /Divide by zero/); + await assertRejects(() => addon.asyncFnDiv(10, 0), /Divide by zero/); }); it("should be able to call fallible fn with async block", async () => { - assert.strictEqual(await addon.async_div(10, 2), 5); + assert.strictEqual(await addon.asyncDiv(10, 2), 5); - await assertRejects(() => addon.async_div(10, 0), /Divide by zero/); + await assertRejects(() => addon.asyncDiv(10, 0), /Divide by zero/); }); it("should be able to code on the event loop before and after async", async () => { @@ -100,7 +94,7 @@ describe("Futures", () => { process.on("async_with_events", eventHandler); try { - let res = await addon.async_with_events([ + let res = await addon.asyncWithEvents([ [1, 2], [3, 4], [5, 6], diff --git a/test/napi/src/js/export.rs b/test/napi/src/js/export.rs index 36f0b75c7..525332881 100644 --- a/test/napi/src/js/export.rs +++ b/test/napi/src/js/export.rs @@ -27,7 +27,7 @@ fn simple_add(a: f64, b: f64) -> f64 { } #[neon::export(name = "renamedAdd")] -fn renamed_add(a: f64, b: f64) -> f64 { +fn rs_renamed_add(a: f64, b: f64) -> f64 { simple_add(a, b) } @@ -37,7 +37,7 @@ fn add_task(a: f64, b: f64) -> f64 { } #[neon::export(task, name = "renamedAddTask")] -fn renamed_add_task(a: f64, b: f64) -> f64 { +fn rs_renamed_add_task(a: f64, b: f64) -> f64 { add_task(a, b) } @@ -48,7 +48,7 @@ fn json_sort(mut items: Vec) -> Vec { } #[neon::export(json, name = "renamedJsonSort")] -fn renamed_json_sort(items: Vec) -> Vec { +fn rs_renamed_json_sort(items: Vec) -> Vec { json_sort(items) } @@ -58,7 +58,7 @@ fn json_sort_task(items: Vec) -> Vec { } #[neon::export(json, name = "renamedJsonSortTask", task)] -fn renamed_json_sort_task(items: Vec) -> Vec { +fn rs_renamed_json_sort_task(items: Vec) -> Vec { json_sort(items) }