Repository: Aleph-Alpha/ts-rs
Pull request #173 (2023-10-14 18:41:31)

Made the TS implementation for hashmap and hashset generic over all hashers
By default the hasher is [RandomState](https://doc.rust-lang.org/stable/std/collections/hash_map/struct.RandomState.html) but developers can override this. This PR makes ts-rs usable with e.g. https://docs.rs/rustc-hash/latest/rustc_hash/.




Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f1d62df..485516b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,8 +3,14 @@ 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, run
-`cargo readme > ../README.md` from the `ts-rs/` directory.  
+`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`
+
 You can install `cargo readme` by running `cargo install cargo-readme`.
 
 
@@ -16,4 +22,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`.
\ No newline at end of file
+To ensure proper formatting, please run `cargo +nightly fmt`.
diff --git a/README.md b/README.md
index ee26579..af2291e 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ generate typescript interface/type declarations from rust types
 
 <div align="center">
 <!-- Github Actions -->
-<img src="https://img.shields.io/github/workflow/status/Aleph-Alpha/ts-rs/Test?style=flat-square" alt="actions status" />
+<img src="https://img.shields.io/github/actions/workflow/status/Aleph-Alpha/ts-rs/test.yml?branch=main" alt="actions status" />
 <a href="https://crates.io/crates/ts-rs">
 <img src="https://img.shields.io/crates/v/ts-rs.svg?style=flat-square"
 alt="Crates.io version" />
@@ -42,7 +42,7 @@ We recommend doing this in your tests.
 ### get started
 ```toml
 [dependencies]
-ts-rs = "7.0"
+ts-rs = "7.1"
 ```
 
 ```rust
@@ -66,6 +66,7 @@ When running `cargo test`, the TypeScript bindings will be exported to the file
 - generate necessary imports when exporting to multiple files
 - serde compatibility
 - generic types
+- support for ESM imports
 
 ### limitations
 - generic fields cannot be inlined or flattened (#56)
@@ -109,6 +110,15 @@ When running `cargo test`, the TypeScript bindings will be exported to the file
 
   Implement `TS` for `Vec` from heapless
 
+- `no-serde-warnings`
+
+  When `serde-compat` is enabled, warnings are printed during build if unsupported serde
+  attributes are encountered. Enabling this feature silences these warnings.
+
+- `import-esm`
+
+  `import` statements in the generated file will have the `.js` extension in the end of
+  the path to conform to the ES Modules spec. (e.g.: `import { MyStruct } from "./my_struct.js"`)
 
 If there's a type you're dealing with which doesn't implement `TS`, use `#[ts(type = "..")]` or open a PR.
 
@@ -127,7 +137,7 @@ Supported serde attributes:
 - `flatten`
 - `default`
 
-When ts-rs encounters an unsupported serde attribute, a warning is emitted.
+When ts-rs encounters an unsupported serde attribute, a warning is emitted, unless the feature `no-serde-warnings` is enabled.
 
 ### contributing
 Contributions are always welcome!
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index 8517120..9897b67 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "ts-rs-macros"
-version = "7.0.0"
+version = "7.1.1"
 authors = ["Moritz Bischof <moritz.bischof1@gmail.com>"]
 edition = "2021"
 description = "derive macro for ts-rs"
@@ -10,6 +10,7 @@ repository = "https://github.com/Aleph-Alpha/ts-rs"
 
 [features]
 serde-compat = ["termcolor"]
+no-serde-warnings = []
 
 [lib]
 proc-macro = true
diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs
index 13daef5..0c91b1c 100644
--- a/macros/src/attr/field.rs
+++ b/macros/src/attr/field.rs
@@ -22,7 +22,10 @@ impl FieldAttr {
         let mut result = Self::default();
         parse_attrs(attrs)?.for_each(|a| result.merge(a));
         #[cfg(feature = "serde-compat")]
-        crate::utils::parse_serde_attrs::<SerdeFieldAttr>(attrs).for_each(|a| result.merge(a.0));
+        if !result.skip {
+            crate::utils::parse_serde_attrs::<SerdeFieldAttr>(attrs)
+                .for_each(|a| result.merge(a.0));
+        }
         Ok(result)
     }
 
diff --git a/macros/src/attr/mod.rs b/macros/src/attr/mod.rs
index dc566ff..6bb7b34 100644
--- a/macros/src/attr/mod.rs
+++ b/macros/src/attr/mod.rs
@@ -32,9 +32,29 @@ impl Inflection {
         match self {
             Inflection::Lower => string.to_lowercase(),
             Inflection::Upper => string.to_uppercase(),
-            Inflection::Camel => string.to_camel_case(),
+            Inflection::Camel => {
+                let pascal = Inflection::apply(Inflection::Pascal, string);
+                pascal[..1].to_ascii_lowercase() + &pascal[1..]
+            }
             Inflection::Snake => string.to_snake_case(),
-            Inflection::Pascal => string.to_pascal_case(),
+            Inflection::Pascal => {
+                let mut s = String::with_capacity(string.len());
+
+                let mut capitalize = true;
+                for c in string.chars() {
+                    if c == '_' {
+                        capitalize = true;
+                        continue;
+                    } else if capitalize {
+                        s.push(c.to_ascii_uppercase());
+                        capitalize = false;
+                    } else {
+                        s.push(c)
+                    }
+                }
+
+                s
+            }
             Inflection::ScreamingSnake => string.to_screaming_snake_case(),
             Inflection::Kebab => string.to_kebab_case(),
         }
diff --git a/macros/src/attr/variant.rs b/macros/src/attr/variant.rs
index 0f67f1e..1f490db 100644
--- a/macros/src/attr/variant.rs
+++ b/macros/src/attr/variant.rs
@@ -22,7 +22,10 @@ impl VariantAttr {
         let mut result = Self::default();
         parse_attrs(attrs)?.for_each(|a| result.merge(a));
         #[cfg(feature = "serde-compat")]
-        crate::utils::parse_serde_attrs::<SerdeVariantAttr>(attrs).for_each(|a| result.merge(a.0));
+        if !result.skip {
+            crate::utils::parse_serde_attrs::<SerdeVariantAttr>(attrs)
+                .for_each(|a| result.merge(a.0));
+        }
         Ok(result)
     }
 
diff --git a/macros/src/types/enum.rs b/macros/src/types/enum.rs
index 043379a..4c73b75 100644
--- a/macros/src/types/enum.rs
+++ b/macros/src/types/enum.rs
@@ -91,18 +91,33 @@ fn format_variant(
         Tagged::Untagged => quote!(#inline_type),
         Tagged::Externally => match &variant.fields {
             Fields::Unit => quote!(format!("\"{}\"", #name)),
+            Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
+                let FieldAttr { skip, .. } = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;
+                if skip {
+                    quote!(format!("\"{}\"", #name))
+                } else {
+                    quote!(format!("{{ \"{}\": {} }}", #name, #inline_type))
+                }
+            }
             _ => quote!(format!("{{ \"{}\": {} }}", #name, #inline_type)),
         },
         Tagged::Adjacently { tag, content } => match &variant.fields {
             Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
-                let FieldAttr { type_override, .. } =
-                    FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;
-                let ty = if let Some(type_override) = type_override {
-                    quote! { #type_override }
+                let FieldAttr {
+                    type_override,
+                    skip,
+                    ..
+                } = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;
+                if skip {
+                    quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
                 } else {
-                    format_type(&unnamed.unnamed[0].ty, dependencies, generics)
-                };
-                quote!(format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #name, #content, #ty))
+                    let ty = if let Some(type_override) = type_override {
+                        quote! { #type_override }
+                    } else {
+                        format_type(&unnamed.unnamed[0].ty, dependencies, generics)
+                    };
+                    quote!(format!("{{ \"{}\": \"{}\", \"{}\": {} }}", #tag, #name, #content, #ty))
+                }
             }
             Fields::Unit => quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name)),
             _ => quote!(
@@ -120,14 +135,21 @@ fn format_variant(
             },
             None => match &variant.fields {
                 Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
-                    let FieldAttr { type_override, .. } =
-                        FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;
-                    let ty = if let Some(type_override) = type_override {
-                        quote! { #type_override }
+                    let FieldAttr {
+                        type_override,
+                        skip,
+                        ..
+                    } = FieldAttr::from_attrs(&unnamed.unnamed[0].attrs)?;
+                    if skip {
+                        quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name))
                     } else {
-                        format_type(&unnamed.unnamed[0].ty, dependencies, generics)
-                    };
-                    quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #name, #ty))
+                        let ty = if let Some(type_override) = type_override {
+                            quote! { #type_override }
+                        } else {
+                            format_type(&unnamed.unnamed[0].ty, dependencies, generics)
+                        };
+                        quote!(format!("{{ \"{}\": \"{}\" }} & {}", #tag, #name, #ty))
+                    }
                 }
                 Fields::Unit => quote!(format!("{{ \"{}\": \"{}\" }}", #tag, #name)),
                 _ => {
diff --git a/macros/src/types/generics.rs b/macros/src/types/generics.rs
index 290659e..4766de9 100644
--- a/macros/src/types/generics.rs
+++ b/macros/src/types/generics.rs
@@ -1,7 +1,8 @@
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
 use syn::{
-    GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeGroup, TypeTuple,
+    GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeArray, TypeGroup,
+    TypeReference, TypeSlice, TypeTuple,
 };
 
 use crate::{attr::StructAttr, deps::Dependencies};
@@ -61,10 +62,10 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi
 
     // special treatment for arrays and tuples
     match ty {
-        // the field is an array (`[T; n]`) so it technically doesn't have a generic argument.
-        // therefore, we handle it explicitly here like a `Vec<T>`
-        Type::Array(array) => {
-            let inner_ty = &array.elem;
+        // The field is an array (`[T; n]`) or a slice (`[T]`) so it technically doesn't have a
+        // generic argument. Therefore, we handle it explicitly here like a `Vec<T>`
+        Type::Array(TypeArray { ref elem, .. }) | Type::Slice(TypeSlice { ref elem, .. }) => {
+            let inner_ty = elem;
             let vec_ty = syn::parse2::<Type>(quote!(Vec::<#inner_ty>)).unwrap();
             return format_type(&vec_ty, dependencies, generics);
         }
@@ -90,6 +91,9 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi
             dependencies.append(tuple_struct.dependencies);
             return tuple_struct.inline;
         }
+        Type::Reference(syn::TypeReference { ref elem, .. }) => {
+            return format_type(elem, dependencies, generics)
+        }
         _ => (),
     };
 
@@ -109,7 +113,9 @@ pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generi
 
 fn extract_type_args(ty: &Type) -> Option<Vec<&Type>> {
     let last_segment = match ty {
-        Type::Group(TypeGroup { elem, .. }) => return extract_type_args(elem),
+        Type::Group(TypeGroup { elem, .. }) | Type::Reference(TypeReference { elem, .. }) => {
+            return extract_type_args(elem)
+        }
         Type::Path(type_path) => type_path.path.segments.last(),
         _ => None,
     }?;
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,
diff --git a/macros/src/types/newtype.rs b/macros/src/types/newtype.rs
index b5070d7..30b0775 100644
--- a/macros/src/types/newtype.rs
+++ b/macros/src/types/newtype.rs
@@ -32,7 +32,7 @@ pub(crate) fn newtype(
 
     match (&rename_inner, skip, optional, flatten) {
         (Some(_), ..) => syn_err!("`rename` is not applicable to newtype fields"),
-        (_, true, ..) => syn_err!("`skip` is not applicable to newtype fields"),
+        (_, true, ..) => return super::unit::null(attr, name),
         (_, _, true, ..) => syn_err!("`optional` is not applicable to newtype fields"),
         (_, _, _, true) => syn_err!("`flatten` is not applicable to newtype fields"),
         _ => {}
diff --git a/macros/src/utils.rs b/macros/src/utils.rs
index 3b6f1fe..ad7f063 100644
--- a/macros/src/utils.rs
+++ b/macros/src/utils.rs
@@ -99,7 +99,10 @@ pub fn parse_serde_attrs<'a, A: TryFrom<&'a Attribute, Error = Error>>(
         .flat_map(|attr| match A::try_from(attr) {
             Ok(attr) => Some(attr),
             Err(_) => {
+                #[cfg(not(feature = "no-serde-warnings"))]
                 use quote::ToTokens;
+
+                #[cfg(not(feature = "no-serde-warnings"))]
                 warning::print_warning(
                     "failed to parse serde attribute",
                     format!("{}", attr.to_token_stream()),
@@ -121,6 +124,7 @@ mod warning {
 
     // Sadly, it is impossible to raise a warning in a proc macro.
     // This function prints a message which looks like a compiler warning.
+    #[allow(unused)]
     pub fn print_warning(
         title: impl Display,
         content: impl Display,
diff --git a/ts-rs/Cargo.toml b/ts-rs/Cargo.toml
index 0457407..58ff302 100644
--- a/ts-rs/Cargo.toml
+++ b/ts-rs/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "ts-rs"
-version = "7.0.0"
+version = "7.1.1"
 authors = ["Moritz Bischof <moritz.bischof1@gmail.com>"]
 edition = "2021"
 license = "MIT"
@@ -8,7 +8,12 @@ description = "generate typescript bindings from rust types"
 homepage = "https://github.com/Aleph-Alpha/ts-rs"
 repository = "https://github.com/Aleph-Alpha/ts-rs"
 keywords = ["typescript", "ts", "bindings", "ts-rs", "wasm"]
-categories = ["development-tools::ffi", "development-tools::build-utils", "wasm", "web-programming"]
+categories = [
+  "development-tools::ffi",
+  "development-tools::build-utils",
+  "wasm",
+  "web-programming",
+]
 readme = "../README.md"
 
 [features]
@@ -24,6 +29,8 @@ default = ["serde-compat"]
 indexmap-impl = ["indexmap"]
 ordered-float-impl = ["ordered-float"]
 heapless-impl = ["heapless"]
+no-serde-warnings = ["ts-rs-macros/no-serde-warnings"]
+import-esm = []
 
 [dev-dependencies]
 serde = { version = "1.0", features = ["derive"] }
@@ -31,10 +38,12 @@ chrono = { version = "0.4", features = ["serde"] }
 
 [dependencies]
 heapless = { version = "0.7", optional = true }
-ts-rs-macros = { version = "7.0.0", path = "../macros" }
+ts-rs-macros = { version = "7.1.1", path = "../macros" }
 dprint-plugin-typescript = { version = "0.85.1", optional = true }
 chrono = { version = "0.4", optional = true }
-bigdecimal = { version = ">=0.0.13, < 0.4.0", features = ["serde"], optional = true }
+bigdecimal = { version = ">=0.0.13, < 0.4.0", features = [
+  "serde",
+], optional = true }
 uuid = { version = "1.1.2", optional = true }
 bson = { version = "2.2.0", optional = true }
 bytes = { version = "1.0", optional = true }
diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs
index c4b9345..fdaf851 100644
--- a/ts-rs/src/export.rs
+++ b/ts-rs/src/export.rs
@@ -110,12 +110,18 @@ fn generate_imports<T: TS + ?Sized + 'static>(out: &mut String) -> Result<(), Ex
 fn import_path(from: &Path, import: &Path) -> String {
     let rel_path =
         diff_paths(import, from.parent().unwrap()).expect("failed to calculate import path");
-    match rel_path.components().next() {
+    let path = match rel_path.components().next() {
         Some(Component::Normal(_)) => format!("./{}", rel_path.to_string_lossy()),
         _ => rel_path.to_string_lossy().into(),
+    };
+
+    let path_without_extension = path.trim_end_matches(".ts");
+
+    if cfg!(feature = "import-esm") {
+        format!("{}.js", path_without_extension)
+    } else {
+        path_without_extension.to_owned()
     }
-    .trim_end_matches(".ts")
-    .to_owned()
 }
 
 // Construct a relative path from a provided base directory path to the provided path.
diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs
index a624cf6..464e44c 100644
--- a/ts-rs/src/lib.rs
+++ b/ts-rs/src/lib.rs
@@ -9,7 +9,7 @@
 //!
 //! <div align="center">
 //! <!-- Github Actions -->
-//! <img src="https://img.shields.io/github/workflow/status/Aleph-Alpha/ts-rs/Test?style=flat-square" alt="actions status" />
+//! <img src="https://img.shields.io/github/actions/workflow/status/Aleph-Alpha/ts-rs/test.yml?branch=main" alt="actions status" />
 //! <a href="https://crates.io/crates/ts-rs">
 //! <img src="https://img.shields.io/crates/v/ts-rs.svg?style=flat-square"
 //! alt="Crates.io version" />
@@ -40,7 +40,7 @@
 //! ## get started
 //! ```toml
 //! [dependencies]
-//! ts-rs = "7.0"
+//! ts-rs = "7.1"
 //! ```
 //!
 //! ```rust
@@ -64,6 +64,7 @@
 //! - generate necessary imports when exporting to multiple files
 //! - serde compatibility
 //! - generic types
+//! - support for ESM imports
 //!
 //! ## limitations
 //! - generic fields cannot be inlined or flattened (#56)
@@ -104,9 +105,18 @@
 //!   Implement `TS` for `OrderedFloat` from ordered_float
 //!
 //! - `heapless-impl`  
-//! 
+//!
 //!   Implement `TS` for `Vec` from heapless
 //!
+//! - `no-serde-warnings`
+//!
+//!   When `serde-compat` is enabled, warnings are printed during build if unsupported serde
+//!   attributes are encountered. Enabling this feature silences these warnings.
+//!
+//! - `import-esm`
+//!
+//!   `import` statements in the generated file will have the `.js` extension in the end of
+//!   the path to conform to the ES Modules spec. (e.g.: `import { MyStruct } from "./my_struct.js"`)
 //!
 //! If there's a type you're dealing with which doesn't implement `TS`, use `#[ts(type = "..")]` or open a PR.
 //!
@@ -125,7 +135,7 @@
 //! - `flatten`
 //! - `default`
 //!
-//! When ts-rs encounters an unsupported serde attribute, a warning is emitted.
+//! When ts-rs encounters an unsupported serde attribute, a warning is emitted, unless the feature `no-serde-warnings` is enabled.
 //!
 //! ## contributing
 //! Contributions are always welcome!
@@ -459,6 +469,27 @@ impl<T: TS> TS for Option<T> {
     }
 }
 
+impl<T: TS, E: TS> TS for Result<T, E> {
+    fn name() -> String {
+        unreachable!();
+    }
+    fn inline() -> String {
+        format!("{{ Ok : {} }} | {{ Err : {} }}", T::inline(), E::inline())
+    }
+    fn dependencies() -> Vec<Dependency>
+    where
+        Self: 'static,
+    {
+        [Dependency::from_ty::<T>(), Dependency::from_ty::<E>()]
+            .into_iter()
+            .flatten()
+            .collect()
+    }
+    fn transparent() -> bool {
+        true
+    }
+}
+
 impl<T: TS> TS for Vec<T> {
     fn name() -> String {
         "Array".to_owned()
@@ -490,7 +521,7 @@ impl<T: TS> TS for Vec<T> {
     }
 }
 
-impl<K: TS, V: TS> TS for HashMap<K, V> {
+impl<K: TS, V: TS, H> TS for HashMap<K, V, H> {
     fn name() -> String {
         "Record".to_owned()
     }
@@ -579,10 +610,11 @@ impl<I: TS> TS for RangeInclusive<I> {
 }
 
 impl_shadow!(as T: impl<'a, T: TS + ?Sized> TS for &T);
-impl_shadow!(as Vec<T>: impl<T: TS> TS for HashSet<T>);
+impl_shadow!(as Vec<T>: impl<T: TS, H> TS for HashSet<T, H>);
 impl_shadow!(as Vec<T>: impl<T: TS> TS for BTreeSet<T>);
 impl_shadow!(as HashMap<K, V>: impl<K: TS, V: TS> TS for BTreeMap<K, V>);
 impl_shadow!(as Vec<T>: impl<T: TS, const N: usize> TS for [T; N]);
+impl_shadow!(as Vec<T>: impl<T: TS> TS for [T]);
 
 impl_wrapper!(impl<T: TS + ?Sized> TS for Box<T>);
 impl_wrapper!(impl<T: TS + ?Sized> TS for std::sync::Arc<T>);


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d980a12..3a2daab 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,6 +1,20 @@
 on: [push, pull_request]
 name: Test
 jobs:
+  readme-up-to-date:
+    name: Check that README.md is up-to-date
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+      - name: Check that README.md is up-to-date
+        working-directory: ts-rs
+        run: |
+          cargo install cargo-readme 
+          diff -u ../README.md <(cargo readme)
+
   test:
     name: Test ts-rs
     runs-on: ubuntu-latest
@@ -16,4 +30,4 @@ jobs:
       - uses: actions-rs/cargo@v1
         with:
           command: test
-          args: --all-features --manifest-path ts-rs/Cargo.toml
+          args: --all-features --manifest-path ts-rs/Cargo.toml
\ No newline at end of file
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/enum_struct_rename_all.rs b/ts-rs/tests/enum_struct_rename_all.rs
new file mode 100644
index 0000000..b901a16
--- /dev/null
+++ b/ts-rs/tests/enum_struct_rename_all.rs
@@ -0,0 +1,25 @@
+use serde::{Serialize, Deserialize};
+use ts_rs::TS;
+
+#[derive(Serialize, Deserialize, TS, Clone)]
+#[ts(export)]
+#[cfg_attr(feature = "serde-compat", serde(rename_all = "camelCase"))]
+#[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "camelCase"))]
+pub enum TaskStatus {
+    #[cfg_attr(feature = "serde-compat", serde(rename_all = "camelCase"))]
+    #[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "camelCase"))]
+    Running { started_time: String },
+
+    #[cfg_attr(feature = "serde-compat", serde(rename_all = "camelCase"))]
+    #[cfg_attr(not(feature = "serde-compat"), ts(rename_all = "camelCase"))]
+    Terminated {
+        status: i32,
+        stdout: String,
+        stderr: String,
+    },
+}
+
+#[test]
+pub fn enum_struct_rename_all() {
+    assert_eq!(TaskStatus::inline(), r#"{ "running": { startedTime: string, } } | { "terminated": { status: number, stdout: string, stderr: 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 c7ce9b6..cd8cd68 100644
--- a/ts-rs/tests/generics.rs
+++ b/ts-rs/tests/generics.rs
@@ -1,3 +1,4 @@
+#![allow(clippy::box_collection)]
 #![allow(dead_code)]
 
 use std::{
@@ -59,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>>, }"
     );
 }
 
@@ -141,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]>>, }"
     )
 }
 
@@ -163,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, }"
     );
 }
 
@@ -176,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>>> {
@@ -184,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"));
 
@@ -199,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]
@@ -208,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);
@@ -231,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/hashmap.rs b/ts-rs/tests/hashmap.rs
new file mode 100644
index 0000000..a588e9c
--- /dev/null
+++ b/ts-rs/tests/hashmap.rs
@@ -0,0 +1,37 @@
+use std::collections::{HashMap, HashSet};
+use ts_rs::TS;
+
+#[test]
+fn hashmap() {
+    #[derive(TS)]
+    #[allow(dead_code)]
+    struct Hashes {
+        map: HashMap<String, String>,
+        set: HashSet<String>,
+    }
+
+    assert_eq!(
+        Hashes::decl(),
+        "type Hashes = { map: Record<string, string>, set: Array<string>, }"
+    )
+}
+
+#[test]
+fn hashmap_with_custom_hasher() {
+    struct CustomHasher {}
+
+    type CustomHashMap<K, V> = HashMap<K, V, CustomHasher>;
+    type CustomHashSet<K> = HashSet<K, CustomHasher>;
+
+    #[derive(TS)]
+    #[allow(dead_code)]
+    struct Hashes {
+        map: CustomHashMap<String, String>,
+        set: CustomHashSet<String>,
+    }
+
+    assert_eq!(
+        Hashes::decl(),
+        "type Hashes = { map: Record<string, string>, set: Array<string>, }"
+    )
+}
diff --git a/ts-rs/tests/imports.rs b/ts-rs/tests/imports.rs
index 7674e09..bf1688b 100644
--- a/ts-rs/tests/imports.rs
+++ b/ts-rs/tests/imports.rs
@@ -31,6 +31,19 @@ fn test_def() {
     let text = std::fs::read_to_string(TestEnum::EXPORT_TO.unwrap()).unwrap();
 
     // Checks to make sure imports are ordered and deduplicated
+    #[cfg(feature = "import-esm")]
+    assert_eq!(text,
+        concat!(
+            "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n",
+            "import type { TestTypeA } from \"./ts_rs_test_type_a.js\";\n",
+            "import type { TestTypeB } from \"./ts_rs_test_type_b.js\";\n",
+            "\n",
+            "export type TestEnum = { \"C\": { value: TestTypeB<number> } } | {\n",
+            "  \"A1\": { value: TestTypeA<number> };\n",
+            "} | { \"A2\": { value: TestTypeA<number> } };\n"
+        )
+    );
+    #[cfg(not(feature = "import-esm"))]
     assert_eq!(text,
         concat!(
             "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n",
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/issue-80.rs b/ts-rs/tests/issue-80.rs
index 8b0ce4b..20c4b07 100644
--- a/ts-rs/tests/issue-80.rs
+++ b/ts-rs/tests/issue-80.rs
@@ -1,5 +1,5 @@
-use ts_rs::TS;
 use serde::Serialize;
+use ts_rs::TS;
 
 #[derive(TS, Serialize)]
 #[ts(export)]
@@ -9,11 +9,11 @@ pub enum SomeTypeList {
         #[ts(skip)]
         skip_this: String,
     },
-    Value2
+    Value2,
 }
 
 #[test]
 fn issue_80() {
     let ty = SomeTypeList::inline();
     assert_eq!(ty, r#"{ "Value1": {  } } | "Value2""#);
-}
\ No newline at end of file
+}
diff --git a/ts-rs/tests/lifetimes.rs b/ts-rs/tests/lifetimes.rs
index 75bdb64..10abf37 100644
--- a/ts-rs/tests/lifetimes.rs
+++ b/ts-rs/tests/lifetimes.rs
@@ -8,5 +8,27 @@ fn contains_borrow() {
         s: &'a str,
     }
 
-    assert_eq!(S::decl(), "interface S { s: string, }")
+    assert_eq!(S::decl(), "type S = { s: string, }")
+}
+
+#[test]
+fn contains_borrow_type_args() {
+    #[derive(TS)]
+    #[allow(dead_code)]
+    struct B<'a, T: 'a> {
+        a: &'a T,
+    }
+
+    #[derive(TS)]
+    #[allow(dead_code)]
+    struct A<'a> {
+        a: &'a &'a &'a Vec<u32>,                        //Multiple References
+        b: &'a Vec<B<'a, u32>>,                         //Nesting
+        c: &'a std::collections::HashMap<String, bool>, //Multiple type args
+    }
+
+    assert_eq!(
+        A::decl(),
+        "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/references.rs b/ts-rs/tests/references.rs
new file mode 100644
index 0000000..1aee02b
--- /dev/null
+++ b/ts-rs/tests/references.rs
@@ -0,0 +1,15 @@
+use ts_rs::TS;
+
+#[test]
+fn references() {
+    #[derive(TS)]
+    #[allow(dead_code)]
+    struct FullOfRefs<'a> {
+        str_slice: &'a str,
+        ref_slice: &'a [&'a str],
+        num_ref: &'a i32,
+    }
+
+    assert_eq!(FullOfRefs::inline(), "{ str_slice: string, ref_slice: Array<string>, num_ref: number, }")
+}
+
diff --git a/ts-rs/tests/serde-skip-with-default.rs b/ts-rs/tests/serde-skip-with-default.rs
new file mode 100644
index 0000000..5f970af
--- /dev/null
+++ b/ts-rs/tests/serde-skip-with-default.rs
@@ -0,0 +1,27 @@
+#![allow(dead_code)]
+
+// from issue #107. This does now no longer generate a warning.
+
+use serde::{Deserialize, Serialize};
+use ts_rs::TS;
+
+fn default_http_version() -> String {
+    "2".to_owned()
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize, TS)]
+#[ts(export)]
+pub struct Foobar {
+    #[ts(skip)]
+    #[serde(skip, default = "default_http_version")]
+    pub http_version: String,
+    pub something_else: i32,
+}
+
+#[test]
+fn serde_skip_with_default() {
+    assert_eq!(
+        Foobar::decl(),
+        "type Foobar = { something_else: number, }"
+    );
+}
diff --git a/ts-rs/tests/slices.rs b/ts-rs/tests/slices.rs
new file mode 100644
index 0000000..268a090
--- /dev/null
+++ b/ts-rs/tests/slices.rs
@@ -0,0 +1,63 @@
+use ts_rs::TS;
+
+#[test]
+fn free() {
+    assert_eq!(<[String]>::inline(), "Array<string>")
+}
+
+#[test]
+fn interface() {
+    #[derive(TS)]
+    struct Interface {
+        #[allow(dead_code)]
+        a: [i32],
+    }
+
+    assert_eq!(Interface::inline(), "{ a: Array<number>, }")
+}
+
+#[test]
+fn slice_ref() {
+    #[derive(TS)]
+    struct Interface<'a> {
+        #[allow(dead_code)]
+        a: &'a [&'a str]
+    }
+
+    assert_eq!(Interface::inline(), "{ a: Array<string>, }")
+}
+
+#[test]
+fn newtype() {
+    #[derive(TS)]
+    struct Newtype(#[allow(dead_code)] [i32]);
+
+    assert_eq!(Newtype::inline(), "Array<number>")
+}
+
+// Since slices usually need to be wrapped in a `Box` or other container,
+// these tests should to check for that
+
+#[test]
+fn boxed_free() {
+    assert_eq!(<Box<[String]>>::inline(), "Array<string>")
+}
+
+#[test]
+fn boxed_interface() {
+    #[derive(TS)]
+    struct Interface {
+        #[allow(dead_code)]
+        a: Box<[i32]>,
+    }
+
+    assert_eq!(Interface::inline(), "{ a: Array<number>, }")
+}
+
+#[test]
+fn boxed_newtype() {
+    #[derive(TS)]
+    struct Newtype(#[allow(dead_code)] Box<[i32]>);
+
+    assert_eq!(Newtype::inline(), "Array<number>")
+}
diff --git a/ts-rs/tests/struct_rename.rs b/ts-rs/tests/struct_rename.rs
index 12039d0..aa7d78d 100644
--- a/ts-rs/tests/struct_rename.rs
+++ b/ts-rs/tests/struct_rename.rs
@@ -14,6 +14,32 @@ fn rename_all() {
     assert_eq!(Rename::inline(), "{ A: number, B: number, }");
 }
 
+#[test]
+fn rename_all_camel_case() {
+    #[derive(TS)]
+    #[ts(rename_all = "camelCase")]
+    #[allow(non_snake_case)]
+    struct Rename {
+        crc32c_hash: i32,
+        b: i32,
+        alreadyCamelCase: i32,
+    }
+
+    assert_eq!(Rename::inline(), "{ crc32cHash: number, b: number, alreadyCamelCase: number, }");
+}
+
+#[test]
+fn rename_all_pascal_case() {
+    #[derive(TS)]
+    #[ts(rename_all = "PascalCase")]
+    struct Rename {
+        crc32c_hash: i32,
+        b: i32,
+    }
+
+    assert_eq!(Rename::inline(), "{ Crc32cHash: number, B: number, }");
+}
+
 #[cfg(feature = "serde-compat")]
 #[test]
 fn serde_rename_special_char() {
diff --git a/ts-rs/tests/union_named_serde_skip.rs b/ts-rs/tests/union_named_serde_skip.rs
new file mode 100644
index 0000000..804e058
--- /dev/null
+++ b/ts-rs/tests/union_named_serde_skip.rs
@@ -0,0 +1,71 @@
+#![allow(dead_code)]
+
+use serde::Deserialize;
+use ts_rs::TS;
+
+#[derive(TS, Deserialize)]
+#[serde(untagged)]
+enum TestUntagged {
+    A,   // serde_json -> `null`
+    B(), // serde_json -> `[]`
+    C {
+        #[serde(skip)]
+        val: i32,
+    }, // serde_json -> `{}`
+}
+
+#[derive(TS, Deserialize)]
+enum TestExternally {
+    A,   // serde_json -> `"A"`
+    B(), // serde_json -> `{"B":[]}`
+    C {
+        #[serde(skip)]
+        val: i32,
+    }, // serde_json -> `{"C":{}}`
+}
+
+#[derive(TS, Deserialize)]
+#[serde(tag = "type", content = "content")]
+enum TestAdjacently {
+    A,   // serde_json -> `{"type":"A"}`
+    B(), // serde_json -> `{"type":"B","content":[]}`
+    C {
+        #[serde(skip)]
+        val: i32,
+    }, // serde_json -> `{"type":"C","content":{}}`
+}
+
+#[derive(TS, Deserialize)]
+#[serde(tag = "type")]
+enum TestInternally {
+    A, // serde_json -> `{"type":"A"}`
+    B, // serde_json -> `{"type":"B"}`
+    C {
+        #[serde(skip)]
+        val: i32,
+    }, // serde_json -> `{"type":"C"}`
+}
+
+#[cfg(feature = "serde-compat")]
+#[test]
+fn test() {
+    assert_eq!(
+        TestUntagged::decl(),
+        r#"type TestUntagged = null | never[] | {  };"#
+    );
+
+    assert_eq!(
+        TestExternally::decl(),
+        r#"type TestExternally = "A" | { "B": never[] } | { "C": {  } };"#
+    );
+
+    assert_eq!(
+        TestAdjacently::decl(),
+        r#"type TestAdjacently = { "type": "A" } | { "type": "B", "content": never[] } | { "type": "C", "content": {  } };"#
+    );
+
+    assert_eq!(
+        TestInternally::decl(),
+        r#"type TestInternally = { "type": "A" } | { "type": "B" } | { "type": "C",  };"#
+    );
+}
diff --git a/ts-rs/tests/union_unnamed_serde_skip.rs b/ts-rs/tests/union_unnamed_serde_skip.rs
new file mode 100644
index 0000000..1dfae1c
--- /dev/null
+++ b/ts-rs/tests/union_unnamed_serde_skip.rs
@@ -0,0 +1,59 @@
+#![allow(dead_code)]
+
+use serde::Deserialize;
+use ts_rs::TS;
+
+#[derive(TS, Deserialize)]
+#[serde(untagged)]
+enum TestUntagged {
+    A,                     // serde_json -> `null`
+    B(),                   // serde_json -> `[]`
+    C(#[serde(skip)] i32), // serde_json -> `null`
+}
+
+#[derive(TS, Deserialize)]
+enum TestExternally {
+    A,                     // serde_json -> `"A"`
+    B(),                   // serde_json -> `{"B":[]}`
+    C(#[serde(skip)] i32), // serde_json -> `"C"`
+}
+
+#[derive(TS, Deserialize)]
+#[serde(tag = "type", content = "content")]
+enum TestAdjacently {
+    A,                     // serde_json -> `{"type":"A"}`
+    B(),                   // serde_json -> `{"type":"B","content":[]}`
+    C(#[serde(skip)] i32), // serde_json -> `{"type":"C"}`
+}
+
+#[derive(TS, Deserialize)]
+#[serde(tag = "type")]
+enum TestInternally {
+    A,                     // serde_json -> `{"type":"A"}`
+    B,                     // serde_json -> `{"type":"B"}`
+    C(#[serde(skip)] i32), // serde_json -> `{"type":"C"}`
+}
+
+#[cfg(feature = "serde-compat")]
+#[test]
+fn test() {
+    assert_eq!(
+        TestUntagged::decl(),
+        r#"type TestUntagged = null | never[] | null;"#
+    );
+
+    assert_eq!(
+        TestExternally::decl(),
+        r#"type TestExternally = "A" | { "B": never[] } | "C";"#
+    );
+
+    assert_eq!(
+        TestAdjacently::decl(),
+        r#"type TestAdjacently = { "type": "A" } | { "type": "B", "content": never[] } | { "type": "C" };"#
+    );
+
+    assert_eq!(
+        TestInternally::decl(),
+        r#"type TestInternally = { "type": "A" } | { "type": "B" } | { "type": "C" };"#
+    );
+}
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/union_with_internal_tag.rs b/ts-rs/tests/union_with_internal_tag.rs
index e0fc0b7..e7382ee 100644
--- a/ts-rs/tests/union_with_internal_tag.rs
+++ b/ts-rs/tests/union_with_internal_tag.rs
@@ -1,4 +1,4 @@
-#![allow(dead_code, clippy::blacklisted_name)]
+#![allow(dead_code, clippy::disallowed_names)]
 
 use serde::Serialize;
 use ts_rs::TS;
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, }"
     )
 }