Repository: Aleph-Alpha/ts-rs
Pull request #203 (2024-01-24 20:26:25)

Change declarations to use type instead of interface
Change TS code generation to use `type` instead of `interface` to make the use of the type intersection (`&`) operator possible


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 485516b..f1d62df 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,14 +3,8 @@ If you are unsure what to work on or want to discuss your idea, feel free to ope
 
 ### Documentation
 After implementing a new feature, please document it in the doc comment on `TS` in `ts_rs/lib.rs`.  
-`README.md` is generated from the module doc comment in `ts_rs/lib.rs`. If you added/updated documentation there, go to the `ts-rs/` directory and run one of the following commands:
-
-On Windows:
-`cargo readme -o ..\README.md`
-
-On other systems:
-`cargo readme > ../README.md`
-
+`README.md` is generated from the module doc comment in `ts_rs/lib.rs`. If you added/updated documentation there, run
+`cargo readme > ../README.md` from the `ts-rs/` directory.  
 You can install `cargo readme` by running `cargo install cargo-readme`.
 
 
@@ -22,4 +16,4 @@ There is nothing special going on here - just run `cargo build`.
 To run the test suite, just run `cargo test` in the root directory.  
 
 ### Formatting
-To ensure proper formatting, please run `cargo +nightly fmt`.
+To ensure proper formatting, please run `cargo +nightly fmt`.
\ No newline at end of file
diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs
index 8786124..1a3249b 100644
--- a/macros/src/types/named.rs
+++ b/macros/src/types/named.rs
@@ -45,7 +45,7 @@ pub(crate) fn named(
                 #fields,
             )
         },
-        decl: quote!(format!("interface {}{} {}", #name, #generic_args, Self::inline())),
+        decl: quote!(format!("type {}{} = {}", #name, #generic_args, Self::inline())),
         inline_flattened: Some(fields),
         name: name.to_owned(),
         dependencies,


Test patch:
diff --git a/ts-rs/tests/chrono.rs b/ts-rs/tests/chrono.rs
index cc52784..d9bc9ee 100644
--- a/ts-rs/tests/chrono.rs
+++ b/ts-rs/tests/chrono.rs
@@ -27,6 +27,6 @@ fn chrono() {
 
     assert_eq!(
         Chrono::decl(),
-        "interface Chrono { date: [string, string, string, string], time: string, date_time: [string, string, string, string], duration: string, month: string, weekday: string, }"
+        "type Chrono = { date: [string, string, string, string], time: string, date_time: [string, string, string, string], duration: string, month: string, weekday: string, }"
     )
 }
diff --git a/ts-rs/tests/export_manually.rs b/ts-rs/tests/export_manually.rs
index aaae5cf..33a7af1 100644
--- a/ts-rs/tests/export_manually.rs
+++ b/ts-rs/tests/export_manually.rs
@@ -27,16 +27,12 @@ fn export_manually() {
     let expected_content = if cfg!(feature = "format") {
         concat!(
             "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n\n",
-            "export interface User {\n",
-            "  name: string;\n",
-            "  age: number;\n",
-            "  active: boolean;\n",
-            "}\n"
+            "export type User = { name: string; age: number; active: boolean };\n"
         )
     } else {
         concat!(
             "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n",
-            "\nexport interface User { name: string, age: number, active: boolean, }"
+            "\nexport type User = { name: string, age: number, active: boolean, }"
         )
     };
 
@@ -52,16 +48,12 @@ fn export_manually_dir() {
     let expected_content = if cfg!(feature = "format") {
         concat!(
             "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n\n",
-            "export interface UserDir {\n",
-            "  name: string;\n",
-            "  age: number;\n",
-            "  active: boolean;\n",
-            "}\n"
+            "export type UserDir = { name: string; age: number; active: boolean };\n"
         )
     } else {
         concat!(
             "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n",
-            "\nexport interface UserDir { name: string, age: number, active: boolean, }"
+            "\nexport type UserDir = { name: string, age: number, active: boolean, }"
         )
     };
 
diff --git a/ts-rs/tests/generics.rs b/ts-rs/tests/generics.rs
index 5dad985..cd8cd68 100644
--- a/ts-rs/tests/generics.rs
+++ b/ts-rs/tests/generics.rs
@@ -60,27 +60,27 @@ declare! {
 fn test() {
     assert_eq!(
         TypeGroup::decl(),
-        "interface TypeGroup { foo: Array<Container>, }",
+        "type TypeGroup = { foo: Array<Container>, }",
     );
 
     assert_eq!(
         Generic::<()>::decl(),
-        "interface Generic<T> { value: T, values: Array<T>, }"
+        "type Generic<T> = { value: T, values: Array<T>, }"
     );
 
     assert_eq!(
         GenericAutoBound::<()>::decl(),
-        "interface GenericAutoBound<T> { value: T, values: Array<T>, }"
+        "type GenericAutoBound<T> = { value: T, values: Array<T>, }"
     );
 
     assert_eq!(
         GenericAutoBound2::<()>::decl(),
-        "interface GenericAutoBound2<T> { value: T, values: Array<T>, }"
+        "type GenericAutoBound2<T> = { value: T, values: Array<T>, }"
     );
 
     assert_eq!(
         Container::decl(),
-        "interface Container { foo: Generic<number>, bar: Array<Generic<number>>, baz: Record<string, Generic<string>>, }"
+        "type Container = { foo: Generic<number>, bar: Array<Generic<number>>, baz: Record<string, Generic<string>>, }"
     );
 }
 
@@ -142,7 +142,7 @@ fn generic_struct() {
 
     assert_eq!(
         Struct::<()>::decl(),
-        "interface Struct<T> { a: T, b: [T, T], c: [T, [T, T]], d: Array<T>, e: Array<[T, T]>, f: Array<T>, g: Array<Array<T>>, h: Array<Array<[T, T]>>, }"
+        "type Struct<T> = { a: T, b: [T, T], c: [T, [T, T]], d: Array<T>, e: Array<[T, T]>, f: Array<T>, g: Array<Array<T>>, h: Array<Array<[T, T]>>, }"
     )
 }
 
@@ -164,10 +164,10 @@ fn inline() {
         t: Generic<String>,
     }
 
-    assert_eq!(Generic::<()>::decl(), "interface Generic<T> { t: T, }");
+    assert_eq!(Generic::<()>::decl(), "type Generic<T> = { t: T, }");
     assert_eq!(
         Container::decl(),
-        "interface Container { g: Generic<string>, gi: { t: string }, t: string, }"
+        "type Container = { g: Generic<string>, gi: { t: string }, t: string, }"
     );
 }
 
@@ -177,7 +177,7 @@ fn default() {
     struct A<T = String> {
         t: T,
     }
-    assert_eq!(A::<()>::decl(), "interface A<T = string> { t: T, }");
+    assert_eq!(A::<()>::decl(), "type A<T = string> = { t: T, }");
 
     #[derive(TS)]
     struct B<U = Option<A<i32>>> {
@@ -185,7 +185,7 @@ fn default() {
     }
     assert_eq!(
         B::<()>::decl(),
-        "interface B<U = A<number> | null> { u: U, }"
+        "type B<U = A<number> | null> = { u: U, }"
     );
     assert!(B::<()>::dependencies().iter().any(|dep| dep.ts_name == "A"));
 
@@ -200,7 +200,7 @@ fn default() {
         // #[ts(inline)]
         // xi2: X<i32>
     }
-    assert_eq!(Y::decl(), "interface Y { a1: A, a2: A<number>, }")
+    assert_eq!(Y::decl(), "type Y = { a1: A, a2: A<number>, }")
 }
 
 #[test]
@@ -209,7 +209,7 @@ fn trait_bounds() {
     struct A<T: ToString = i32> {
         t: T,
     }
-    assert_eq!(A::<i32>::decl(), "interface A<T = number> { t: T, }");
+    assert_eq!(A::<i32>::decl(), "type A<T = number> = { t: T, }");
 
     #[derive(TS)]
     struct B<T: ToString + Debug + Clone + 'static>(T);
@@ -232,5 +232,5 @@ fn trait_bounds() {
         t: [T; N],
     }
 
-    assert_eq!(D::<&str, 41>::decl(), "interface D<T> { t: Array<T>, }")
+    assert_eq!(D::<&str, 41>::decl(), "type D<T> = { t: Array<T>, }")
 }
diff --git a/ts-rs/tests/indexmap.rs b/ts-rs/tests/indexmap.rs
index 8b7b05e..7c8eb20 100644
--- a/ts-rs/tests/indexmap.rs
+++ b/ts-rs/tests/indexmap.rs
@@ -14,6 +14,6 @@ fn indexmap() {
 
     assert_eq!(
         Indexes::decl(),
-        "interface Indexes { map: Record<string, string>, set: Array<string>, }"
+        "type Indexes = { map: Record<string, string>, set: Array<string>, }"
     )
 }
diff --git a/ts-rs/tests/lifetimes.rs b/ts-rs/tests/lifetimes.rs
index 835610e..10abf37 100644
--- a/ts-rs/tests/lifetimes.rs
+++ b/ts-rs/tests/lifetimes.rs
@@ -8,7 +8,7 @@ fn contains_borrow() {
         s: &'a str,
     }
 
-    assert_eq!(S::decl(), "interface S { s: string, }")
+    assert_eq!(S::decl(), "type S = { s: string, }")
 }
 
 #[test]
@@ -29,6 +29,6 @@ fn contains_borrow_type_args() {
 
     assert_eq!(
         A::decl(),
-        "interface A { a: Array<number>, b: Array<B<number>>, c: Record<string, boolean>, }"
+        "type A = { a: Array<number>, b: Array<B<number>>, c: Record<string, boolean>, }"
     );
 }
diff --git a/ts-rs/tests/list.rs b/ts-rs/tests/list.rs
index 65aaa00..dfa3126 100644
--- a/ts-rs/tests/list.rs
+++ b/ts-rs/tests/list.rs
@@ -10,6 +10,6 @@ fn list() {
 
     assert_eq!(
         List::decl(),
-        "interface List { data: Array<number> | null, }"
+        "type List = { data: Array<number> | null, }"
     );
 }
diff --git a/ts-rs/tests/ranges.rs b/ts-rs/tests/ranges.rs
index ac99070..10cdcd6 100644
--- a/ts-rs/tests/ranges.rs
+++ b/ts-rs/tests/ranges.rs
@@ -19,7 +19,7 @@ struct RangeTest {
 fn range() {
     assert_eq!(
         RangeTest::decl(),
-        "interface RangeTest { a: { start: number, end: number, }, b: { start: string, end: string, }, c: { start: { start: number, end: number, }, end: { start: number, end: number, }, }, d: { start: number, end: number, }, e: { start: Inner, end: Inner, }, }"
+        "type RangeTest = { a: { start: number, end: number, }, b: { start: string, end: string, }, c: { start: { start: number, end: number, }, end: { start: number, end: number, }, }, d: { start: number, end: number, }, e: { start: Inner, end: Inner, }, }"
     );
     assert_eq!(
         RangeTest::dependencies(),
diff --git a/ts-rs/tests/raw_idents.rs b/ts-rs/tests/raw_idents.rs
index 51ee787..9fae879 100644
--- a/ts-rs/tests/raw_idents.rs
+++ b/ts-rs/tests/raw_idents.rs
@@ -15,6 +15,6 @@ fn raw_idents() {
     let out = <r#enum as TS>::decl();
     assert_eq!(
         out,
-        "interface enum { type: number, use: number, struct: number, let: number, enum: number, }"
+        "type enum = { type: number, use: number, struct: number, let: number, enum: number, }"
     );
 }
diff --git a/ts-rs/tests/serde-skip-with-default.rs b/ts-rs/tests/serde-skip-with-default.rs
index 6287f36..5f970af 100644
--- a/ts-rs/tests/serde-skip-with-default.rs
+++ b/ts-rs/tests/serde-skip-with-default.rs
@@ -22,6 +22,6 @@ pub struct Foobar {
 fn serde_skip_with_default() {
     assert_eq!(
         Foobar::decl(),
-        "interface Foobar { something_else: number, }"
+        "type Foobar = { something_else: number, }"
     );
 }
diff --git a/ts-rs/tests/union_with_data.rs b/ts-rs/tests/union_with_data.rs
index 2c69739..b35f368 100644
--- a/ts-rs/tests/union_with_data.rs
+++ b/ts-rs/tests/union_with_data.rs
@@ -24,10 +24,10 @@ enum SimpleEnum {
 
 #[test]
 fn test_stateful_enum() {
-    assert_eq!(Bar::decl(), r#"interface Bar { field: number, }"#);
+    assert_eq!(Bar::decl(), r#"type Bar = { field: number, }"#);
     assert_eq!(Bar::dependencies(), vec![]);
 
-    assert_eq!(Foo::decl(), r#"interface Foo { bar: Bar, }"#);
+    assert_eq!(Foo::decl(), r#"type Foo = { bar: Bar, }"#);
     assert_eq!(
         Foo::dependencies(),
         vec![Dependency::from_ty::<Bar>().unwrap()]
diff --git a/ts-rs/tests/unsized.rs b/ts-rs/tests/unsized.rs
index 61c0a26..bfddf75 100644
--- a/ts-rs/tests/unsized.rs
+++ b/ts-rs/tests/unsized.rs
@@ -15,6 +15,6 @@ fn contains_str() {
 
     assert_eq!(
         S::decl(),
-        "interface S { b: string, c: string, r: string, a: string, }"
+        "type S = { b: string, c: string, r: string, a: string, }"
     )
 }