From 30ba9d104ccf64fd7a425de2ec231da0a2510b00 Mon Sep 17 00:00:00 2001
From: eythaann <eythan.cvt@gmail.com>
Date: Tue, 17 Dec 2024 16:48:09 -0500
Subject: [PATCH 1/3] feat: add import-deno as crate feature to import with .ts
 extension

---
 README.md                            |  5 ++-
 ts-rs/Cargo.toml                     |  1 +
 ts-rs/src/export.rs                  |  4 ++
 ts-rs/src/lib.rs                     |  5 ++-
 ts-rs/tests/integration/imports.rs   | 56 ++++++++++++++--------------
 ts-rs/tests/integration/issue_168.rs |  2 +-
 ts-rs/tests/integration/issue_232.rs |  2 +-
 7 files changed, 42 insertions(+), 33 deletions(-)

diff --git a/README.md b/README.md
index 1b931f0f..4a1401a8 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ export type User = { user_id: number, first_name: string, last_name: string, };
 | format             | Enables formatting of the generated TypeScript bindings. <br/>Currently, this unfortunately adds quite a few dependencies.                                                                                |
 | no-serde-warnings  | By default, warnings are printed during build if unsupported serde attributes are encountered. <br/>Enabling this feature silences these warnings.                                                        |
 | import-esm         | When enabled,`import` statements in the generated file will have the `.js` extension in the end of the path to conform to the ES Modules spec. <br/> Example: `import { MyStruct } from "./my_struct.js"` |
+| import-deno        | When enabled,`import` statements in the generated file will have the `.ts` extension in the end of the path to conform to the Deno spec. <br/> Example: `import { MyStruct } from "./my_struct.ts"`       |
 | serde-json-impl    | Implement `TS` for types from *serde_json*                                                                                                                                                                |
 | chrono-impl        | Implement `TS` for types from *chrono*                                                                                                                                                                    |
 | bigdecimal-impl    | Implement `TS` for types from *bigdecimal*                                                                                                                                                                |
@@ -92,8 +93,8 @@ export type User = { user_id: number, first_name: string, last_name: string, };
 | ordered-float-impl | Implement `TS` for types from *ordered_float*                                                                                                                                                             |
 | heapless-impl      | Implement `TS` for types from *heapless*                                                                                                                                                                  |
 | semver-impl        | Implement `TS` for types from *semver*                                                                                                                                                                    |
-| smol_str-impl      | Implement `TS` for types from *smol_str*                                                                                                                                                                    |
-| tokio-impl         | Implement `TS` for types from *tokio*                                                                                                                                                                    |
+| smol_str-impl      | Implement `TS` for types from *smol_str*                                                                                                                                                                  |
+| tokio-impl         | Implement `TS` for types from *tokio*                                                                                                                                                                     |
 
 <br/>
 
diff --git a/ts-rs/Cargo.toml b/ts-rs/Cargo.toml
index 85d00d48..0c82bf6e 100644
--- a/ts-rs/Cargo.toml
+++ b/ts-rs/Cargo.toml
@@ -35,6 +35,7 @@ smol_str-impl = ["smol_str"]
 serde-json-impl = ["serde_json"]
 no-serde-warnings = ["ts-rs-macros/no-serde-warnings"]
 import-esm = []
+import-deno = []
 tokio-impl = ["tokio"]
 
 [dev-dependencies]
diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs
index 3640c96e..87818330 100644
--- a/ts-rs/src/export.rs
+++ b/ts-rs/src/export.rs
@@ -347,6 +347,10 @@ fn import_path(from: &Path, import: &Path) -> Result<String, ExportError> {
         str_path
     };
 
+    if cfg!(feature = "import-deno") {
+        return Ok(path);
+    }
+
     let path_without_extension = path.trim_end_matches(".ts");
 
     Ok(if cfg!(feature = "import-esm") {
diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs
index b2bbc073..cfa429a6 100644
--- a/ts-rs/src/lib.rs
+++ b/ts-rs/src/lib.rs
@@ -79,6 +79,7 @@
 //! | format             | Enables formatting of the generated TypeScript bindings. <br/>Currently, this unfortunately adds quite a few dependencies.                                                                                |
 //! | no-serde-warnings  | By default, warnings are printed during build if unsupported serde attributes are encountered. <br/>Enabling this feature silences these warnings.                                                        |
 //! | import-esm         | When enabled,`import` statements in the generated file will have the `.js` extension in the end of the path to conform to the ES Modules spec. <br/> Example: `import { MyStruct } from "./my_struct.js"` |
+//! | import-deno        | When enabled,`import` statements in the generated file will have the `.ts` extension in the end of the path to conform to the Deno spec. <br/> Example: `import { MyStruct } from "./my_struct.ts"`       |
 //! | serde-json-impl    | Implement `TS` for types from *serde_json*                                                                                                                                                                |
 //! | chrono-impl        | Implement `TS` for types from *chrono*                                                                                                                                                                    |
 //! | bigdecimal-impl    | Implement `TS` for types from *bigdecimal*                                                                                                                                                                |
@@ -90,8 +91,8 @@
 //! | ordered-float-impl | Implement `TS` for types from *ordered_float*                                                                                                                                                             |
 //! | heapless-impl      | Implement `TS` for types from *heapless*                                                                                                                                                                  |
 //! | semver-impl        | Implement `TS` for types from *semver*                                                                                                                                                                    |
-//! | smol_str-impl      | Implement `TS` for types from *smol_str*                                                                                                                                                                    |
-//! | tokio-impl         | Implement `TS` for types from *tokio*                                                                                                                                                                    |
+//! | smol_str-impl      | Implement `TS` for types from *smol_str*                                                                                                                                                                  |
+//! | tokio-impl         | Implement `TS` for types from *tokio*                                                                                                                                                                     |
 //!
 //! <br/>
 //!
diff --git a/ts-rs/tests/integration/imports.rs b/ts-rs/tests/integration/imports.rs
index bd7e5f81..87baae5d 100644
--- a/ts-rs/tests/integration/imports.rs
+++ b/ts-rs/tests/integration/imports.rs
@@ -28,42 +28,44 @@ fn test_def() {
     TestEnum::export_all().unwrap();
     let text = std::fs::read_to_string(TestEnum::default_output_path().unwrap()).unwrap();
 
-    let expected = match (cfg!(feature = "format"), cfg!(feature = "import-esm")) {
-        (true, true) => 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",
-        ),
-        (true, false) => concat!(
+    let extension = if cfg!(feature = "import-deno") {
+        ".ts"
+    } else if cfg!(feature = "import-esm") {
+        ".js"
+    } else {
+        ""
+    };
+
+    let import_a = format!(
+        "import type {{ TestTypeA }} from \"./ts_rs_test_type_a{}\";\n",
+        extension
+    );
+
+    let import_b = format!(
+        "import type {{ TestTypeB }} from \"./ts_rs_test_type_b{}\";\n",
+        extension
+    );
+
+    let expected = if cfg!(feature = "format") {
+        vec!(
             "// 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\";\n",
-            "import type { TestTypeB } from \"./ts_rs_test_type_b\";\n",
+            &import_a,
+            &import_b,
             "\n",
             "export type TestEnum = { \"C\": { value: TestTypeB<number> } } | {\n",
             "  \"A1\": { value: TestTypeA<number> };\n",
             "} | { \"A2\": { value: TestTypeA<number> } };\n",
-        ),
-        (false, true) => concat!(
+        )
+    } else {
+        vec!(
             "// 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",
+            &import_a,
+            &import_b,
             "\n",
             "export type TestEnum = { \"C\": { value: TestTypeB<number>, } } | { \"A1\": { value: TestTypeA<number>, } } | { \"A2\": { value: TestTypeA<number>, } };",
             "\n",
-        ),
-        (false, false) => 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\";\n",
-            "import type { TestTypeB } from \"./ts_rs_test_type_b\";\n",
-            "\n",
-            "export type TestEnum = { \"C\": { value: TestTypeB<number>, } } | { \"A1\": { value: TestTypeA<number>, } } | { \"A2\": { value: TestTypeA<number>, } };",
-            "\n",
-        ),
-    };
+        )
+    }.join("").to_string();
 
     assert_eq!(text, expected);
 }
diff --git a/ts-rs/tests/integration/issue_168.rs b/ts-rs/tests/integration/issue_168.rs
index 732ba9be..190671e0 100644
--- a/ts-rs/tests/integration/issue_168.rs
+++ b/ts-rs/tests/integration/issue_168.rs
@@ -32,7 +32,7 @@ struct Baz {
 }
 
 #[test]
-#[cfg(not(feature = "import-esm"))]
+#[cfg(not(any(feature = "import-esm", feature = "import-deno")))]
 fn issue_168() {
     assert_eq!(
         FooInlined::export_to_string().unwrap(),
diff --git a/ts-rs/tests/integration/issue_232.rs b/ts-rs/tests/integration/issue_232.rs
index 5cb6f684..582d105e 100644
--- a/ts-rs/tests/integration/issue_232.rs
+++ b/ts-rs/tests/integration/issue_232.rs
@@ -42,7 +42,7 @@ enum Enum {
 }
 
 #[test]
-#[cfg(not(feature = "import-esm"))]
+#[cfg(not(any(feature = "import-esm", feature = "import-deno")))]
 fn issue_232() {
     println!("{}", StateInlinedVec::export_to_string().unwrap());
     assert_eq!(

From b5f473e236e5516b58eec892eb79431bc3230adb Mon Sep 17 00:00:00 2001
From: eythaann <eythan.cvt@gmail.com>
Date: Sat, 18 Jan 2025 19:38:38 -0500
Subject: [PATCH 2/3] fix(test): same_file_export

---
 ts-rs/src/export.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs
index 87818330..9ec7956a 100644
--- a/ts-rs/src/export.rs
+++ b/ts-rs/src/export.rs
@@ -314,7 +314,7 @@ fn generate_imports<T: TS + ?Sized + 'static>(
             .and_then(std::ffi::OsStr::to_str)
             .map(|x| x.trim_end_matches(".ts"))
             .map(|x| format!("./{x}"))
-            .map(|x| x == rel_path.trim_end_matches(".js"))
+            .map(|x| x == rel_path.trim_end_matches(".js").trim_end_matches(".ts"))
             .unwrap_or(false);
 
         if is_same_file {

From b737ddfe115211b284b99efdcb9480ecf9a890d2 Mon Sep 17 00:00:00 2001
From: eythaann <eythan.cvt@gmail.com>
Date: Sat, 18 Jan 2025 19:49:18 -0500
Subject: [PATCH 3/3] enh(gen-imports): improve readability

---
 ts-rs/src/export.rs | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs
index 9ec7956a..9a0bfe66 100644
--- a/ts-rs/src/export.rs
+++ b/ts-rs/src/export.rs
@@ -309,13 +309,21 @@ fn generate_imports<T: TS + ?Sized + 'static>(
         let dep_path = out_dir.as_ref().join(dep.output_path);
         let rel_path = import_path(&path, &dep_path)?;
 
+        // Determine the appropriate extension to trim based on the active feature
+        let trimmed_rel_path = if cfg!(feature = "import-deno") {
+            rel_path.trim_end_matches(".ts")
+        } else if cfg!(feature = "import-esm") {
+            rel_path.trim_end_matches(".js")
+        } else {
+            &rel_path
+        };
+
+        // Check if the file is importing itself
         let is_same_file = path
-            .file_name()
+            .file_stem()
             .and_then(std::ffi::OsStr::to_str)
-            .map(|x| x.trim_end_matches(".ts"))
-            .map(|x| format!("./{x}"))
-            .map(|x| x == rel_path.trim_end_matches(".js").trim_end_matches(".ts"))
-            .unwrap_or(false);
+            .map(|stem| format!("./{stem}"))
+            .map_or(false, |formatted_stem| formatted_stem == trimmed_rel_path);
 
         if is_same_file {
             continue;