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

Add support for typescript bit-int types #112

Closed
wants to merge 4 commits into from
Closed
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
1 change: 1 addition & 0 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub struct SwiftParams {
#[serde(default)]
pub struct TypeScriptParams {
pub type_mappings: HashMap<String, String>,
pub unsafe_types: bool,
}

#[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
Expand Down
18 changes: 18 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const ARG_SCALA_MODULE_NAME: &str = "SCALAMODULENAME";
#[cfg(feature = "go")]
const ARG_GO_PACKAGE: &str = "GOPACKAGE";
const ARG_CONFIG_FILE_NAME: &str = "CONFIGFILENAME";
const ARG_TYPESCRIPT_UNSAFE_TYPES: &str = "TYPESCRIPTUNSAFE";
const ARG_GENERATE_CONFIG: &str = "generate-config-file";
const ARG_OUTPUT_FILE: &str = "output-file";

Expand Down Expand Up @@ -78,6 +79,14 @@ fn build_command() -> Command<'static> {
.takes_value(true)
.required(false),
)
.arg(
Arg::new(ARG_TYPESCRIPT_UNSAFE_TYPES)
.short('u')
.long("unsafe-types")
.help("Generate unsafe (64bit) types for TypeScript (true/false). Default false.")
.takes_value(true)
.required(false),
)
.arg(
Arg::new(ARG_MODULE_NAME)
.short('m')
Expand Down Expand Up @@ -200,6 +209,7 @@ fn main() {
}),
Some(SupportedLanguage::TypeScript) => Box::new(TypeScript {
type_mappings: config.typescript.type_mappings,
unsafe_types: config.typescript.unsafe_types,
..Default::default()
}),
#[cfg(feature = "go")]
Expand Down Expand Up @@ -298,6 +308,14 @@ fn override_configuration(mut config: Config, options: &ArgMatches) -> Config {
config.swift.prefix = swift_prefix.to_string();
}

if let Some(typescript_unsafe) = options.value_of(ARG_TYPESCRIPT_UNSAFE_TYPES) {
config.typescript.unsafe_types = match typescript_unsafe {
"true" => true,
"false" => false,
_ => false,
}
}

if let Some(java_package) = options.value_of(ARG_JAVA_PACKAGE) {
config.kotlin.package = java_package.to_string();
}
Expand Down
18 changes: 17 additions & 1 deletion core/src/language/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub struct TypeScript {
/// Whether or not to exclude the version header that normally appears at the top of generated code.
/// If you aren't generating a snapshot test, this setting can just be left as a default (false)
pub no_version_header: bool,
/// Whether or not to print unsafe types in typescript.
pub unsafe_types: bool,
}

impl Language for TypeScript {
Expand Down Expand Up @@ -69,7 +71,21 @@ impl Language for TypeScript {
| SpecialRustType::I64
| SpecialRustType::ISize
| SpecialRustType::USize => {
panic!("64 bit types not allowed in Typeshare")
if self.unsafe_types {
println!(
r#"WARNING:
64bit types are not supported by the default JSON lib for JS/TS! This will lead to memory safety issues when passing data between JS/TS and Rust.
If your intended encpoint is for JSON parsing/serializing between JS/TS & Rust, please consider using a custom JSON parser that supports 64 bit static types.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo - encpoint should be endpoint I believe?

An open-source option is available with superjson
`https://github.com/blitz-js/superjson`
`https://www.npmjs.com/package/superjson`"#,
);
Ok("bigint".into())
} else {
Err(RustTypeFormatError::GenericsForbiddenInTS(
special_ty.id().clone().to_string(),
))
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion core/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
rename::RenameExt,
rust_types::{
Id, RustEnum, RustEnumShared, RustEnumVariant, RustEnumVariantShared, RustField, RustItem,
RustStruct, RustType, RustTypeAlias, RustTypeParseError,
RustStruct, RustType, RustTypeAlias, RustTypeFormatError, RustTypeParseError,
},
};
use proc_macro2::{Ident, Span};
Expand Down Expand Up @@ -61,6 +61,8 @@ pub enum ParseError {
UnsupportedLanguage(String),
#[error("unsupported type encountered: {0}")]
UnsupportedType(String),
#[error("failed to format a rust type: {0}")]
RustTypeFormatError(#[from] RustTypeFormatError),
#[error("tuple structs with more than one field are currently unsupported")]
ComplexTupleStruct,
#[error("multiple unnamed associated types are not currently supported")]
Expand Down
9 changes: 6 additions & 3 deletions core/src/rust_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,10 @@ impl TryFrom<&syn::Type> for RustType {
"u16" => Self::Special(SpecialRustType::U16),
"u32" => Self::Special(SpecialRustType::U32),
"U53" => Self::Special(SpecialRustType::U53),
"u64" | "i64" | "usize" | "isize" => {
return Err(RustTypeParseError::UnsupportedType(vec![id]))
}
"u64" => RustType::Special(SpecialRustType::U64),
"usize" => RustType::Special(SpecialRustType::USize),
"i64" => RustType::Special(SpecialRustType::I64),
"isize" => RustType::Special(SpecialRustType::ISize),
"i8" => Self::Special(SpecialRustType::I8),
"i16" => Self::Special(SpecialRustType::I16),
"i32" => Self::Special(SpecialRustType::I32),
Expand Down Expand Up @@ -335,6 +336,8 @@ pub enum RustTypeFormatError {
GenericsForbiddenInGo(String),
#[error("Generic type `{0}` cannot be used as a map key in Typescript")]
GenericKeyForbiddenInTS(String),
#[error("Generic type `{0}` cannot be used in Typescript")]
GenericsForbiddenInTS(String),
}

impl SpecialRustType {
Expand Down
101 changes: 86 additions & 15 deletions core/tests/agnostic_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use typeshare_core::{
language::TypeScript, parser::ParseError, process_input, rust_types::RustTypeParseError,
language::TypeScript, parser::ParseError, process_input, rust_types::RustTypeFormatError,
ProcessInputError,
};

Expand All @@ -20,46 +20,117 @@ mod blocklisted_types {

let mut out: Vec<u8> = Vec::new();
assert!(matches!(
process_input(&source, &mut TypeScript::default(), &mut out),
process_input(&source, &mut (TypeScript {
unsafe_types: false,
..Default::default()
}), &mut out),
Err(ProcessInputError::ParseError(
ParseError::RustTypeParseError(RustTypeParseError::UnsupportedType(contents))
)) if contents == vec![blocklisted_type.to_owned()]
));
ParseError::RustTypeFormatError(RustTypeFormatError::GenericsForbiddenInTS(contents))
)) if contents == blocklisted_type.to_owned()
))
}

fn assert_type_is_unblocklisted(ty: &str) {
let source = format!(
r##"
#[typeshare]
#[serde(default, rename_all = "camelCase")]
pub struct Foo {{
pub bar: {ty},
}}
"##,
ty = ty
);

let mut out: Vec<u8> = Vec::new();
assert!(matches!(
process_input(
&source,
&mut (TypeScript {
unsafe_types: true,
..Default::default()
}),
&mut out
),
Ok(contents) if contents == ()
))
}

/// Right now the support for testing "Warning" messages
/// written to stdout is poor. Possible solutions exist if you
/// pass in a "writer" from the beginning. However, I worked around
/// this by passing an option that defaults to false.

#[test]
fn test_i64_blocklisted_struct() {
assert_type_is_blocklisted("i64", "i64");
assert_type_is_blocklisted("i64", "i64")
}

#[test]
fn test_u64_blocklisted_struct() {
assert_type_is_blocklisted("u64", "u64");
assert_type_is_blocklisted("u64", "u64")
}

#[test]
fn test_isize_blocklisted_struct() {
assert_type_is_blocklisted("isize", "isize");
assert_type_is_blocklisted("isize", "isize")
}

#[test]
fn test_usize_blocklisted_in_struct() {
assert_type_is_blocklisted("usize", "usize");
assert_type_is_blocklisted("usize", "usize")
}

#[test]
fn test_optional_blocklisted_struct() {
assert_type_is_blocklisted("Option<i64>", "i64");
assert_type_is_blocklisted("Option<i64>", "i64")
}

#[test]
fn test_vec_blocklisted_struct() {
assert_type_is_blocklisted("Vec<i64>", "i64");
assert_type_is_blocklisted("Vec<i64>", "i64")
}

#[test]
fn test_hashmap_blocklisted_struct() {
assert_type_is_blocklisted("HashMap<String, i64>", "i64");
assert_type_is_blocklisted("HashMap<String, i64>", "i64")
}

/// Unblocklisted types

#[test]
fn test_i64_unblocklisted_struct() {
assert_type_is_unblocklisted("i64")
}

#[test]
fn test_u64_unblocklisted_struct() {
assert_type_is_unblocklisted("u64")
}

#[test]
fn test_isize_unblocklisted_struct() {
assert_type_is_unblocklisted("isize")
}

#[test]
fn test_usize_unblocklisted_in_struct() {
assert_type_is_unblocklisted("usize")
}

#[test]
fn test_optional_unblocklisted_struct() {
assert_type_is_unblocklisted("Option<i64>")
}

#[test]
fn test_vec_unblocklisted_struct() {
assert_type_is_unblocklisted("Vec<i64>")
}

#[test]
fn test_hashmap_unblocklisted_struct() {
assert_type_is_unblocklisted("HashMap<String, i64>")
}
}

Expand All @@ -81,7 +152,7 @@ mod serde_attributes_on_enums {
assert!(matches!(
process_input(source, &mut TypeScript::default(), &mut out).unwrap_err(),
ProcessInputError::ParseError(ParseError::SerdeContentNotAllowed { enum_ident }) if enum_ident == "Foo"
));
))
}

#[test]
Expand All @@ -99,7 +170,7 @@ mod serde_attributes_on_enums {
assert!(matches!(
process_input(source, &mut TypeScript::default(), &mut out).unwrap_err(),
ProcessInputError::ParseError(ParseError::SerdeTagNotAllowed { enum_ident }) if enum_ident == "Foo"
));
))
}

#[test]
Expand All @@ -117,6 +188,6 @@ mod serde_attributes_on_enums {
assert!(matches!(
process_input(source, &mut TypeScript::default(), &mut out).unwrap_err(),
ProcessInputError::ParseError(ParseError::SerdeTagNotAllowed { enum_ident }) if enum_ident == "Foo"
));
))
}
}