diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index f150597f..18d7ddd2 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -71,7 +71,7 @@ macro_rules! attrgen { (ts_return_type, TsReturnType(Span, String, Span)), (ts_type, TsType(Span, String, Span)), (ts_generic_types, TsGenericTypes(Span, String, Span)), - (string_enum, StringEnum(Span)), + (string_enum, StringEnum(Span, Option<(String, Span)>)), (use_nullable, UseNullable(Span, Option), false), // impl later @@ -132,6 +132,21 @@ macro_rules! methods { } }; + (@method $name:ident, $variant:ident(Span, Option<(String, Span)>)) => { + pub fn $name(&self) -> Option> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(_, s) => { + a.0.set(true); + Some(s.as_ref()) + } + _ => None, + }) + .next() + } + }; + (@method $name:ident, $variant:ident(Span, Option), $default_value:literal) => { pub fn $name(&self) -> bool { self.attrs @@ -396,6 +411,21 @@ impl Parse for BindgenAttr { return Ok(BindgenAttr::$variant(attr_span, val, span)) }); + (@parser $variant:ident(Span, Option<(String, Span)>)) => ({ + if let Ok(_) = input.parse::() { + let val = match input.parse::() { + Ok(str) => Some((str.value(), str.span())), + Err(_) => { + let ident = input.parse::()?.0; + Some((ident.to_string(), ident.span())) + } + }; + return Ok(BindgenAttr::$variant(attr_span, val)) + } else { + return Ok(BindgenAttr::$variant(attr_span, None)) + } + }); + (@parser $variant:ident(Span, Option), $default_value:literal) => ({ if let Ok(_) = input.parse::() { let (val, _) = match input.parse::() { diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index ff01a070..8951ffde 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -1062,26 +1062,48 @@ impl ConvertToAST for syn::ItemEnum { .map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string()); let variants = match opts.string_enum() { - Some(_) => self - .variants - .iter() - .map(|v| { - if !matches!(v.fields, syn::Fields::Unit) { - bail_span!(v.fields, "Structured enum is not supported in #[napi]") + Some(case) => { + let case = case.map(|c| Ok::(match c.0.as_str() { + "lowercase" => Case::Flat, + "UPPERCASE" => Case::UpperFlat, + "PascalCase" => Case::Pascal, + "camelCase" => Case::Camel, + "snake_case" => Case::Snake, + "SCREAMING_SNAKE_CASE" => Case::UpperSnake, + "kebab-case" => Case::Kebab, + "SCREAMING-KEBAB-CASE" => Case::UpperKebab, + _ => { + bail_span!(self, "Unknown string enum case. Possible values are \"lowercase\", \"UPPERCASE\", \"PascalCase\", \"camelCase\", \"snake_case\", \"SCREAMING_SNAKE_CASE\", \"kebab-case\", or \"SCREAMING-KEBAB-CASE\"") } - if matches!(&v.discriminant, Some((_, _))) { - bail_span!( - v.fields, - "Literal values are not supported with string enum in #[napi]" - ) - } - Ok(NapiEnumVariant { - name: v.ident.clone(), - val: NapiEnumValue::String(v.ident.to_string()), - comments: extract_doc_comments(&v.attrs), + })).transpose()?; + + self + .variants + .iter() + .map(|v| { + if !matches!(v.fields, syn::Fields::Unit) { + bail_span!(v.fields, "Structured enum is not supported in #[napi]") + } + if matches!(&v.discriminant, Some((_, _))) { + bail_span!( + v.fields, + "Literal values are not supported with string enum in #[napi]" + ) + } + + let mut val = v.ident.to_string(); + if let Some(case) = case { + val = val.to_case(case) + }; + + Ok(NapiEnumVariant { + name: v.ident.clone(), + val: NapiEnumValue::String(val), + comments: extract_doc_comments(&v.attrs), + }) }) - }) - .collect::>>()?, + .collect::>>()? + } None => { let mut last_variant_val: i32 = -1; diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index ed0416e5..ab7a246a 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -617,6 +617,12 @@ Generated by [AVA](https://avajs.dev). name: string␊ }␊ ␊ + export const enum StringEnum {␊ + VariantOne = 'variantone',␊ + VariantTwo = 'varianttwo',␊ + VariantThree = 'variantthree'␊ + }␊ + ␊ export function sumBtreeMapping(nums: Record): number␊ ␊ export function sumIndexMapping(nums: Record): number␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index 4855e659..37b58329 100644 Binary files a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap differ diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index d4241d4d..22206d79 100644 Binary files a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap differ diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index cc92f5d1..c7c611ee 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -509,6 +509,7 @@ module.exports.roundtripStr = nativeBinding.roundtripStr module.exports.runScript = nativeBinding.runScript module.exports.setSymbolInObj = nativeBinding.setSymbolInObj module.exports.Status = nativeBinding.Status +module.exports.StringEnum = nativeBinding.StringEnum module.exports.sumBtreeMapping = nativeBinding.sumBtreeMapping module.exports.sumIndexMapping = nativeBinding.sumIndexMapping module.exports.sumMapping = nativeBinding.sumMapping diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index 24feb183..1ebc2ce4 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -607,6 +607,12 @@ export interface StrictObject { name: string } +export const enum StringEnum { + VariantOne = 'variantone', + VariantTwo = 'varianttwo', + VariantThree = 'variantthree' +} + export function sumBtreeMapping(nums: Record): number export function sumIndexMapping(nums: Record): number diff --git a/examples/napi/src/enum.rs b/examples/napi/src/enum.rs index 11435af7..7192662a 100644 --- a/examples/napi/src/enum.rs +++ b/examples/napi/src/enum.rs @@ -19,6 +19,13 @@ pub enum Status { Ready, } +#[napi(string_enum = "lowercase")] +pub enum StringEnum { + VariantOne, + VariantTwo, + VariantThree, +} + /// You could break the step and for an new continuous value. #[napi] pub enum CustomNumEnum {