Repository: Aleph-Alpha/ts-rs
Pull request #231 (2024-02-10 17:08:57)

Fix `#[ts(skip)]` in newtype variants


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f657100..eae40cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
 - Export Rust doc comments/attributes on structs/enums as TSDoc strings ([#187](https://github.com/Aleph-Alpha/ts-rs/pull/187))
 
 ### Fixes
+- fix `#[ts(skip)]` and `#[serde(skip)]` in variants of adjacently or internally tagged enums ([#231](https://github.com/Aleph-Alpha/ts-rs/pull/231))
 - `rename_all` with `camelCase` produces wrong names if fields were already in camelCase ([#198](https://github.com/Aleph-Alpha/ts-rs/pull/198))
 - Improve support for references ([#199](https://github.com/Aleph-Alpha/ts-rs/pull/199))
 
diff --git a/macros/src/types/enum.rs b/macros/src/types/enum.rs
index 6aaf0d4..4fb0159 100644
--- a/macros/src/types/enum.rs
+++ b/macros/src/types/enum.rs
@@ -115,18 +115,20 @@ fn format_variant(
                     ..
                 } = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;
 
-                let ty = match (type_override, type_as) {
-                    (Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"),
-                    (Some(type_override), None) => quote! { #type_override },
-                    (None, Some(type_as)) => {
-                        format_type(&syn::parse_str::<Type>(&type_as)?, dependencies, generics)
-                    }
-                    (None, None) => format_type(&unnamed.unnamed[0].ty, dependencies, generics),
-                };
+
 
                 if skip {
                     quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
                 } else {
+                    let ty = match (type_override, type_as) {
+                        (Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"),
+                        (Some(type_override), None) => quote! { #type_override },
+                        (None, Some(type_as)) => {
+                            format_type(&syn::parse_str::<Type>(&type_as)?, dependencies, generics)
+                        }
+                        (None, None) => format_type(&unnamed.unnamed[0].ty, dependencies, generics),
+                    };
+
                     quote!(format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #name, #content, #ty))
                 }
             }
@@ -161,18 +163,18 @@ fn format_variant(
                         ..
                     } = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;
 
-                    let ty = match (type_override, type_as) {
-                        (Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"),
-                        (Some(type_override), None) => quote! { #type_override },
-                        (None, Some(type_as)) => {
-                            format_type(&syn::parse_str::<Type>(&type_as)?, dependencies, generics)
-                        }
-                        (None, None) => format_type(&unnamed.unnamed[0].ty, dependencies, generics),
-                    };
-
                     if skip {
                         quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
                     } else {
+                        let ty = match (type_override, type_as) {
+                            (Some(_), Some(_)) => syn_err!("`type` is not compatible with `as`"),
+                            (Some(type_override), None) => quote! { #type_override },
+                            (None, Some(type_as)) => {
+                                format_type(&syn::parse_str::<Type>(&type_as)?, dependencies, generics)
+                            }
+                            (None, None) => format_type(&unnamed.unnamed[0].ty, dependencies, generics),
+                        };
+
                         quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #name, #ty))
                     }
                 }


Test patch:
diff --git a/ts-rs/tests/skip.rs b/ts-rs/tests/skip.rs
index 563788d..f730760 100644
--- a/ts-rs/tests/skip.rs
+++ b/ts-rs/tests/skip.rs
@@ -1,16 +1,128 @@
-#![allow(dead_code)]
+#![allow(dead_code, unused_imports)]
 
+use std::error::Error;
+use serde::Serialize;
 use ts_rs::TS;
 
-#[derive(TS)]
-struct Skip {
-    a: i32,
-    b: i32,
-    #[ts(skip)]
-    c: String,
-}
+struct Unsupported;
 
 #[test]
-fn test_def() {
+fn simple() {
+    #[derive(TS)]
+    struct Skip {
+        a: i32,
+        b: i32,
+        #[ts(skip)]
+        c: String,
+        #[ts(skip)]
+        d: Box<dyn Error>
+    }
+
     assert_eq!(Skip::inline(), "{ a: number, b: number, }");
 }
+
+#[test]
+fn externally_tagged() {
+    #[cfg_attr(feature = "serde-compat", derive(Serialize, TS))]
+    #[cfg_attr(not(feature = "serde-compat"), derive(TS))]
+    enum Externally {
+        A (
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            Unsupported,
+        ),
+        B(
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            Unsupported,
+            i32
+        ),
+        C {
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            x: Unsupported,
+        },
+        D {
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            x: Unsupported,
+            y: i32
+        },
+    }
+
+    // TODO: variant C should probably not generate `{}`
+    assert_eq!(
+        Externally::decl(),
+        r#"type Externally = "A" | { "B": [number] } | { "C": {  } } | { "D": { y: number, } };"#
+    );
+}
+
+#[test]
+fn internally_tagged() {
+    #[cfg_attr(feature = "serde-compat", derive(Serialize, TS))]
+    #[cfg_attr(not(feature = "serde-compat"), derive(TS))]
+    #[cfg_attr(feature = "serde-compat", serde(tag = "t"))]
+    #[cfg_attr(not(feature = "serde-compat"), ts(tag = "t"))]
+    enum Internally {
+        A (
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            Unsupported,
+        ),
+        B {
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            x: Unsupported,
+        },
+        C {
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            x: Unsupported,
+            y: i32,
+        },
+    }
+
+    assert_eq!(
+        Internally::decl(),
+        r#"type Internally = { "t": "A" } | { "t": "B",  } | { "t": "C", y: number, };"#
+    );
+}
+
+#[test]
+fn adjacently_tagged() {
+    #[cfg_attr(feature = "serde-compat", derive(Serialize, TS))]
+    #[cfg_attr(not(feature = "serde-compat"), derive(TS))]
+    #[cfg_attr(feature = "serde-compat", serde(tag = "t", content = "c"))]
+    #[cfg_attr(not(feature = "serde-compat"), ts(tag = "t", content = "c"))]
+    enum Adjacently {
+        A (
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            Unsupported,
+        ),
+        B(
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            Unsupported,
+            i32
+        ),
+        C {
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            x: Unsupported,
+        },
+        D {
+            #[cfg_attr(feature = "serde-compat", serde(skip))]
+            #[cfg_attr(not(feature = "serde-compat"), ts(skip))]
+            x: Unsupported,
+            y: i32
+        },
+
+    }
+
+    // TODO: variant C should probably not generate `{ .., "c": { } }`
+    assert_eq!(
+        Adjacently::decl(),
+        r#"type Adjacently = { "t": "A" } | { "t": "B", "c": [number] } | { "t": "C", "c": {  } } | { "t": "D", "c": { y: number, } };"#
+    );
+}