Repository: Aleph-Alpha/ts-rs
Pull request #263 (2024-03-10 17:21:22)

Revise API for manual exports
This is the next revision on our journey to improve path handling 😅 

- Make public API more consistent and more powerfull:

  | Function              | Includes Dependencies | To                 |
  |-----------------------|-----------------------|--------------------|
  | `TS::export`     | ❌                     | `TS_RS_EXPORT_DIR`  |
  | `TS::export_all`    | ✔️                    | `TS_RS_EXPORT_DIR` |
  | `TS::export_all_to` | ✔️                    | _custom_           |

  Before, we had 
  - `TS::export`, which is now `TS::export_all` and
  - `TS::export_to`, which ~~kept its name~~ got removed
  
  Aditionally, this PR adds `TS::export` and `TS::export_all_to`.

- To make this work, I (again) changed `TS::output_path`.  
  After the previous PR, it returned `TS_RS_EXPORT_DIR + export_to`.  
  The same functionality is now in the function `TS::default_output_path()`, while the new `TS::output_path()` now only returns `export_to`, without reading any environment variables

- I transfered that new naming scheme for the functions to `export.rs`.


@escritorio-gustavo I'd love to hear what you think about this.  
I took care in writing nice docs for it, and I think this is overall a good change.

## Todos
- [x] ~~`TS::export_to`: Imports are generated useing the "default" `TS_RS_EXPORT_DIR + export_to` path, instead of the one the user provided~~
- [x] Remove `TS::export_to`
- [x] Can `generate_imports` be simplified?
  - No, we actually need the output directory in case a type wants to import from a parent directory.
- [x] `TS::export_to_string`: Can/should we enable formatting for this?
  - Seems like we can't, since our formatter needs a path to be provided. We could use the default path, but since I'm not sure *why* it wants a path, I left it as-is.
- [ ] Is there a better name for `TS::output_path()` and `TS::default_output_path()`? 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ab65bf..636b3b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,8 +10,10 @@
 - Added `TS::dependency_types()` ([#221](https://github.com/Aleph-Alpha/ts-rs/pull/221))
 - Added `TS::generics()` ([#241](https://github.com/Aleph-Alpha/ts-rs/pull/241))
 - Added `TS::WithoutGenerics` ([#241](https://github.com/Aleph-Alpha/ts-rs/pull/241))
-- `Result`, `Option`, `HashMap` and `Vec` had their implementations of `TS` changed ([#241](https://github.com/Aleph-Alpha/ts-rs/pull/241))
 - Removed `TS::transparent()` ([#243](https://github.com/Aleph-Alpha/ts-rs/pull/243))
+- Handling of output paths ([#247](https://github.com/Aleph-Alpha/ts-rs/pull/247), [#250](https://github.com/Aleph-Alpha/ts-rs/pull/250), [#256](https://github.com/Aleph-Alpha/ts-rs/pull/256))
+  - All paths specified using `#[ts(export_to = "...")]` are now relative to `TS_RS_EXPORT_DIR`, which defaults to `./bindings/`
+- Replace `TS::export` with `TS::export`, `TS::export_all` and `TS::export_to_all` ([#263](https://github.com/Aleph-Alpha/ts-rs/pull/263))
 
 ### Features
 
@@ -27,11 +29,14 @@
 - Support `#[serde(untagged)]` on individual enum variants ([#226](https://github.com/Aleph-Alpha/ts-rs/pull/226))
 - Support for `#[serde(rename_all_fields = "...")]` ([#225](https://github.com/Aleph-Alpha/ts-rs/pull/225))
 - Export Rust doc comments/attributes on structs/enums as TSDoc strings ([#187](https://github.com/Aleph-Alpha/ts-rs/pull/187))
+- `Result`, `Option`, `HashMap` and `Vec` had their implementations of `TS` changed ([#241](https://github.com/Aleph-Alpha/ts-rs/pull/241))
 - Implement `#[ts(...)]` equivalent for `#[serde(tag = "...")]` being used on a struct with named fields ([#244](https://github.com/Aleph-Alpha/ts-rs/pull/244))
 
 ### Fixes
 
-- fix `#[ts(skip)]` and `#[serde(skip)]` in variants of adjacently or internally tagged enums ([#231](https://github.com/Aleph-Alpha/ts-rs/pull/231))
+- Fix `#[ts(skip)]` and `#[serde(skip)]` in variants of adjacently or internally tagged enums ([#231](https://github.com/Aleph-Alpha/ts-rs/pull/231))
 - `rename_all` with `camelCase` produces wrong names if fields were already in camelCase ([#198](https://github.com/Aleph-Alpha/ts-rs/pull/198))
 - Improve support for references ([#199](https://github.com/Aleph-Alpha/ts-rs/pull/199))
 - Generic type aliases generate correctly ([#233](https://github.com/Aleph-Alpha/ts-rs/pull/233))
+- Improve compiler errors ([#257](https://github.com/Aleph-Alpha/ts-rs/pull/257))
+- Update dependencies ([#255](https://github.com/Aleph-Alpha/ts-rs/pull/255))
\ No newline at end of file
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index f66aa3e..558d7dc 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -44,11 +44,8 @@ impl DerivedTS {
             };
 
             quote! {
-                fn output_path() -> Option<std::path::PathBuf> {
-                    let path = std::env::var("TS_RS_EXPORT_DIR");
-                    let path = path.as_deref().unwrap_or("./bindings");
-
-                    Some(std::path::Path::new(path).join(#path))
+                fn output_path() -> Option<&'static std::path::Path> {
+                    Some(std::path::Path::new(#path))
                 }
             }
         };
@@ -164,7 +161,7 @@ impl DerivedTS {
             #[cfg(test)]
             #[test]
             fn #test_fn() {
-                #ty::export().expect("could not export type");
+                #ty::export_all().expect("could not export type");
             }
         }
     }
diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs
index 12fe355..07d35df 100644
--- a/ts-rs/src/export.rs
+++ b/ts-rs/src/export.rs
@@ -1,12 +1,14 @@
 use std::{
     any::TypeId,
+    borrow::Cow,
     collections::BTreeMap,
     fmt::Write,
+    fs::File,
     path::{Component, Path, PathBuf},
     sync::Mutex,
 };
 
-pub(crate) use recursive_export::export_type_with_dependencies;
+pub(crate) use recursive_export::export_all_into;
 use thiserror::Error;
 
 use crate::TS;
@@ -30,16 +32,27 @@ pub enum ExportError {
 }
 
 mod recursive_export {
-    use std::{any::TypeId, collections::HashSet};
+    use std::{any::TypeId, collections::HashSet, path::Path};
 
-    use super::export_type;
+    use super::export_into;
     use crate::{
         typelist::{TypeList, TypeVisitor},
         ExportError, TS,
     };
 
+    /// Exports `T` to the file specified by the `#[ts(export_to = ..)]` attribute within the given
+    /// base directory.  
+    /// Additionally, all dependencies of `T` will be exported as well.
+    pub(crate) fn export_all_into<T: TS + ?Sized + 'static>(
+        out_dir: impl AsRef<Path>,
+    ) -> Result<(), ExportError> {
+        let mut seen = HashSet::new();
+        export_recursive::<T>(&mut seen, out_dir)
+    }
+
     struct Visit<'a> {
         seen: &'a mut HashSet<TypeId>,
+        out_dir: &'a Path,
         error: Option<ExportError>,
     }
 
@@ -51,29 +64,27 @@ mod recursive_export {
                 return;
             }
 
-            self.error = export_recursive::<T>(self.seen).err();
+            self.error = export_recursive::<T>(self.seen, self.out_dir).err();
         }
     }
 
-    /// Exports `T` to the file specified by the `#[ts(export_to = ..)]` attribute.
-    /// Additionally, all dependencies of `T` will be exported as well.
-    pub(crate) fn export_type_with_dependencies<T: TS + ?Sized + 'static>(
-    ) -> Result<(), ExportError> {
-        let mut seen = HashSet::new();
-        export_recursive::<T>(&mut seen)
-    }
-
     // exports T, then recursively calls itself with all of its dependencies
     fn export_recursive<T: TS + ?Sized + 'static>(
         seen: &mut HashSet<TypeId>,
+        out_dir: impl AsRef<Path>,
     ) -> Result<(), ExportError> {
         if !seen.insert(TypeId::of::<T>()) {
             return Ok(());
         }
+        let out_dir = out_dir.as_ref();
 
-        export_type::<T>()?;
+        export_into::<T>(out_dir)?;
 
-        let mut visitor = Visit { seen, error: None };
+        let mut visitor = Visit {
+            seen,
+            out_dir,
+            error: None,
+        };
         T::dependency_types().for_each(&mut visitor);
 
         if let Some(e) = visitor.error {
@@ -85,15 +96,19 @@ mod recursive_export {
 }
 
 /// Export `T` to the file specified by the `#[ts(export_to = ..)]` attribute
-pub(crate) fn export_type<T: TS + ?Sized + 'static>() -> Result<(), ExportError> {
+pub(crate) fn export_into<T: TS + ?Sized + 'static>(
+    out_dir: impl AsRef<Path>,
+) -> Result<(), ExportError> {
     let path = T::output_path()
         .ok_or_else(std::any::type_name::<T>)
         .map_err(ExportError::CannotBeExported)?;
-    export_type_to::<T, _>(path::absolute(path)?)
+    let path = out_dir.as_ref().join(path);
+
+    export_to::<T, _>(path::absolute(path)?)
 }
 
 /// Export `T` to the file specified by the `path` argument.
-pub(crate) fn export_type_to<T: TS + ?Sized + 'static, P: AsRef<Path>>(
+pub(crate) fn export_to<T: TS + ?Sized + 'static, P: AsRef<Path>>(
     path: P,
 ) -> Result<(), ExportError> {
     // Lock to make sure only one file will be written at a time.
@@ -102,7 +117,7 @@ pub(crate) fn export_type_to<T: TS + ?Sized + 'static, P: AsRef<Path>>(
     static FILE_LOCK: Mutex<()> = Mutex::new(());
 
     #[allow(unused_mut)]
-    let mut buffer = export_type_to_string::<T>()?;
+    let mut buffer = export_to_string::<T>()?;
 
     // format output
     #[cfg(feature = "format")]
@@ -121,20 +136,35 @@ pub(crate) fn export_type_to<T: TS + ?Sized + 'static, P: AsRef<Path>>(
         std::fs::create_dir_all(parent)?;
     }
     let lock = FILE_LOCK.lock().unwrap();
-    std::fs::write(path.as_ref(), buffer)?;
+    {
+        // Manually write to file & call `sync_data`. Otherwise, calling `fs::read(path)`
+        // immediately after `T::export()` might result in an empty file.
+        use std::io::Write;
+        let mut file = File::create(path)?;
+        file.write_all(buffer.as_bytes())?;
+        file.sync_data()?;
+    }
+
     drop(lock);
     Ok(())
 }
 
-/// Returns the generated defintion for `T`.
-pub(crate) fn export_type_to_string<T: TS + ?Sized + 'static>() -> Result<String, ExportError> {
+/// Returns the generated definition for `T`.
+pub(crate) fn export_to_string<T: TS + ?Sized + 'static>() -> Result<String, ExportError> {
     let mut buffer = String::with_capacity(1024);
     buffer.push_str(NOTE);
-    generate_imports::<T::WithoutGenerics>(&mut buffer)?;
+    generate_imports::<T::WithoutGenerics>(&mut buffer, default_out_dir())?;
     generate_decl::<T>(&mut buffer);
     Ok(buffer)
 }
 
+pub(crate) fn default_out_dir() -> Cow<'static, Path> {
+    match std::env::var("TS_RS_EXPORT_DIR") {
+        Err(..) => Cow::Borrowed(Path::new("./bindings")),
+        Ok(dir) => Cow::Owned(PathBuf::from(dir)),
+    }
+}
+
 /// Push the declaration of `T`
 fn generate_decl<T: TS + ?Sized>(out: &mut String) {
     // Type Docs
@@ -148,11 +178,15 @@ fn generate_decl<T: TS + ?Sized>(out: &mut String) {
     out.push_str(&T::decl());
 }
 
-/// Push an import statement for all dependencies of `T`
-fn generate_imports<T: TS + ?Sized + 'static>(out: &mut String) -> Result<(), ExportError> {
+/// Push an import statement for all dependencies of `T`.
+fn generate_imports<T: TS + ?Sized + 'static>(
+    out: &mut String,
+    out_dir: impl AsRef<Path>,
+) -> Result<(), ExportError> {
     let path = T::output_path()
         .ok_or_else(std::any::type_name::<T>)
         .map_err(ExportError::CannotBeExported)?;
+    let path = out_dir.as_ref().join(path);
 
     let deps = T::dependencies();
     let deduplicated_deps = deps
@@ -162,7 +196,8 @@ fn generate_imports<T: TS + ?Sized + 'static>(out: &mut String) -> Result<(), Ex
         .collect::<BTreeMap<_, _>>();
 
     for (_, dep) in deduplicated_deps {
-        let rel_path = import_path(&path, Path::new(&dep.exported_to));
+        let dep_path = out_dir.as_ref().join(dep.output_path);
+        let rel_path = import_path(&path, &dep_path);
         writeln!(
             out,
             "import type {{ {} }} from {:?};",
diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs
index 2952875..512e078 100644
--- a/ts-rs/src/lib.rs
+++ b/ts-rs/src/lib.rs
@@ -149,14 +149,24 @@ pub mod typelist;
 ///
 /// ### exporting
 /// Because Rusts procedural macros are evaluated before other compilation steps, TypeScript
-/// bindings cannot be exported during compile time.
+/// bindings __cannot__ be exported during compile time.
+/// 
 /// Bindings can be exported within a test, which ts-rs generates for you by adding `#[ts(export)]`
-/// to a type you wish to export to a file.
-/// If, for some reason, you need to do this during runtime, you can call [`TS::export`] yourself.
-///
-/// **Note:**
-/// Annotating a type with `#[ts(export)]` (or exporting it during runtime using
-/// [`TS::export`]) will cause all of its dependencies to be exported as well.
+/// to a type you wish to export to a file.  
+/// When `cargo test` is run, all types annotated with `#[ts(export)]` and all of their 
+/// dependencies will be written to `TS_RS_EXPORT_DIR`, or `./bindings` by default.
+/// 
+/// For each individual type, path and filename within the output directory can be changed using 
+/// `#[ts(export_to = "...")]`. By default, the filename will be derived from the name of the type.
+/// 
+/// If, for some reason, you need to do this during runtime or cannot use `#[ts(export)]`, bindings
+/// can be exported manually:
+/// 
+/// | Function              | Includes Dependencies | To                 |
+/// |-----------------------|-----------------------|--------------------|
+/// | [`TS::export`]        | ❌                    | `TS_RS_EXPORT_DIR` |
+/// | [`TS::export_all`]    | ✔️                    | `TS_RS_EXPORT_DIR` |
+/// | [`TS::export_all_to`] | ✔️                    | _custom_           |
 ///
 /// ### serde compatibility
 /// By default, the feature `serde-compat` is enabled.
@@ -380,39 +390,95 @@ pub trait TS {
         deps
     }
 
-    /// Manually export this type to a file.
-    /// The output file can be specified by annotating the type with `#[ts(export_to = ".."]`.
-    /// By default, the filename will be derived from the types name.
+    /// Manually export this type to the filesystem. 
+    /// To export this type together with all of its dependencies, use [`TS::export_all`].
+    ///
+    /// # Automatic Exporting
+    /// Types annotated with `#[ts(export)]`, together with all of their dependencies, will be 
+    /// exported automatically whenever `cargo test` is run.  
+    /// In that case, there is no need to manually call this function.
+    ///
+    /// # Target Directory
+    /// The target directory to which the type will be exported may be changed by setting the
+    /// `TS_RS_EXPORT_DIR` environment variable. By default, `./bindings` will be used.
+    /// 
+    /// To specify a target directory manually, use [`TS::export_all_to`], which also exports all
+    /// dependencies.
     ///
-    /// When a type is annotated with `#[ts(export)]`, it is exported automatically within a test.
-    /// This function is only usefull if you need to export the type outside of the context of a
-    /// test.
+    /// To alter the filename or path of the type within the target directory, 
+    /// use `#[ts(export_to = "...")]`.
     fn export() -> Result<(), ExportError>
     where
         Self: 'static,
     {
-        export::export_type_with_dependencies::<Self>()
+        let path = Self::default_output_path()
+            .ok_or_else(std::any::type_name::<Self>)
+            .map_err(ExportError::CannotBeExported)?;
+        
+        export::export_to::<Self, _>(path)
+    }
+    
+    /// Manually export this type to the filesystem, together with all of its dependencies.  
+    /// To export only this type, without its dependencies, use [`TS::export`].
+    ///
+    /// # Automatic Exporting
+    /// Types annotated with `#[ts(export)]`, together with all of their dependencies, will be 
+    /// exported automatically whenever `cargo test` is run.  
+    /// In that case, there is no need to manually call this function.
+    ///
+    /// # Target Directory
+    /// The target directory to which the types will be exported may be changed by setting the
+    /// `TS_RS_EXPORT_DIR` environment variable. By default, `./bindings` will be used.
+    /// 
+    /// To specify a target directory manually, use [`TS::export_all_to`].
+    /// 
+    /// To alter the filenames or paths of the types within the target directory, 
+    /// use `#[ts(export_to = "...")]`.
+    fn export_all() -> Result<(), ExportError>
+        where
+            Self: 'static,
+    {
+        export::export_all_into::<Self>(&*export::default_out_dir())
     }
-
-    /// Manually export this type to a file with a file with the specified path. This
-    /// function will ignore the `#[ts(export_to = "..)]` attribute.
-    fn export_to(path: impl AsRef<Path>) -> Result<(), ExportError>
-    where
-        Self: 'static,
+    
+    /// Manually export this type into the given directory, together with all of its dependencies.  
+    /// To export only this type, without its dependencies, use [`TS::export`].
+    /// 
+    /// Unlike [`TS::export_all`], this function disregards `TS_RS_EXPORT_DIR`, using the provided
+    /// directory instead.
+    /// 
+    /// To alter the filenames or paths of the types within the target directory, 
+    /// use `#[ts(export_to = "...")]`.
+    ///
+    /// # Automatic Exporting
+    /// Types annotated with `#[ts(export)]`, together with all of their dependencies, will be 
+    /// exported automatically whenever `cargo test` is run.  
+    /// In that case, there is no need to manually call this function.
+    fn export_all_to(out_dir: impl AsRef<Path>) -> Result<(), ExportError>
+        where
+            Self: 'static,
     {
-        export::export_type_to::<Self, _>(path)
+        export::export_all_into::<Self>(out_dir)
     }
 
     /// Manually generate bindings for this type, returning a [`String`].  
-    /// This function does not format the output, even if the `format` feature is enabled.
+    /// This function does not format the output, even if the `format` feature is enabled. TODO
+    ///
+    /// # Automatic Exporting
+    /// Types annotated with `#[ts(export)]`, together with all of their dependencies, will be 
+    /// exported automatically whenever `cargo test` is run.  
+    /// In that case, there is no need to manually call this function.
     fn export_to_string() -> Result<String, ExportError>
     where
         Self: 'static,
     {
-        export::export_type_to_string::<Self>()
+        export::export_to_string::<Self>()
     }
 
-    /// Returns the output path to where `T` should be exported.
+    /// Returns the output path to where `T` should be exported.  
+    /// The returned path does _not_ include the base directory from `TS_RS_EXPORT_DIR`.  
+    /// 
+    /// To get the output path containing `TS_RS_EXPORT_DIR`, use [`TS::default_output_path`].
     ///
     /// When deriving `TS`, the output path can be altered using `#[ts(export_to = "...")]`.  
     /// See the documentation of [`TS`] for more details.
@@ -422,9 +488,26 @@ pub trait TS {
     ///
     /// If `T` cannot be exported (e.g because it's a primitive type), this function will return
     /// `None`.
-    fn output_path() -> Option<PathBuf> {
+    fn output_path() -> Option<&'static Path> {
         None
     }
+
+    /// Returns the output path to where `T` should be exported.  
+    /// 
+    /// The output of this function depends on the environment variable `TS_RS_EXPORT_DIR`, which is
+    /// used as base directory. If it is not set, `./bindings` is used as default directory.
+    ///
+    /// To get the output path relative to `TS_RS_EXPORT_DIR` and without reading the environment
+    /// variable, use [`TS::output_path`].
+    ///
+    /// When deriving `TS`, the output path can be altered using `#[ts(export_to = "...")]`.  
+    /// See the documentation of [`TS`] for more details.
+    ///
+    /// If `T` cannot be exported (e.g because it's a primitive type), this function will return
+    /// `None`.
+    fn default_output_path() -> Option<PathBuf> {
+        Some(export::default_out_dir().join(Self::output_path()?))
+    }
 }
 
 /// A typescript type which is depended upon by other types.
@@ -436,8 +519,9 @@ pub struct Dependency {
     /// Name of the type in TypeScript
     pub ts_name: String,
     /// Path to where the type would be exported. By default a filename is derived from the types
-    /// name, which can be customized with `#[ts(export_to = "..")]`.
-    pub exported_to: String,
+    /// name, which can be customized with `#[ts(export_to = "..")]`.  
+    /// This path does _not_ include a base directory.
+    pub output_path: &'static Path,
 }
 
 impl Dependency {
@@ -445,11 +529,11 @@ impl Dependency {
     /// If `T` is not exportable (meaning `T::EXPORT_TO` is `None`), this function will return
     /// `None`
     pub fn from_ty<T: TS + 'static + ?Sized>() -> Option<Self> {
-        let exported_to = T::output_path()?.to_str()?.to_owned();
+        let output_path = T::output_path()?;
         Some(Dependency {
             type_id: TypeId::of::<T>(),
             ts_name: T::ident(),
-            exported_to,
+            output_path,
         })
     }
 }


Test patch:
diff --git a/ts-rs/tests/docs.rs b/ts-rs/tests/docs.rs
index 026df25..d989fa0 100644
--- a/ts-rs/tests/docs.rs
+++ b/ts-rs/tests/docs.rs
@@ -136,7 +136,7 @@ fn export_a() {
         )
     };
 
-    let actual_content = fs::read_to_string(A::output_path().unwrap()).unwrap();
+    let actual_content = fs::read_to_string(A::default_output_path().unwrap()).unwrap();
 
     assert_eq!(actual_content, expected_content);
 }
@@ -182,7 +182,7 @@ fn export_b() {
         )
     };
 
-    let actual_content = fs::read_to_string(B::output_path().unwrap()).unwrap();
+    let actual_content = fs::read_to_string(B::default_output_path().unwrap()).unwrap();
 
     assert_eq!(actual_content, expected_content);
 }
@@ -215,7 +215,7 @@ fn export_c() {
         )
     };
 
-    let actual_content = fs::read_to_string(C::output_path().unwrap()).unwrap();
+    let actual_content = fs::read_to_string(C::default_output_path().unwrap()).unwrap();
 
     assert_eq!(actual_content, expected_content);
 }
@@ -247,7 +247,7 @@ fn export_d() {
             "export type D = null;"
         )
     };
-    let actual_content = fs::read_to_string(D::output_path().unwrap()).unwrap();
+    let actual_content = fs::read_to_string(D::default_output_path().unwrap()).unwrap();
 
     assert_eq!(actual_content, expected_content);
 }
@@ -280,7 +280,7 @@ fn export_e() {
         )
     };
 
-    let actual_content = fs::read_to_string(E::output_path().unwrap()).unwrap();
+    let actual_content = fs::read_to_string(E::default_output_path().unwrap()).unwrap();
 
     assert_eq!(actual_content, expected_content);
 }
@@ -328,7 +328,7 @@ fn export_f() {
         )
     };
 
-    let actual_content = fs::read_to_string(F::output_path().unwrap()).unwrap();
+    let actual_content = fs::read_to_string(F::default_output_path().unwrap()).unwrap();
 
     assert_eq!(actual_content, expected_content);
 }
@@ -376,7 +376,7 @@ fn export_g() {
         )
     };
 
-    let actual_content = fs::read_to_string(G::output_path().unwrap()).unwrap();
+    let actual_content = fs::read_to_string(G::default_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 8686a70..cdf1118 100644
--- a/ts-rs/tests/export_manually.rs
+++ b/ts-rs/tests/export_manually.rs
@@ -36,7 +36,7 @@ fn export_manually() {
         )
     };
 
-    let actual_content = fs::read_to_string(User::output_path().unwrap()).unwrap();
+    let actual_content = fs::read_to_string(User::default_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::output_path().unwrap()).unwrap();
+    let actual_content = fs::read_to_string(UserDir::default_output_path().unwrap()).unwrap();
 
     assert_eq!(actual_content, expected_content);
 }
diff --git a/ts-rs/tests/imports.rs b/ts-rs/tests/imports.rs
index a1e8ec3..35cad17 100644
--- a/ts-rs/tests/imports.rs
+++ b/ts-rs/tests/imports.rs
@@ -26,7 +26,7 @@ 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::output_path().unwrap()).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!(
diff --git a/ts-rs/tests/path_bug.rs b/ts-rs/tests/path_bug.rs
index fc8cbd1..12ade1b 100644
--- a/ts-rs/tests/path_bug.rs
+++ b/ts-rs/tests/path_bug.rs
@@ -2,13 +2,13 @@
 use ts_rs::TS;
 
 #[derive(TS)]
-#[ts(export, export_to = "../ts-rs/path_bug/")]
+#[ts(export, export_to = "path_bug/aaa/")]
 struct Foo {
     bar: Bar,
 }
 
 #[derive(TS)]
-#[ts(export_to = "path_bug/aaa/")]
+#[ts(export_to = "../bindings/path_bug/")]
 struct Bar {
     i: i32,
 }
@@ -17,6 +17,6 @@ struct Bar {
 fn path_bug() {
     export_bindings_foo();
 
-    assert!(Foo::output_path().unwrap().is_file());
-    assert!(Bar::output_path().unwrap().is_file());
+    assert!(Foo::default_output_path().unwrap().is_file());
+    assert!(Bar::default_output_path().unwrap().is_file());
 }