From ae64422ba662f5c01dbe5cfb2fa97d22ef36303a Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 14:18:06 -0300 Subject: [PATCH 01/11] --- .github/workflows/test.yml | 14 +++++++-- macros/src/lib.rs | 63 +++++++++++++++++++++++++++----------- ts-rs/src/export.rs | 30 ++---------------- ts-rs/src/lib.rs | 9 +----- ts-rs/tests/docs.rs | 16 +++++++++- 5 files changed, 75 insertions(+), 57 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 39b0e7601..10fb9a09b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,8 @@ jobs: working-directory: e2e/dependencies/consumer run: | TS_RS_EXPORT_DIR=custom-bindings cargo t - tsc custom-bindings/* --noEmit --noUnusedLocals --strict + shopt -s globstar + tsc custom-bindings/**/*.ts --noEmit --noUnusedLocals --strict e2e-workspace: name: Run 'workspace' end-to-end test runs-on: ubuntu-latest @@ -36,7 +37,8 @@ jobs: working-directory: e2e/workspace run: | TS_RS_EXPORT_DIR=custom-bindings cargo t - tsc parent/custom-bindings/* --noEmit --noUnusedLocals --strict + shopt -s globstar + tsc parent/custom-bindings/**/*.ts --noEmit --noUnusedLocals --strict e2e-example: name: End-to-end test example runs-on: ubuntu-latest @@ -54,7 +56,8 @@ jobs: working-directory: example run: | TS_RS_EXPORT_DIR=custom-bindings cargo t - tsc custom-bindings/* --noEmit --noUnusedLocals --strict + shopt -s globstar + tsc custom-bindings/**/*.ts --noEmit --noUnusedLocals --strict readme-up-to-date: name: Check that README.md is up-to-date @@ -88,3 +91,8 @@ jobs: cargo test --all-features shopt -s globstar tsc ts-rs/tests-out/**/*.ts --noEmit --noUnusedLocals --strict + - name: test with export env + run: | + TS_RS_EXPORT_DIR=output cargo test --no-default-features + shopt -s globstar + tsc ts-rs/output/tests-out/**/*.ts --noEmit --noUnusedLocals --strict diff --git a/macros/src/lib.rs b/macros/src/lib.rs index afb24c210..df1aa7bba 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -30,26 +30,54 @@ struct DerivedTS { impl DerivedTS { fn into_impl(mut self, rust_ty: Ident, generics: Generics) -> TokenStream { - let mut get_export_to = quote! {}; - let export_to = match &self.export_to { - Some(dirname) if dirname.ends_with('/') => { - format!("{}{}.ts", dirname, self.ts_name) - } - Some(filename) => filename.clone(), - None => { - get_export_to = quote! { - fn get_export_to() -> Option { - ts_rs::__private::get_export_to_path::() - } - }; - format!("bindings/{}.ts", self.ts_name) - } - }; - let export = self .export .then(|| self.generate_export_test(&rust_ty, &generics)); + let export_dir = std::env::var("TS_RS_EXPORT_DIR").ok(); + let export_to = { + let path = match self.export_to.as_deref() { + Some(dirname) if dirname.ends_with('/') => { + export_dir + .map_or_else( + || format!("{}{}.ts", dirname, self.ts_name), + |ts_rs_dir| format!( + "{}/{}{}.ts", + ts_rs_dir.trim_end_matches('/'), + dirname, + self.ts_name, + ) + ) + }, + Some(filename) => { + export_dir + .map_or_else( + || filename.to_owned(), + |ts_rs_dir| format!( + "{}/{}", + ts_rs_dir.trim_end_matches('/'), + filename, + ), + ) + }, + None => { + export_dir + .map_or_else( + || format!("bindings/{}.ts", self.ts_name), + |ts_rs_dir| format!( + "{}/{}.ts", + ts_rs_dir.trim_end_matches('/'), + self.ts_name, + ), + ) + }, + }; + + quote! { + const EXPORT_TO: Option<&'static str> = Some(#path); + } + }; + let docs = match &*self.docs { "" => None, docs => Some(quote!(const DOCS: Option<&'static str> = Some(#docs);)), @@ -67,13 +95,12 @@ impl DerivedTS { quote! { #impl_start { #assoc_type - const EXPORT_TO: Option<&'static str> = Some(#export_to); + #export_to fn ident() -> String { #ident.to_owned() } - #get_export_to #docs #name #decl diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index eeb60783c..de37373a1 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -5,7 +5,7 @@ use std::{ collections::BTreeMap, fmt::Write, path::{Component, Path, PathBuf}, - sync::{Mutex, OnceLock}, + sync::Mutex, }; use thiserror::Error; @@ -124,30 +124,6 @@ pub(crate) fn export_type_to>( Ok(()) } -#[doc(hidden)] -pub mod __private { - use super::*; - - const EXPORT_DIR_ENV_VAR: &str = "TS_RS_EXPORT_DIR"; - fn provided_default_dir() -> Option<&'static str> { - static EXPORT_TO: OnceLock> = OnceLock::new(); - EXPORT_TO - .get_or_init(|| std::env::var(EXPORT_DIR_ENV_VAR).ok()) - .as_deref() - } - - /// Returns the path to where `T` should be exported using the `TS_RS_EXPORT_DIR` environment variable. - /// - /// This should only be used by the TS derive macro; the `get_export_to` trait method should not - /// be overridden if the `#[ts(export_to = ..)]` attribute exists. - pub fn get_export_to_path() -> Option { - provided_default_dir().map_or_else( - || T::EXPORT_TO.map(ToString::to_string), - |path| Some(format!("{path}/{}.ts", T::ident())), - ) - } -} - /// Returns the generated defintion for `T`. pub(crate) fn export_type_to_string() -> Result { let mut buffer = String::with_capacity(1024); @@ -160,7 +136,7 @@ pub(crate) fn export_type_to_string() -> Result() -> Result { path::absolute(Path::new( - &T::get_export_to() + T::EXPORT_TO .ok_or_else(|| std::any::type_name::()) .map_err(ExportError::CannotBeExported)?, )) @@ -182,7 +158,7 @@ fn generate_decl(out: &mut String) { /// Push an import statement for all dependencies of `T` fn generate_imports(out: &mut String) -> Result<(), ExportError> { let export_to = - T::get_export_to().ok_or(ExportError::CannotBeExported(std::any::type_name::()))?; + T::EXPORT_TO.ok_or(ExportError::CannotBeExported(std::any::type_name::()))?; let path = Path::new(&export_to); let deps = T::dependencies(); diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 4f99ed972..bb88a1382 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -165,9 +165,6 @@ use std::{ pub use ts_rs_macros::TS; pub use crate::export::ExportError; -// Used in generated code. Not public API -#[doc(hidden)] -pub use crate::export::__private; use crate::typelist::TypeList; #[cfg(feature = "chrono-impl")] @@ -304,10 +301,6 @@ pub trait TS { } } - fn get_export_to() -> Option { - Self::EXPORT_TO.map(ToString::to_string) - } - /// Declaration of this type, e.g. `interface User { user_id: number, ... }`. /// This function will panic if the type has no declaration. /// @@ -428,7 +421,7 @@ impl Dependency { /// If `T` is not exportable (meaning `T::EXPORT_TO` is `None`), this function will return /// `None` pub fn from_ty() -> Option { - let exported_to = T::get_export_to()?; + let exported_to = T::EXPORT_TO.map(ToOwned::to_owned)?; Some(Dependency { type_id: TypeId::of::(), ts_name: T::ident(), diff --git a/ts-rs/tests/docs.rs b/ts-rs/tests/docs.rs index d007ec42a..1ede72d74 100644 --- a/ts-rs/tests/docs.rs +++ b/ts-rs/tests/docs.rs @@ -4,6 +4,8 @@ use std::{concat, fs}; use ts_rs::TS; +const PATH: Option<&str> = std::option_env!("TS_RS_EXPORT_DIR"); + /* ============================================================================================== */ /// Doc comment. @@ -95,6 +97,16 @@ struct G { /* ============================================================================================== */ +fn generate_path(path: &str) -> String { + format!( + "{}{path}", + PATH + .map(|x| x.trim_end_matches('/')) + .map(|x| format!("{x}/")) + .unwrap_or_default() + ) +} + #[test] fn export_a() { A::export().unwrap(); @@ -136,7 +148,9 @@ fn export_a() { ) }; - let actual_content = fs::read_to_string("tests-out/docs/A.ts").unwrap(); + let actual_content = fs::read_to_string( + generate_path("tests-out/docs/A.ts") + ).unwrap(); assert_eq!(actual_content, expected_content); } From 82863bb79e36b8a4dad7eb252b81e2cedb8803fc Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 15:00:55 -0300 Subject: [PATCH 02/11] Fix docs.rs test --- ts-rs/tests/docs.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/ts-rs/tests/docs.rs b/ts-rs/tests/docs.rs index 1ede72d74..e5875e9f3 100644 --- a/ts-rs/tests/docs.rs +++ b/ts-rs/tests/docs.rs @@ -4,7 +4,6 @@ use std::{concat, fs}; use ts_rs::TS; -const PATH: Option<&str> = std::option_env!("TS_RS_EXPORT_DIR"); /* ============================================================================================== */ @@ -98,13 +97,11 @@ struct G { /* ============================================================================================== */ fn generate_path(path: &str) -> String { - format!( - "{}{path}", - PATH - .map(|x| x.trim_end_matches('/')) - .map(|x| format!("{x}/")) - .unwrap_or_default() - ) + let base = std::env::var("TS_RS_EXPORT_DIR").ok() + .map(|x| x.trim_end_matches('/').to_owned()) + .map(|x| format!("{x}/")) + .unwrap_or_default(); + format!("{base}{path}") } #[test] @@ -196,7 +193,7 @@ fn export_b() { ) }; - let actual_content = fs::read_to_string("tests-out/docs/B.ts").unwrap(); + let actual_content = fs::read_to_string(generate_path("tests-out/docs/B.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -229,7 +226,7 @@ fn export_c() { ) }; - let actual_content = fs::read_to_string("tests-out/docs/C.ts").unwrap(); + let actual_content = fs::read_to_string(generate_path("tests-out/docs/C.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -261,7 +258,7 @@ fn export_d() { "export type D = null;" ) }; - let actual_content = fs::read_to_string("tests-out/docs/D.ts").unwrap(); + let actual_content = fs::read_to_string(generate_path("tests-out/docs/D.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -294,7 +291,7 @@ fn export_e() { ) }; - let actual_content = fs::read_to_string("tests-out/docs/E.ts").unwrap(); + let actual_content = fs::read_to_string(generate_path("tests-out/docs/E.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -342,7 +339,7 @@ fn export_f() { ) }; - let actual_content = fs::read_to_string("tests-out/docs/F.ts").unwrap(); + let actual_content = fs::read_to_string(generate_path("tests-out/docs/F.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -390,7 +387,7 @@ fn export_g() { ) }; - let actual_content = fs::read_to_string("tests-out/docs/G.ts").unwrap(); + let actual_content = fs::read_to_string(generate_path("tests-out/docs/G.ts")).unwrap(); assert_eq!(actual_content, expected_content); } From 7e73ed8d6926f315b1a88324b7e945230df6db8c Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 15:36:09 -0300 Subject: [PATCH 03/11] Fix path_bug.rs test --- ts-rs/tests/export_manually.rs | 14 ++++++++++++-- ts-rs/tests/path_bug.rs | 13 +++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/ts-rs/tests/export_manually.rs b/ts-rs/tests/export_manually.rs index 6bf0e3b39..26eed37fe 100644 --- a/ts-rs/tests/export_manually.rs +++ b/ts-rs/tests/export_manually.rs @@ -36,7 +36,12 @@ fn export_manually() { ) }; - let actual_content = fs::read_to_string("tests-out/export_manually/UserFile.ts").unwrap(); + let actual_content = fs::read_to_string( + match std::env::var("TS_RS_EXPORT_DIR") { + Ok(path) => format!("{}/tests-out/export_manually/UserFile.ts", path.trim_end_matches('/')), + Err(_) => "tests-out/export_manually/UserFile.ts".to_owned(), + } + ).unwrap(); assert_eq!(actual_content, expected_content); } @@ -57,7 +62,12 @@ fn export_manually_dir() { ) }; - let actual_content = fs::read_to_string("tests-out/export_manually/dir/UserDir.ts").unwrap(); + let actual_content = fs::read_to_string( + match std::env::var("TS_RS_EXPORT_DIR") { + Ok(path) => format!("{}/tests-out/export_manually/dir/UserDir.ts", path.trim_end_matches('/')), + Err(_) => "tests-out/export_manually/dir/UserDir.ts".to_owned(), + } + ).unwrap(); assert_eq!(actual_content, expected_content); } diff --git a/ts-rs/tests/path_bug.rs b/ts-rs/tests/path_bug.rs index cadfe1564..11dcc5e0e 100644 --- a/ts-rs/tests/path_bug.rs +++ b/ts-rs/tests/path_bug.rs @@ -15,10 +15,11 @@ struct Bar { #[test] fn path_bug() { - Foo::export().unwrap(); - Bar::export().unwrap(); + export_bindings_foo(); - let base = std::env::current_dir().unwrap(); - assert!(base.join("./tests-out/path_bug/Foo.ts").is_file()); - assert!(base.join("./tests-out/path_bug/aaa/Bar.ts").is_file()); -} \ No newline at end of file + let base = std::env::current_dir() + .unwrap() + .join(std::env::var("TS_RS_EXPORT_DIR").unwrap_or("".to_owned())); + assert!(base.join("../ts-rs/tests-out/path_bug/Foo.ts").is_file()); + assert!(base.join("tests-out/path_bug/aaa/Bar.ts").is_file()); +} From b25ac881121a8b1db582b1d2f14899fcf9afa80a Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 15:40:24 -0300 Subject: [PATCH 04/11] Fix docs.rs test again --- ts-rs/tests/docs.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ts-rs/tests/docs.rs b/ts-rs/tests/docs.rs index e5875e9f3..c0bfbd505 100644 --- a/ts-rs/tests/docs.rs +++ b/ts-rs/tests/docs.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] -use std::{concat, fs}; +use std::{concat, fs, path::PathBuf}; use ts_rs::TS; @@ -96,12 +96,10 @@ struct G { /* ============================================================================================== */ -fn generate_path(path: &str) -> String { - let base = std::env::var("TS_RS_EXPORT_DIR").ok() - .map(|x| x.trim_end_matches('/').to_owned()) - .map(|x| format!("{x}/")) - .unwrap_or_default(); - format!("{base}{path}") +fn generate_path(path: &str) -> PathBuf { + std::env::current_dir().unwrap() + .join(std::env::var("TS_RS_EXPORT_DIR").unwrap_or_default()) + .join(path) } #[test] From 8e86b3588fab591b89521ef1a5ec0823cacdb049 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 15:58:04 -0300 Subject: [PATCH 05/11] Try to get info on test failure --- ts-rs/tests/docs.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/ts-rs/tests/docs.rs b/ts-rs/tests/docs.rs index c0bfbd505..a8f88d61b 100644 --- a/ts-rs/tests/docs.rs +++ b/ts-rs/tests/docs.rs @@ -99,6 +99,7 @@ struct G { fn generate_path(path: &str) -> PathBuf { std::env::current_dir().unwrap() .join(std::env::var("TS_RS_EXPORT_DIR").unwrap_or_default()) + .join("tests-out/docs/") .join(path) } @@ -143,9 +144,9 @@ fn export_a() { ) }; - let actual_content = fs::read_to_string( - generate_path("tests-out/docs/A.ts") - ).unwrap(); + let Ok(actual_content) = fs::read_to_string(generate_path("A.ts")) else { + panic!("failed to read path: {}", generate_path("A.ts").to_string_lossy()) + }; assert_eq!(actual_content, expected_content); } @@ -191,7 +192,7 @@ fn export_b() { ) }; - let actual_content = fs::read_to_string(generate_path("tests-out/docs/B.ts")).unwrap(); + let actual_content = fs::read_to_string(generate_path("B.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -224,7 +225,7 @@ fn export_c() { ) }; - let actual_content = fs::read_to_string(generate_path("tests-out/docs/C.ts")).unwrap(); + let actual_content = fs::read_to_string(generate_path("C.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -256,7 +257,7 @@ fn export_d() { "export type D = null;" ) }; - let actual_content = fs::read_to_string(generate_path("tests-out/docs/D.ts")).unwrap(); + let actual_content = fs::read_to_string(generate_path("D.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -289,7 +290,7 @@ fn export_e() { ) }; - let actual_content = fs::read_to_string(generate_path("tests-out/docs/E.ts")).unwrap(); + let actual_content = fs::read_to_string(generate_path("E.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -337,7 +338,7 @@ fn export_f() { ) }; - let actual_content = fs::read_to_string(generate_path("tests-out/docs/F.ts")).unwrap(); + let actual_content = fs::read_to_string(generate_path("F.ts")).unwrap(); assert_eq!(actual_content, expected_content); } @@ -385,7 +386,7 @@ fn export_g() { ) }; - let actual_content = fs::read_to_string(generate_path("tests-out/docs/G.ts")).unwrap(); + let actual_content = fs::read_to_string(generate_path("G.ts")).unwrap(); assert_eq!(actual_content, expected_content); } From 83bebd549dfeee80b043f959cc4ad26d1210c385 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 16:13:30 -0300 Subject: [PATCH 06/11] More info --- ts-rs/tests/docs.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ts-rs/tests/docs.rs b/ts-rs/tests/docs.rs index a8f88d61b..bfef23ea8 100644 --- a/ts-rs/tests/docs.rs +++ b/ts-rs/tests/docs.rs @@ -145,7 +145,12 @@ fn export_a() { }; let Ok(actual_content) = fs::read_to_string(generate_path("A.ts")) else { - panic!("failed to read path: {}", generate_path("A.ts").to_string_lossy()) + panic!( + "failed to read path: {}\ncurrent dir: {}\nTS_RS_EXPORT_DIR: {}", + generate_path("A.ts").to_string_lossy(), + std::env::current_dir().unwrap().to_string_lossy(), + std::env::var("TS_RS_EXPORT_DIR").unwrap_or_default() + ) }; assert_eq!(actual_content, expected_content); From afbc24e34ca6ddef80129dc0814aadfb53f2c7ad Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 16:24:06 -0300 Subject: [PATCH 07/11] Test TS_RS_EXPORT_DIR first to get error faster --- .github/workflows/test.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 10fb9a09b..5be162919 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,6 +81,12 @@ jobs: - uses: actions-rs/toolchain@v1 with: toolchain: stable + - name: Test with export env + run: | + ls + TS_RS_EXPORT_DIR=output cargo test --no-default-features + shopt -s globstar + tsc ts-rs/output/tests-out/**/*.ts --noEmit --noUnusedLocals --strict - name: No features run: | cargo test --no-default-features @@ -91,8 +97,3 @@ jobs: cargo test --all-features shopt -s globstar tsc ts-rs/tests-out/**/*.ts --noEmit --noUnusedLocals --strict - - name: test with export env - run: | - TS_RS_EXPORT_DIR=output cargo test --no-default-features - shopt -s globstar - tsc ts-rs/output/tests-out/**/*.ts --noEmit --noUnusedLocals --strict From b325448cc644f0781fb8a0b433ff764e8d115ce8 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 16:44:51 -0300 Subject: [PATCH 08/11] Change how environment variable is read --- macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index df1aa7bba..fcf5af8fd 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -34,7 +34,7 @@ impl DerivedTS { .export .then(|| self.generate_export_test(&rust_ty, &generics)); - let export_dir = std::env::var("TS_RS_EXPORT_DIR").ok(); + let export_dir = std::option_env!("TS_RS_EXPORT_DIR"); let export_to = { let path = match self.export_to.as_deref() { Some(dirname) if dirname.ends_with('/') => { From 91485a3f55e0b08f0fa0b692b88e2f67753a5eac Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 17:13:37 -0300 Subject: [PATCH 09/11] Read environment variable outside macro --- .github/workflows/test.yml | 1 - macros/src/lib.rs | 38 +++----------------------------------- ts-rs/src/export.rs | 25 ++++++++++++++++++++----- ts-rs/src/lib.rs | 12 +++++++++++- ts-rs/tests/imports.rs | 13 +++++++++++-- 5 files changed, 45 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5be162919..133d37be6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -83,7 +83,6 @@ jobs: toolchain: stable - name: Test with export env run: | - ls TS_RS_EXPORT_DIR=output cargo test --no-default-features shopt -s globstar tsc ts-rs/output/tests-out/**/*.ts --noEmit --noUnusedLocals --strict diff --git a/macros/src/lib.rs b/macros/src/lib.rs index fcf5af8fd..09cd71d23 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -34,43 +34,11 @@ impl DerivedTS { .export .then(|| self.generate_export_test(&rust_ty, &generics)); - let export_dir = std::option_env!("TS_RS_EXPORT_DIR"); let export_to = { let path = match self.export_to.as_deref() { - Some(dirname) if dirname.ends_with('/') => { - export_dir - .map_or_else( - || format!("{}{}.ts", dirname, self.ts_name), - |ts_rs_dir| format!( - "{}/{}{}.ts", - ts_rs_dir.trim_end_matches('/'), - dirname, - self.ts_name, - ) - ) - }, - Some(filename) => { - export_dir - .map_or_else( - || filename.to_owned(), - |ts_rs_dir| format!( - "{}/{}", - ts_rs_dir.trim_end_matches('/'), - filename, - ), - ) - }, - None => { - export_dir - .map_or_else( - || format!("bindings/{}.ts", self.ts_name), - |ts_rs_dir| format!( - "{}/{}.ts", - ts_rs_dir.trim_end_matches('/'), - self.ts_name, - ), - ) - }, + Some(dirname) if dirname.ends_with('/') => format!("{}{}.ts", dirname, self.ts_name), + Some(filename) => filename.to_owned(), + None => format!("bindings/{}.ts", self.ts_name), }; quote! { diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index de37373a1..fa7e865f2 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -135,10 +135,19 @@ pub(crate) fn export_type_to_string() -> Result() -> Result { + let path = std::env::var("TS_RS_EXPORT_DIR") + .ok() + .as_deref() + .map(Path::new) + .unwrap_or_else(|| Path::new(".")) + .to_owned(); + path::absolute(Path::new( - T::EXPORT_TO - .ok_or_else(|| std::any::type_name::()) - .map_err(ExportError::CannotBeExported)?, + &path.join( + T::EXPORT_TO + .ok_or_else(|| std::any::type_name::()) + .map_err(ExportError::CannotBeExported)?, + ) )) } @@ -157,9 +166,15 @@ fn generate_decl(out: &mut String) { /// Push an import statement for all dependencies of `T` fn generate_imports(out: &mut String) -> Result<(), ExportError> { + let base = std::env::var("TS_RS_EXPORT_DIR") + .ok() + .as_deref() + .map(Path::new) + .unwrap_or_else(|| Path::new(".")) + .to_owned(); let export_to = T::EXPORT_TO.ok_or(ExportError::CannotBeExported(std::any::type_name::()))?; - let path = Path::new(&export_to); + let path = base.join(export_to); let deps = T::dependencies(); let deduplicated_deps = deps @@ -169,7 +184,7 @@ fn generate_imports(out: &mut String) -> Result<(), Ex .collect::>(); for (_, dep) in deduplicated_deps { - let rel_path = import_path(path, Path::new(&dep.exported_to)); + let rel_path = import_path(&path, Path::new(&dep.exported_to)); writeln!( out, "import type {{ {} }} from {:?};", diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index bb88a1382..45b8bcdbb 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -421,7 +421,17 @@ impl Dependency { /// If `T` is not exportable (meaning `T::EXPORT_TO` is `None`), this function will return /// `None` pub fn from_ty() -> Option { - let exported_to = T::EXPORT_TO.map(ToOwned::to_owned)?; + let base = std::env::var("TS_RS_EXPORT_DIR") + .ok() + .as_deref() + .map(Path::new) + .unwrap_or_else(|| Path::new(".")) + .to_owned(); + + let exported_to = base + .join(T::EXPORT_TO.map(ToOwned::to_owned)?) + .to_string_lossy() + .to_string(); Some(Dependency { type_id: TypeId::of::(), ts_name: T::ident(), diff --git a/ts-rs/tests/imports.rs b/ts-rs/tests/imports.rs index 0451e17bb..8f958ddb1 100644 --- a/ts-rs/tests/imports.rs +++ b/ts-rs/tests/imports.rs @@ -1,4 +1,6 @@ #![allow(dead_code)] +use std::path::Path; + use ts_rs::TS; #[derive(TS)] @@ -25,7 +27,14 @@ pub enum TestEnum { fn test_def() { // The only way to get access to how the imports look is to export the type and load the exported file TestEnum::export().unwrap(); - let text = std::fs::read_to_string(TestEnum::EXPORT_TO.unwrap()).unwrap(); + let path = std::env::var("TS_RS_EXPORT_DIR") + .ok() + .as_deref() + .map(Path::new) + .unwrap_or_else(|| Path::new(".")) + .to_owned() + .join(TestEnum::EXPORT_TO.unwrap()); + let text = std::fs::read_to_string(&path).unwrap(); let expected = match (cfg!(feature = "format"), cfg!(feature = "import-esm")) { (true, true) => concat!( @@ -65,5 +74,5 @@ fn test_def() { }; assert_eq!(text, expected); - std::fs::remove_file(TestEnum::EXPORT_TO.unwrap()).unwrap(); + std::fs::remove_file(path).unwrap(); } From 5ecae522de8a56dcbd46ab6ec3f8d255cfd7d231 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 17:53:27 -0300 Subject: [PATCH 10/11] add TS::export_path --- ts-rs/src/lib.rs | 27 +++++++++++++++++---------- ts-rs/tests/docs.rs | 30 ++++++++---------------------- ts-rs/tests/export_manually.rs | 14 ++------------ ts-rs/tests/path_bug.rs | 7 ++----- 4 files changed, 29 insertions(+), 49 deletions(-) diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 45b8bcdbb..e0ed34373 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -286,9 +286,23 @@ pub trait TS { /// ``` type WithoutGenerics: TS + ?Sized; + /// The path given to `#[ts(export_to = "...")]` const EXPORT_TO: Option<&'static str> = None; const DOCS: Option<&'static str> = None; + /// Returns the path to which the type will be exported, taking + /// `TS_RS_EXPORT_DIR` into account + fn export_path() -> Option { + let base = std::env::var("TS_RS_EXPORT_DIR") + .ok() + .as_deref() + .map(Path::new) + .unwrap_or_else(|| Path::new(".")) + .to_owned(); + + Some(base.join(Self::EXPORT_TO?)) + } + /// Identifier of this type, excluding generic parameters. fn ident() -> String { // by default, fall back to `TS::name()`. @@ -421,17 +435,10 @@ impl Dependency { /// If `T` is not exportable (meaning `T::EXPORT_TO` is `None`), this function will return /// `None` pub fn from_ty() -> Option { - let base = std::env::var("TS_RS_EXPORT_DIR") - .ok() + let exported_to = T::export_path() .as_deref() - .map(Path::new) - .unwrap_or_else(|| Path::new(".")) - .to_owned(); - - let exported_to = base - .join(T::EXPORT_TO.map(ToOwned::to_owned)?) - .to_string_lossy() - .to_string(); + .and_then(Path::to_str) + .map(ToOwned::to_owned)?; Some(Dependency { type_id: TypeId::of::(), ts_name: T::ident(), diff --git a/ts-rs/tests/docs.rs b/ts-rs/tests/docs.rs index bfef23ea8..0da2c8828 100644 --- a/ts-rs/tests/docs.rs +++ b/ts-rs/tests/docs.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] -use std::{concat, fs, path::PathBuf}; +use std::{concat, fs}; use ts_rs::TS; @@ -96,13 +96,6 @@ struct G { /* ============================================================================================== */ -fn generate_path(path: &str) -> PathBuf { - std::env::current_dir().unwrap() - .join(std::env::var("TS_RS_EXPORT_DIR").unwrap_or_default()) - .join("tests-out/docs/") - .join(path) -} - #[test] fn export_a() { A::export().unwrap(); @@ -144,14 +137,7 @@ fn export_a() { ) }; - let Ok(actual_content) = fs::read_to_string(generate_path("A.ts")) else { - panic!( - "failed to read path: {}\ncurrent dir: {}\nTS_RS_EXPORT_DIR: {}", - generate_path("A.ts").to_string_lossy(), - std::env::current_dir().unwrap().to_string_lossy(), - std::env::var("TS_RS_EXPORT_DIR").unwrap_or_default() - ) - }; + let actual_content = fs::read_to_string(A::export_path().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -197,7 +183,7 @@ fn export_b() { ) }; - let actual_content = fs::read_to_string(generate_path("B.ts")).unwrap(); + let actual_content = fs::read_to_string(B::export_path().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -230,7 +216,7 @@ fn export_c() { ) }; - let actual_content = fs::read_to_string(generate_path("C.ts")).unwrap(); + let actual_content = fs::read_to_string(C::export_path().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -262,7 +248,7 @@ fn export_d() { "export type D = null;" ) }; - let actual_content = fs::read_to_string(generate_path("D.ts")).unwrap(); + let actual_content = fs::read_to_string(D::export_path().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -295,7 +281,7 @@ fn export_e() { ) }; - let actual_content = fs::read_to_string(generate_path("E.ts")).unwrap(); + let actual_content = fs::read_to_string(E::export_path().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -343,7 +329,7 @@ fn export_f() { ) }; - let actual_content = fs::read_to_string(generate_path("F.ts")).unwrap(); + let actual_content = fs::read_to_string(F::export_path().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -391,7 +377,7 @@ fn export_g() { ) }; - let actual_content = fs::read_to_string(generate_path("G.ts")).unwrap(); + let actual_content = fs::read_to_string(G::export_path().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } diff --git a/ts-rs/tests/export_manually.rs b/ts-rs/tests/export_manually.rs index 26eed37fe..038a10bc0 100644 --- a/ts-rs/tests/export_manually.rs +++ b/ts-rs/tests/export_manually.rs @@ -36,12 +36,7 @@ fn export_manually() { ) }; - let actual_content = fs::read_to_string( - match std::env::var("TS_RS_EXPORT_DIR") { - Ok(path) => format!("{}/tests-out/export_manually/UserFile.ts", path.trim_end_matches('/')), - Err(_) => "tests-out/export_manually/UserFile.ts".to_owned(), - } - ).unwrap(); + let actual_content = fs::read_to_string(User::export_path().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -62,12 +57,7 @@ fn export_manually_dir() { ) }; - let actual_content = fs::read_to_string( - match std::env::var("TS_RS_EXPORT_DIR") { - Ok(path) => format!("{}/tests-out/export_manually/dir/UserDir.ts", path.trim_end_matches('/')), - Err(_) => "tests-out/export_manually/dir/UserDir.ts".to_owned(), - } - ).unwrap(); + let actual_content = fs::read_to_string(UserDir::export_path().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } diff --git a/ts-rs/tests/path_bug.rs b/ts-rs/tests/path_bug.rs index 11dcc5e0e..58d060d40 100644 --- a/ts-rs/tests/path_bug.rs +++ b/ts-rs/tests/path_bug.rs @@ -17,9 +17,6 @@ struct Bar { fn path_bug() { export_bindings_foo(); - let base = std::env::current_dir() - .unwrap() - .join(std::env::var("TS_RS_EXPORT_DIR").unwrap_or("".to_owned())); - assert!(base.join("../ts-rs/tests-out/path_bug/Foo.ts").is_file()); - assert!(base.join("tests-out/path_bug/aaa/Bar.ts").is_file()); + assert!(Foo::export_path().unwrap().is_file()); + assert!(Bar::export_path().unwrap().is_file()); } From 1dd9d7337d8fcd68888085925053c321967dbf0f Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 6 Mar 2024 18:02:53 -0300 Subject: [PATCH 11/11] Make output_path public --- ts-rs/src/export.rs | 14 ++++++-------- ts-rs/src/lib.rs | 17 +++-------------- ts-rs/tests/docs.rs | 16 ++++++++-------- ts-rs/tests/export_manually.rs | 6 +++--- ts-rs/tests/path_bug.rs | 6 +++--- 5 files changed, 23 insertions(+), 36 deletions(-) diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index fa7e865f2..0dae7933f 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -87,7 +87,7 @@ mod recursive_export { /// Export `T` to the file specified by the `#[ts(export_to = ..)]` attribute pub(crate) fn export_type() -> Result<(), ExportError> { let path = output_path::()?; - export_type_to::(&path) + export_type_to::(path::absolute(path)?) } /// Export `T` to the file specified by the `path` argument. @@ -134,7 +134,7 @@ pub(crate) fn export_type_to_string() -> Result() -> Result { +pub fn output_path() -> Result { let path = std::env::var("TS_RS_EXPORT_DIR") .ok() .as_deref() @@ -142,12 +142,10 @@ fn output_path() -> Result { .unwrap_or_else(|| Path::new(".")) .to_owned(); - path::absolute(Path::new( - &path.join( - T::EXPORT_TO - .ok_or_else(|| std::any::type_name::()) - .map_err(ExportError::CannotBeExported)?, - ) + Ok(path.join( + T::EXPORT_TO + .ok_or_else(|| std::any::type_name::()) + .map_err(ExportError::CannotBeExported)?, )) } diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index e0ed34373..4db38eedc 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -162,6 +162,7 @@ use std::{ path::{Path, PathBuf}, }; +pub use export::output_path; pub use ts_rs_macros::TS; pub use crate::export::ExportError; @@ -290,19 +291,6 @@ pub trait TS { const EXPORT_TO: Option<&'static str> = None; const DOCS: Option<&'static str> = None; - /// Returns the path to which the type will be exported, taking - /// `TS_RS_EXPORT_DIR` into account - fn export_path() -> Option { - let base = std::env::var("TS_RS_EXPORT_DIR") - .ok() - .as_deref() - .map(Path::new) - .unwrap_or_else(|| Path::new(".")) - .to_owned(); - - Some(base.join(Self::EXPORT_TO?)) - } - /// Identifier of this type, excluding generic parameters. fn ident() -> String { // by default, fall back to `TS::name()`. @@ -435,7 +423,8 @@ impl Dependency { /// If `T` is not exportable (meaning `T::EXPORT_TO` is `None`), this function will return /// `None` pub fn from_ty() -> Option { - let exported_to = T::export_path() + let exported_to = output_path::() + .ok() .as_deref() .and_then(Path::to_str) .map(ToOwned::to_owned)?; diff --git a/ts-rs/tests/docs.rs b/ts-rs/tests/docs.rs index 0da2c8828..6473c36bf 100644 --- a/ts-rs/tests/docs.rs +++ b/ts-rs/tests/docs.rs @@ -2,7 +2,7 @@ use std::{concat, fs}; -use ts_rs::TS; +use ts_rs::{TS, output_path}; /* ============================================================================================== */ @@ -137,7 +137,7 @@ fn export_a() { ) }; - let actual_content = fs::read_to_string(A::export_path().unwrap()).unwrap(); + let actual_content = fs::read_to_string(output_path::().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -183,7 +183,7 @@ fn export_b() { ) }; - let actual_content = fs::read_to_string(B::export_path().unwrap()).unwrap(); + let actual_content = fs::read_to_string(output_path::().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -216,7 +216,7 @@ fn export_c() { ) }; - let actual_content = fs::read_to_string(C::export_path().unwrap()).unwrap(); + let actual_content = fs::read_to_string(output_path::().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -248,7 +248,7 @@ fn export_d() { "export type D = null;" ) }; - let actual_content = fs::read_to_string(D::export_path().unwrap()).unwrap(); + let actual_content = fs::read_to_string(output_path::().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -281,7 +281,7 @@ fn export_e() { ) }; - let actual_content = fs::read_to_string(E::export_path().unwrap()).unwrap(); + let actual_content = fs::read_to_string(output_path::().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -329,7 +329,7 @@ fn export_f() { ) }; - let actual_content = fs::read_to_string(F::export_path().unwrap()).unwrap(); + let actual_content = fs::read_to_string(output_path::().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -377,7 +377,7 @@ fn export_g() { ) }; - let actual_content = fs::read_to_string(G::export_path().unwrap()).unwrap(); + let actual_content = fs::read_to_string(output_path::().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } diff --git a/ts-rs/tests/export_manually.rs b/ts-rs/tests/export_manually.rs index 038a10bc0..d0491d9cb 100644 --- a/ts-rs/tests/export_manually.rs +++ b/ts-rs/tests/export_manually.rs @@ -2,7 +2,7 @@ use std::{concat, fs}; -use ts_rs::TS; +use ts_rs::{output_path, TS}; #[derive(TS)] #[ts(export_to = "tests-out/export_manually/UserFile.ts")] @@ -36,7 +36,7 @@ fn export_manually() { ) }; - let actual_content = fs::read_to_string(User::export_path().unwrap()).unwrap(); + let actual_content = fs::read_to_string(output_path::().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } @@ -57,7 +57,7 @@ fn export_manually_dir() { ) }; - let actual_content = fs::read_to_string(UserDir::export_path().unwrap()).unwrap(); + let actual_content = fs::read_to_string(output_path::().unwrap()).unwrap(); assert_eq!(actual_content, expected_content); } diff --git a/ts-rs/tests/path_bug.rs b/ts-rs/tests/path_bug.rs index 58d060d40..0b5d905b9 100644 --- a/ts-rs/tests/path_bug.rs +++ b/ts-rs/tests/path_bug.rs @@ -1,5 +1,5 @@ #![allow(dead_code)] -use ts_rs::TS; +use ts_rs::{TS, output_path}; #[derive(TS)] #[ts(export, export_to = "../ts-rs/tests-out/path_bug/")] @@ -17,6 +17,6 @@ struct Bar { fn path_bug() { export_bindings_foo(); - assert!(Foo::export_path().unwrap().is_file()); - assert!(Bar::export_path().unwrap().is_file()); + assert!(output_path::().unwrap().is_file()); + assert!(output_path::().unwrap().is_file()); }