Skip to content

Commit

Permalink
Merge pull request #250 from Aleph-Alpha/export_relative_ts_rs_env
Browse files Browse the repository at this point in the history
Make `#[ts(export_to = "...")]` relative to `TS_RS_EXPORT_DIR`
  • Loading branch information
escritorio-gustavo authored Mar 7, 2024
2 parents ffac0e2 + 1dd9d73 commit 3a667d4
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 81 deletions.
14 changes: 11 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -78,6 +81,11 @@ jobs:
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- 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
- name: No features
run: |
cargo test --no-default-features
Expand Down
31 changes: 13 additions & 18 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,22 @@ 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<String> {
ts_rs::__private::get_export_to_path::<Self>()
}
};
format!("bindings/{}.ts", self.ts_name)
}
};

let export = self
.export
.then(|| self.generate_export_test(&rust_ty, &generics));

let export_to = {
let path = match self.export_to.as_deref() {
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! {
const EXPORT_TO: Option<&'static str> = Some(#path);
}
};

let docs = match &*self.docs {
"" => None,
docs => Some(quote!(const DOCS: Option<&'static str> = Some(#docs);)),
Expand All @@ -67,13 +63,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
Expand Down
53 changes: 21 additions & 32 deletions ts-rs/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
collections::BTreeMap,
fmt::Write,
path::{Component, Path, PathBuf},
sync::{Mutex, OnceLock},
sync::Mutex,
};

use thiserror::Error;
Expand Down Expand Up @@ -87,7 +87,7 @@ 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> {
let path = output_path::<T>()?;
export_type_to::<T, _>(&path)
export_type_to::<T, _>(path::absolute(path)?)
}

/// Export `T` to the file specified by the `path` argument.
Expand Down Expand Up @@ -124,30 +124,6 @@ pub(crate) fn export_type_to<T: TS + ?Sized + 'static, P: AsRef<Path>>(
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<Option<String>> = 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<T: TS + ?Sized>() -> Option<String> {
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<T: TS + ?Sized + 'static>() -> Result<String, ExportError> {
let mut buffer = String::with_capacity(1024);
Expand All @@ -158,9 +134,16 @@ pub(crate) fn export_type_to_string<T: TS + ?Sized + 'static>() -> Result<String
}

/// Compute the output path to where `T` should be exported.
fn output_path<T: TS + ?Sized>() -> Result<PathBuf, ExportError> {
path::absolute(Path::new(
&T::get_export_to()
pub fn output_path<T: TS + ?Sized>() -> Result<PathBuf, ExportError> {
let path = std::env::var("TS_RS_EXPORT_DIR")
.ok()
.as_deref()
.map(Path::new)
.unwrap_or_else(|| Path::new("."))
.to_owned();

Ok(path.join(
T::EXPORT_TO
.ok_or_else(|| std::any::type_name::<T>())
.map_err(ExportError::CannotBeExported)?,
))
Expand All @@ -181,9 +164,15 @@ fn generate_decl<T: TS + ?Sized>(out: &mut String) {

/// Push an import statement for all dependencies of `T`
fn generate_imports<T: TS + ?Sized + 'static>(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::get_export_to().ok_or(ExportError::CannotBeExported(std::any::type_name::<T>()))?;
let path = Path::new(&export_to);
T::EXPORT_TO.ok_or(ExportError::CannotBeExported(std::any::type_name::<T>()))?;
let path = base.join(export_to);

let deps = T::dependencies();
let deduplicated_deps = deps
Expand All @@ -193,7 +182,7 @@ 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 rel_path = import_path(&path, Path::new(&dep.exported_to));
writeln!(
out,
"import type {{ {} }} from {:?};",
Expand Down
15 changes: 7 additions & 8 deletions ts-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,10 @@ use std::{
path::{Path, PathBuf},
};

pub use export::output_path;
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")]
Expand Down Expand Up @@ -289,6 +287,7 @@ 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;

Expand All @@ -304,10 +303,6 @@ pub trait TS {
}
}

fn get_export_to() -> Option<String> {
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.
///
Expand Down Expand Up @@ -428,7 +423,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::get_export_to()?;
let exported_to = output_path::<T>()
.ok()
.as_deref()
.and_then(Path::to_str)
.map(ToOwned::to_owned)?;
Some(Dependency {
type_id: TypeId::of::<T>(),
ts_name: T::ident(),
Expand Down
17 changes: 9 additions & 8 deletions ts-rs/tests/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

use std::{concat, fs};

use ts_rs::TS;
use ts_rs::{TS, output_path};


/* ============================================================================================== */

Expand Down Expand Up @@ -136,7 +137,7 @@ fn export_a() {
)
};

let actual_content = fs::read_to_string("tests-out/docs/A.ts").unwrap();
let actual_content = fs::read_to_string(output_path::<A>().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
Expand Down Expand Up @@ -182,7 +183,7 @@ fn export_b() {
)
};

let actual_content = fs::read_to_string("tests-out/docs/B.ts").unwrap();
let actual_content = fs::read_to_string(output_path::<B>().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
Expand Down Expand Up @@ -215,7 +216,7 @@ fn export_c() {
)
};

let actual_content = fs::read_to_string("tests-out/docs/C.ts").unwrap();
let actual_content = fs::read_to_string(output_path::<C>().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
Expand Down Expand Up @@ -247,7 +248,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(output_path::<D>().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
Expand Down Expand Up @@ -280,7 +281,7 @@ fn export_e() {
)
};

let actual_content = fs::read_to_string("tests-out/docs/E.ts").unwrap();
let actual_content = fs::read_to_string(output_path::<E>().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
Expand Down Expand Up @@ -328,7 +329,7 @@ fn export_f() {
)
};

let actual_content = fs::read_to_string("tests-out/docs/F.ts").unwrap();
let actual_content = fs::read_to_string(output_path::<F>().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
Expand Down Expand Up @@ -376,7 +377,7 @@ fn export_g() {
)
};

let actual_content = fs::read_to_string("tests-out/docs/G.ts").unwrap();
let actual_content = fs::read_to_string(output_path::<G>().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
6 changes: 3 additions & 3 deletions ts-rs/tests/export_manually.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -36,7 +36,7 @@ fn export_manually() {
)
};

let actual_content = fs::read_to_string("tests-out/export_manually/UserFile.ts").unwrap();
let actual_content = fs::read_to_string(output_path::<User>().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
Expand All @@ -57,7 +57,7 @@ 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(output_path::<UserDir>().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
13 changes: 11 additions & 2 deletions ts-rs/tests/imports.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#![allow(dead_code)]
use std::path::Path;

use ts_rs::TS;

#[derive(TS)]
Expand All @@ -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!(
Expand Down Expand Up @@ -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();
}
12 changes: 5 additions & 7 deletions ts-rs/tests/path_bug.rs
Original file line number Diff line number Diff line change
@@ -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/")]
Expand All @@ -15,10 +15,8 @@ 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());
}
assert!(output_path::<Foo>().unwrap().is_file());
assert!(output_path::<Bar>().unwrap().is_file());
}

0 comments on commit 3a667d4

Please sign in to comment.