Skip to content

Commit

Permalink
Merge branch 'main' into docstring
Browse files Browse the repository at this point in the history
  • Loading branch information
Mattchine authored May 29, 2022
2 parents 08ec9c5 + 81d83ae commit 9002a33
Show file tree
Hide file tree
Showing 13 changed files with 97 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
on: [push]
on: [push, pull_request]
name: Test
jobs:
test:
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ If you are unsure what to work on or want to discuss your idea, feel free to ope
### Documentation
After implementing a new feature, please document it in the doc comment on `TS` in `ts_rs/lib.rs`.
`README.md` is generated from the module doc comment in `ts_rs/lib.rs`. If you added/updated documentation there, run
`cargo readme > ../README.md` from the `ts_rs/` directory.
`cargo readme > ../README.md` from the `ts-rs/` directory.
You can install `cargo readme` by running `cargo install cargo-readme`.


Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ When running `cargo test`, the TypeScript bindings will be exported to the file
- serde compatibility
- generic types

### limitations
- generic fields cannot be inlined or flattened (#56)
- type aliases must not alias generic types (#70)

### cargo features
- `serde-compat` (default)

Expand Down Expand Up @@ -94,6 +98,10 @@ When running `cargo test`, the TypeScript bindings will be exported to the file

Implement `TS` for `IndexMap` and `IndexSet` from indexmap

- `ordered-float-impl`

Implement `TS` for `OrderedFloat` from ordered_float

If there's a type you're dealing with which doesn't implement `TS`, use `#[ts(type = "..")]` or open a PR.

### serde compatability
Expand Down
2 changes: 1 addition & 1 deletion macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ts-rs-macros"
version = "6.1.2"
version = "6.2.0"
authors = ["Moritz Bischof <[email protected]>"]
edition = "2021"
description = "derive macro for ts-rs"
Expand Down
5 changes: 3 additions & 2 deletions macros/src/types/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
attr::{FieldAttr, Inflection, StructAttr},
deps::Dependencies,
types::generics::{format_generics, format_type},
utils::to_ts_ident,
utils::{raw_name_to_ts_field, to_ts_ident},
DerivedTS,
};

Expand Down Expand Up @@ -108,14 +108,15 @@ fn format_field(
(None, Some(rn)) => rn.apply(&field_name),
(None, None) => field_name,
};
let valid_name = raw_name_to_ts_field(name);

let doc_string = match doc_string {
Some(s) => format!("\n/**\n* {}\n*/\n", s),
None => "".to_string(),
};

formatted_fields.push(quote! {
format!("{}{}{}: {},", #doc_string, #name, #optional_annotation, #formatted_ty)
format!("{}{}{}: {},", #doc_string, #valid_name, #optional_annotation, #formatted_ty)
});

Ok(())
Expand Down
19 changes: 19 additions & 0 deletions macros/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,25 @@ pub fn to_ts_ident(ident: &Ident) -> String {
}
}

/// Convert an arbitrary name to a valid Typescript field name.
///
/// If the name contains special characters it will be wrapped in quotes.
pub fn raw_name_to_ts_field(value: String) -> String {
let valid = value
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '$')
&& value
.chars()
.next()
.map(|first| !first.is_numeric())
.unwrap_or(true);
if !valid {
format!(r#""{value}""#)
} else {
value
}
}

/// Parse all `#[ts(..)]` attributes from the given slice.
pub fn parse_attrs<'a, A>(attrs: &'a [Attribute]) -> Result<impl Iterator<Item = A>>
where
Expand Down
6 changes: 4 additions & 2 deletions ts-rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ts-rs"
version = "6.1.2"
version = "6.2.0"
authors = ["Moritz Bischof <[email protected]>"]
edition = "2021"
license = "MIT"
Expand All @@ -21,13 +21,14 @@ serde-compat = ["ts-rs-macros/serde-compat"]
format = ["dprint-plugin-typescript"]
default = ["serde-compat"]
indexmap-impl = ["indexmap"]
ordered-float-impl = ["ordered-float"]

[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }

[dependencies]
ts-rs-macros = { version = "6.1.2", path = "../macros" }
ts-rs-macros = { version = "6.2.0", path = "../macros" }
dprint-plugin-typescript = { version = "0.43", optional = true }
chrono = { version = "0.4.19", optional = true }
bigdecimal = { version = ">=0.0.13, < 0.4.0", features = ["serde"], optional = true }
Expand All @@ -36,3 +37,4 @@ bson = { version = "2.2.0", optional = true, features = ["uuid-0_8"], default-fe
bytes = { version = "1.0", optional = true }
thiserror = "1"
indexmap = { version = "1.6.1", optional = true }
ordered-float = { version = "3.0.0", optional = true }
24 changes: 12 additions & 12 deletions ts-rs/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,25 @@ pub enum ExportError {
ManifestDirNotSet,
}

/// Export `T` to the file specified by the `#[ts(export = ..)]` attribute and/or the `out_dir`
/// setting in the `ts.toml` config file.
/// Export `T` to the file specified by the `#[ts(export_to = ..)]` attribute
pub(crate) fn export_type<T: TS + ?Sized>() -> Result<(), ExportError> {
let path = output_path::<T>()?;
export_type_to::<T, _>(&path)
}

/// Export `T` to the file specified by the `path` argument.
pub(crate) fn export_type_to<T: TS + ?Sized, P: AsRef<Path>>(path: P) -> Result<(), ExportError> {
let buffer = export_type_to_string::<T>()?;
#[allow(unused_mut)]
let mut buffer = export_type_to_string::<T>()?;

// format output
#[cfg(feature = "format")]
{
use dprint_plugin_typescript::{configuration::ConfigurationBuilder, format_text};

let fmt_cfg = ConfigurationBuilder::new().deno().build();
buffer = format_text(path.as_ref(), &buffer, &fmt_cfg).map_err(Formatting)?;
}

if let Some(parent) = path.as_ref().parent() {
std::fs::create_dir_all(parent)?;
Expand All @@ -50,15 +59,6 @@ pub(crate) fn export_type_to_string<T: TS + ?Sized>() -> Result<String, ExportEr
buffer.push_str(NOTE);
generate_imports::<T>(&mut buffer)?;
generate_decl::<T>(&mut buffer);
// format output
#[cfg(feature = "format")]
{
use dprint_plugin_typescript::{configuration::ConfigurationBuilder, format_text};

let fmt_cfg = ConfigurationBuilder::new().deno().build();
buffer = format_text(path.as_ref(), &buffer, &fmt_cfg).map_err(Formatting)?;
}

Ok(buffer)
}

Expand Down
16 changes: 15 additions & 1 deletion ts-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@
//!
//! Implement `TS` for `IndexMap` and `IndexSet` from indexmap
//!
//! - `ordered-float-impl`
//!
//! Implement `TS` for `OrderedFloat` from ordered_float
//!
//! If there's a type you're dealing with which doesn't implement `TS`, use `#[ts(type = "..")]` or open a PR.
//!
//! ## serde compatability
Expand Down Expand Up @@ -169,7 +173,8 @@ mod export;
/// `cargo test`
///
/// - `#[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 `bindings/<name>.ts`.
/// If the provided path ends in a trailing `/`, it is interpreted as a directory.
///
/// - `#[ts(rename = "..")]`:
/// Sets the typescript name of the generated type
Expand Down Expand Up @@ -282,6 +287,8 @@ pub trait TS: 'static {
export::export_type_to::<Self, _>(path)
}

/// Manually generate bindings for this type, returning a [`String`].
/// This function does not format the output, even if the `format` feature is enabled.
fn export_to_string() -> Result<String, ExportError> {
export::export_type_to_string::<Self>()
}
Expand Down Expand Up @@ -534,6 +541,7 @@ impl_wrapper!(impl<T: TS> TS for std::cell::Cell<T>);
impl_wrapper!(impl<T: TS> TS for std::cell::RefCell<T>);
impl_wrapper!(impl<T: TS> TS for std::sync::Mutex<T>);
impl_wrapper!(impl<T: TS> TS for std::sync::Weak<T>);
impl_wrapper!(impl<T: TS> TS for std::marker::PhantomData<T>);

impl_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);

Expand All @@ -543,6 +551,12 @@ impl_primitives! { bigdecimal::BigDecimal => "string" }
#[cfg(feature = "uuid-impl")]
impl_primitives! { uuid::Uuid => "string" }

#[cfg(feature = "ordered-float-impl")]
impl_primitives! { ordered_float::OrderedFloat<f32> => "number" }

#[cfg(feature = "ordered-float-impl")]
impl_primitives! { ordered_float::OrderedFloat<f64> => "number" }

#[cfg(feature = "bson-uuid-impl")]
impl_primitives! { bson::Uuid => "string" }

Expand Down
4 changes: 2 additions & 2 deletions ts-rs/tests/export_manually.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn export_manually() {

let expected_content = if cfg!(feature = "format") {
concat!(
"// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n",
"// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n\n",
"export interface User {\n",
" name: string;\n",
" age: number;\n",
Expand All @@ -51,7 +51,7 @@ fn export_manually_dir() {

let expected_content = if cfg!(feature = "format") {
concat!(
"// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n",
"// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n\n",
"export interface UserDir {\n",
" name: string;\n",
" age: number;\n",
Expand Down
2 changes: 1 addition & 1 deletion ts-rs/tests/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fn generic_struct() {

#[test]
#[ignore]
// https://github.com/Aleph-Alpha/ts-rs/issues/56
// https://github.com/Aleph-Alpha/ts-rs/issues/56 TODO
fn inline() {
#[derive(TS)]
struct Generic<T> {
Expand Down
22 changes: 11 additions & 11 deletions ts-rs/tests/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ 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 = String::from_utf8(std::fs::read(TestEnum::EXPORT_TO.unwrap()).unwrap()).unwrap();
let text = std::fs::read_to_string(TestEnum::EXPORT_TO.unwrap()).unwrap();

// Checks to make sure imports are ordered and deduplicated
assert_eq!(
text,
"\
import type { TestTypeA } from \"./ts_rs_test_type_a\";
import type { TestTypeB } from \"./ts_rs_test_type_b\";
export type TestEnum = { C: { value: TestTypeB<number> } } | {
A1: { value: TestTypeA<number> };
} | { A2: { value: TestTypeA<number> } };
"
assert_eq!(text,
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> } } | {\n",
" A1: { value: TestTypeA<number> };\n",
"} | { A2: { value: TestTypeA<number> } };\n"
)
);

std::fs::remove_file(TestEnum::EXPORT_TO.unwrap()).unwrap();
Expand Down
26 changes: 19 additions & 7 deletions ts-rs/tests/struct_rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@

use ts_rs::TS;

#[derive(TS)]
#[ts(rename_all = "UPPERCASE")]
struct Rename {
a: i32,
b: i32,
#[test]
fn rename_all() {
#[derive(TS)]
#[ts(rename_all = "UPPERCASE")]
struct Rename {
a: i32,
b: i32,
}

assert_eq!(Rename::inline(), "{ A: number, B: number, }");
}

#[cfg(feature = "serde-compat")]
#[test]
fn test() {
assert_eq!(Rename::inline(), "{ A: number, B: number, }")
fn serde_rename_special_char() {
#[derive(serde::Serialize, TS)]
struct RenameSerdeSpecialChar {
#[serde(rename = "a/b")]
b: i32,
}

assert_eq!(RenameSerdeSpecialChar::inline(), r#"{ "a/b": number, }"#);
}

0 comments on commit 9002a33

Please sign in to comment.