Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to generate unions instead of TS enums in Typescript #145

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The test suite can of course be run normally without updating any expectations:
cargo test -p typeshare-core
```

If you find yourself needing to update expectations for a specific test only, run the following (subsituting the name of your test in for the last arg):
If you find yourself needing to update expectations for a specific test only, run the following (substituting the name of your test in for the last arg):

```
env UPDATE_EXPECT=1 cargo test -p typeshare-core --test snapshot_tests -- can_handle_serde_rename_all::swift
Expand Down
18 changes: 18 additions & 0 deletions core/data/tests/can_generate_unit_ts_union_enum/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#[typeshare(ts_union)]
pub enum UnitEnumMultiple {
VariantA,
VariantB,
VariantC,
}

#[typeshare(ts_union)]
pub enum UnitEnumOne {
VariantA,
}

#[typeshare(ts_union)]
pub enum UnitEnumSkip {
#[typeshare(skip)]
VariantA,
VariantB,
}
6 changes: 6 additions & 0 deletions core/data/tests/can_generate_unit_ts_union_enum/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type UnitEnumMultiple = "VariantA" | "VariantB" | "VariantC";

export type UnitEnumOne = "VariantA";

export type UnitEnumSkip = "VariantB";

2 changes: 1 addition & 1 deletion core/src/language/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ impl Go {
write_comments(w, 0, &e.shared().comments)?;

match e {
RustEnum::Unit(shared) => {
RustEnum::Unit { shared, .. } => {
writeln!(
w,
"type {} string",
Expand Down
4 changes: 2 additions & 2 deletions core/src/language/kotlin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ impl Language for Kotlin {
.unwrap_or_default();

match e {
RustEnum::Unit(shared) => {
RustEnum::Unit { shared, .. } => {
write!(
w,
"enum class {}{}(val string: String) ",
Expand All @@ -192,7 +192,7 @@ impl Language for Kotlin {
impl Kotlin {
fn write_enum_variants(&mut self, w: &mut dyn Write, e: &RustEnum) -> std::io::Result<()> {
match e {
RustEnum::Unit(shared) => {
RustEnum::Unit { shared, .. } => {
for v in &shared.variants {
self.write_comments(w, 1, &v.shared().comments)?;
writeln!(w, "\t@SerialName({:?})", &v.shared().id.renamed)?;
Expand Down
4 changes: 2 additions & 2 deletions core/src/language/scala.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ impl Language for Scala {
.unwrap_or_default();

match e {
RustEnum::Unit(shared) => {
RustEnum::Unit { shared, .. } => {
writeln!(
w,
"sealed trait {}{} {{",
Expand All @@ -220,7 +220,7 @@ impl Language for Scala {
impl Scala {
fn write_enum_variants(&mut self, w: &mut dyn Write, e: &RustEnum) -> std::io::Result<()> {
match e {
RustEnum::Unit(shared) => {
RustEnum::Unit { shared, .. } => {
for v in shared.variants.iter() {
self.write_comments(w, 1, &v.shared().comments)?;
writeln!(
Expand Down
4 changes: 2 additions & 2 deletions core/src/language/swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ impl Language for Swift {
let enum_name =
swift_keyword_aware_rename(&format!("{}{}", self.prefix, shared.id.renamed));
let always_present = match e {
RustEnum::Unit(_) => {
RustEnum::Unit { .. } => {
let mut always_present = vec!["String".into()];
always_present.append(&mut self.get_default_decorators());
always_present
Expand Down Expand Up @@ -547,7 +547,7 @@ impl Swift {
let mut coding_keys = Vec::new();

match e {
RustEnum::Unit(shared) => {
RustEnum::Unit { shared, .. } => {
for v in &shared.variants {
let variant_name = v.shared().id.original.to_camel_case();

Expand Down
27 changes: 25 additions & 2 deletions core/src/language/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,14 @@ impl Language for TypeScript {
.unwrap_or_default();

match e {
RustEnum::Unit(shared) => {
RustEnum::Unit { shared, ts_union } if *ts_union => {
write!(w, "export type {} = ", shared.id.renamed)?;

self.write_enum_variants(w, e)?;

writeln!(w, ";\n")
}
RustEnum::Unit { shared, .. } => {
write!(
w,
"export enum {}{} {{",
Expand Down Expand Up @@ -170,9 +177,25 @@ impl Language for TypeScript {
impl TypeScript {
fn write_enum_variants(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> {
match e {
// Write all the unit variants out (there can only be unit variants in
// this case). Format it as ts union.
RustEnum::Unit { shared, ts_union } if *ts_union => {
let variants = shared
.variants
.iter()
.filter_map(|v| match v {
RustEnumVariant::Unit(shared) => Some(format!("\"{}\"", shared.id.renamed)),
_ => None,
})
.collect::<Vec<_>>()
.join(" | ");

write!(w, "{}", variants)
}

// Write all the unit variants out (there can only be unit variants in
// this case)
RustEnum::Unit(shared) => shared.variants.iter().try_for_each(|v| match v {
RustEnum::Unit { shared, .. } => shared.variants.iter().try_for_each(|v| match v {
RustEnumVariant::Unit(shared) => {
writeln!(w)?;
self.write_comments(w, 1, &shared.comments)?;
Expand Down
23 changes: 22 additions & 1 deletion core/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ fn parse_enum(e: &ItemEnum) -> Result<RustItem, ParseError> {
let maybe_tag_key = get_tag_key(&e.attrs);
let maybe_content_key = get_content_key(&e.attrs);

let ts_union = get_ts_union(&e.attrs);

// Parse all of the enum's variants
let variants = e
.variants
Expand Down Expand Up @@ -309,7 +311,7 @@ fn parse_enum(e: &ItemEnum) -> Result<RustItem, ParseError> {
});
}

Ok(RustItem::Enum(RustEnum::Unit(shared)))
Ok(RustItem::Enum(RustEnum::Unit { shared, ts_union }))
} else {
// At least one enum variant is either a tuple or an anonymous struct

Expand Down Expand Up @@ -643,6 +645,25 @@ fn get_content_key(attrs: &[syn::Attribute]) -> Option<String> {
.and_then(literal_as_string)
}

fn get_ts_union(attrs: &[syn::Attribute]) -> bool {
let ts_union = Ident::new("ts_union", Span::call_site());

attrs.iter().any(|attr| {
get_typeshare_meta_items(attr)
.into_iter()
.any(|arg| match arg {
NestedMeta::Meta(Meta::Path(path)) => {
if let Some(ident) = path.get_ident() {
*ident == ts_union
} else {
false
}
}
_ => false,
})
})
}

fn serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
get_serde_name_value_meta_items(attrs, "rename")
.next()
Expand Down
8 changes: 6 additions & 2 deletions core/src/rust_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,11 @@ pub enum RustEnum {
/// Yay,
/// }
/// ```
Unit(RustEnumShared),
Unit {
/// Represents this enum in typescript as union instead of ts enum
ts_union: bool,
shared: RustEnumShared,
},
/// An algebraic enum
///
/// An example of such an enum:
Expand Down Expand Up @@ -513,7 +517,7 @@ impl RustEnum {
/// Get a reference to the inner shared content
pub fn shared(&self) -> &RustEnumShared {
match self {
Self::Unit(shared) | Self::Algebraic { shared, .. } => shared,
Self::Unit { shared, .. } | Self::Algebraic { shared, .. } => shared,
}
}
}
Expand Down
10 changes: 3 additions & 7 deletions core/src/topsort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ fn get_enum_dependencies(
seen: &mut HashSet<String>,
) {
match enm {
RustEnum::Unit(_) => {}
RustEnum::Unit { .. } => {}
RustEnum::Algebraic {
tag_key: _,
content_key: _,
Expand Down Expand Up @@ -185,12 +185,8 @@ pub(crate) fn topsort(things: Vec<&RustItem>) -> Vec<&RustItem> {
let types = HashMap::from_iter(things.iter().map(|&thing| {
let id = match thing {
RustItem::Enum(e) => match e {
RustEnum::Algebraic {
tag_key: _,
content_key: _,
shared,
} => shared.id.original.clone(),
RustEnum::Unit(shared) => shared.id.original.clone(),
RustEnum::Algebraic { shared, .. } => shared.id.original.clone(),
RustEnum::Unit { shared, .. } => shared.id.original.clone(),
},
RustItem::Struct(strct) => strct.id.original.clone(),
RustItem::Alias(ta) => ta.id.original.clone(),
Expand Down
1 change: 1 addition & 0 deletions core/tests/snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ tests! {
scala,
typescript
];
can_generate_unit_ts_union_enum: [typescript];
can_generate_generic_struct: [
swift {
prefix: "Core".into(),
Expand Down
19 changes: 18 additions & 1 deletion docs/src/usage/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,24 @@ This would generate the following Kotlin code:
typealias Options = String
```

### TypeScript enum representation

For unit enums, you can choose to generate a TypeScript union type instead
of a TypeScript enum.

```rust
#[typeshare(ts_union)]
pub enum UnitEnum {
VariantA,
VariantB,
VariantC,
}
```

This would generate the following TypeScript code:
```ts
export type UnitEnum = "VariantA" | "VariantB" | "VariantC";
```

## The `#[serde]` Attribute

Expand Down Expand Up @@ -155,4 +172,4 @@ export interface MyStruct {
a: number;
c: number;
}
```
```
Loading