Skip to content

Commit

Permalink
feat(neon-macros): Convert snake_case to camelCase when exporting fun…
Browse files Browse the repository at this point in the history
…ctions
  • Loading branch information
kjvalencik committed Nov 22, 2024
1 parent 7780a2f commit 4f9bab0
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 31 deletions.
43 changes: 42 additions & 1 deletion crates/neon-macros/src/export/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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");
}
}
20 changes: 10 additions & 10 deletions test/napi/lib/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -73,20 +73,20 @@ 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 () => {
const time = 500;
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);
Expand All @@ -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);
});
}
6 changes: 3 additions & 3 deletions test/napi/lib/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
Expand Down
20 changes: 7 additions & 13 deletions test/napi/lib/futures.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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],
Expand Down
8 changes: 4 additions & 4 deletions test/napi/src/js/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -48,7 +48,7 @@ fn json_sort(mut items: Vec<String>) -> Vec<String> {
}

#[neon::export(json, name = "renamedJsonSort")]
fn renamed_json_sort(items: Vec<String>) -> Vec<String> {
fn rs_renamed_json_sort(items: Vec<String>) -> Vec<String> {
json_sort(items)
}

Expand All @@ -58,7 +58,7 @@ fn json_sort_task(items: Vec<String>) -> Vec<String> {
}

#[neon::export(json, name = "renamedJsonSortTask", task)]
fn renamed_json_sort_task(items: Vec<String>) -> Vec<String> {
fn rs_renamed_json_sort_task(items: Vec<String>) -> Vec<String> {
json_sort(items)
}

Expand Down

0 comments on commit 4f9bab0

Please sign in to comment.