Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative handling of output paths #256

Merged
merged 3 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ jobs:
working-directory: e2e/workspace
run: |
cargo t
tsc parent/bindings/* --noEmit --noUnusedLocals --strict
shopt -s globstar
tsc parent/bindings/**/*.ts --noEmit --noUnusedLocals --strict
rm -rf parent/bindings
- name: workspace e2e with default export env
working-directory: e2e/workspace
run: |
TS_RS_EXPORT_DIR=custom-bindings cargo t
shopt -s globstar
tsc parent/custom-bindings/**/*.ts --noEmit --noUnusedLocals --strict
rm -rf parent/custom-bindings
e2e-example:
name: End-to-end test example
runs-on: ubuntu-latest
Expand Down Expand Up @@ -86,13 +89,16 @@ jobs:
TS_RS_EXPORT_DIR=output cargo test --no-default-features
shopt -s globstar
tsc ts-rs/output/tests-out/**/*.ts --noEmit --noUnusedLocals --strict
rm -rf ts-rs/output
- name: No features
run: |
cargo test --no-default-features
shopt -s globstar
tsc ts-rs/tests-out/**/*.ts --noEmit --noUnusedLocals
tsc ts-rs/bindings/tests-out/**/*.ts --noEmit --noUnusedLocals
rm -rf ts-rs/bindings
- name: All features
run: |
cargo test --all-features
shopt -s globstar
tsc ts-rs/tests-out/**/*.ts --noEmit --noUnusedLocals --strict
tsc ts-rs/bindings/tests-out/**/*.ts --noEmit --noUnusedLocals --strict
rm -rf ts-rs/bindings
2 changes: 1 addition & 1 deletion example/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use uuid::Uuid;

#[derive(Serialize, TS)]
#[ts(rename_all = "lowercase")]
#[ts(export, export_to = "bindings/UserRole.ts")]
#[ts(export, export_to = "UserRole.ts")]
enum Role {
User,
#[ts(rename = "administrator")]
Comment on lines 10 to 15
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before, renaming the file from Role.ts to UserRole.ts required adding the bindings/ prefix back again.
With this change, this is no longer necessary.

Expand Down
13 changes: 9 additions & 4 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,22 @@ impl DerivedTS {
.export
.then(|| self.generate_export_test(&rust_ty, &generics));

let export_to = {
let output_path_fn = {
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),
None => format!("{}.ts", self.ts_name),
};

quote! {
const EXPORT_TO: Option<&'static str> = Some(#path);
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))
}
Comment on lines +47 to +52
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation of that is super simple - Take TS_RS_EXPORT_DIR (or ./bindings), and append the name (or #[ts(export_to = "...")] override to it.

}
};

Expand All @@ -65,7 +70,6 @@ impl DerivedTS {
quote! {
#impl_start {
#assoc_type
#export_to

fn ident() -> String {
#ident.to_owned()
Expand All @@ -76,6 +80,7 @@ impl DerivedTS {
#decl
#inline
#generics_fn
#output_path_fn

#[allow(clippy::unused_unit)]
fn dependency_types() -> impl ts_rs::typelist::TypeList
Expand Down
40 changes: 10 additions & 30 deletions ts-rs/src/export.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
mod path;

use std::{
any::TypeId,
collections::BTreeMap,
Expand All @@ -8,10 +6,13 @@ use std::{
sync::Mutex,
};

pub(crate) use recursive_export::export_type_with_dependencies;
use thiserror::Error;

use crate::TS;

mod path;

const NOTE: &str = "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n";

/// An error which may occur when exporting a type
Expand All @@ -28,7 +29,6 @@ pub enum ExportError {
ManifestDirNotSet,
}

pub(crate) use recursive_export::export_type_with_dependencies;
mod recursive_export {
use std::{any::TypeId, collections::HashSet};

Expand All @@ -47,7 +47,7 @@ mod recursive_export {
fn visit<T: TS + 'static + ?Sized>(&mut self) {
// if an error occurred previously, or the type cannot be exported (it's a primitive),
// we return
if self.error.is_some() || T::EXPORT_TO.is_none() {
if self.error.is_some() || T::output_path().is_none() {
return;
}

Expand Down Expand Up @@ -86,7 +86,9 @@ 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>()?;
let path = T::output_path()
.ok_or_else(std::any::type_name::<T>)
.map_err(ExportError::CannotBeExported)?;
export_type_to::<T, _>(path::absolute(path)?)
}

Expand Down Expand Up @@ -133,22 +135,6 @@ pub(crate) fn export_type_to_string<T: TS + ?Sized + 'static>() -> Result<String
Ok(buffer)
}

/// Compute the output path to where `T` should be exported.
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)?,
))
}

/// Push the declaration of `T`
fn generate_decl<T: TS + ?Sized>(out: &mut String) {
// Type Docs
Expand All @@ -164,15 +150,9 @@ 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::EXPORT_TO.ok_or(ExportError::CannotBeExported(std::any::type_name::<T>()))?;
let path = base.join(export_to);
let path = T::output_path()
.ok_or_else(std::any::type_name::<T>)
.map_err(ExportError::CannotBeExported)?;

let deps = T::dependencies();
let deduplicated_deps = deps
Expand Down
41 changes: 23 additions & 18 deletions ts-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@
//! | 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* |
//!
//!
//! <br/>
//!
//!
//! If there's a type you're dealing with which doesn't implement `TS`, use either
//! `#[ts(as = "..")]` or `#[ts(type = "..")]`, or open a PR.
//!
Expand Down Expand Up @@ -132,7 +132,6 @@ use std::{
path::{Path, PathBuf},
};

pub use export::output_path;
pub use ts_rs_macros::TS;

pub use crate::export::ExportError;
Expand All @@ -154,9 +153,9 @@ pub mod typelist;
/// 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
/// 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.
///
/// ### serde compatibility
Expand All @@ -179,12 +178,11 @@ pub mod typelist;
/// TS_RS_EXPORT_DIR = { value = "<OVERRIDE_DIR>", relative = true }
/// ```
/// <br/>
///
///
/// - **`#[ts(export_to = "..")]`**
/// Specifies where the type should be exported to. Defaults to `bindings/<name>.ts`.
/// Specifies where the type should be exported to. Defaults to `<name>.ts`.
/// The path given to the `export_to` attribute is relative to the `TS_RS_EXPORT_DIR` environment variable,
/// or, if `TS_RS_EXPORT_DIR` is not set, to you project's root directory - more specifically,
/// it'll be relative to the `Cargo.toml` file.
/// or, if `TS_RS_EXPORT_DIR` is not set, to `./bindings`
/// If the provided path ends in a trailing `/`, it is interpreted as a directory.
/// Note that you need to add the `export` attribute as well, in order to generate a test which exports the type.
/// <br/><br/>
Expand Down Expand Up @@ -299,11 +297,8 @@ pub trait TS {
/// ```
type WithoutGenerics: TS + ?Sized;

/// The path given to `#[ts(export_to = "...")]`
const EXPORT_TO: Option<&'static str> = None;

/// JSDoc comment to describe this type in TypeScript - when `TS` is derived, docs are
/// automatically read from your doc comments or `#[doc = ".."]` attrubutes
/// automatically read from your doc comments or `#[doc = ".."]` attributes
const DOCS: Option<&'static str> = None;

/// Identifier of this type, excluding generic parameters.
Expand Down Expand Up @@ -409,6 +404,20 @@ pub trait TS {
{
export::export_type_to_string::<Self>()
}

/// Returns the output path to where `T` should be exported.
///
/// When deriving `TS`, the output path can be altered using `#[ts(export_to = "...")]`.
/// See the documentation of [`TS`] for more details.
///
/// 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.
///
/// If `T` cannot be exported (e.g because it's a primitive type), this function will return
/// `None`.
fn output_path() -> Option<PathBuf> {
None
}
Comment on lines +407 to +420
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the implementation: I moved output_path() into TS, and completely got rid of the const EXPORT_TO.

}

/// A typescript type which is depended upon by other types.
Expand All @@ -429,11 +438,7 @@ 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 = output_path::<T>()
.ok()
.as_deref()
.and_then(Path::to_str)
.map(ToOwned::to_owned)?;
let exported_to = T::output_path()?.to_str()?.to_owned();
Some(Dependency {
type_id: TypeId::of::<T>(),
ts_name: T::ident(),
Expand Down
16 changes: 8 additions & 8 deletions ts-rs/tests/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::{concat, fs};

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

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

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

let actual_content = fs::read_to_string(output_path::<A>().unwrap()).unwrap();
let actual_content = fs::read_to_string(A::output_path().unwrap()).unwrap();

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

let actual_content = fs::read_to_string(output_path::<B>().unwrap()).unwrap();
let actual_content = fs::read_to_string(B::output_path().unwrap()).unwrap();

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

let actual_content = fs::read_to_string(output_path::<C>().unwrap()).unwrap();
let actual_content = fs::read_to_string(C::output_path().unwrap()).unwrap();

assert_eq!(actual_content, expected_content);
}
Expand Down Expand Up @@ -247,7 +247,7 @@ fn export_d() {
"export type D = null;"
)
};
let actual_content = fs::read_to_string(output_path::<D>().unwrap()).unwrap();
let actual_content = fs::read_to_string(D::output_path().unwrap()).unwrap();

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

let actual_content = fs::read_to_string(output_path::<E>().unwrap()).unwrap();
let actual_content = fs::read_to_string(E::output_path().unwrap()).unwrap();

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

let actual_content = fs::read_to_string(output_path::<F>().unwrap()).unwrap();
let actual_content = fs::read_to_string(F::output_path().unwrap()).unwrap();

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

let actual_content = fs::read_to_string(output_path::<G>().unwrap()).unwrap();
let actual_content = fs::read_to_string(G::output_path().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::{output_path, TS};
use ts_rs::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(output_path::<User>().unwrap()).unwrap();
let actual_content = fs::read_to_string(User::output_path().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(output_path::<UserDir>().unwrap()).unwrap();
let actual_content = fs::read_to_string(UserDir::output_path().unwrap()).unwrap();

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

use ts_rs::TS;

Expand Down Expand Up @@ -27,14 +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 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 text = std::fs::read_to_string(TestEnum::output_path().unwrap()).unwrap();

let expected = match (cfg!(feature = "format"), cfg!(feature = "import-esm")) {
(true, true) => concat!(
Expand Down Expand Up @@ -74,5 +66,4 @@ fn test_def() {
};

assert_eq!(text, expected);
std::fs::remove_file(path).unwrap();
}
Loading
Loading