Skip to content

Commit 0b6b6f6

Browse files
feat: support for tuple structs
closes issue #46
1 parent a106fe0 commit 0b6b6f6

File tree

5 files changed

+113
-8
lines changed

5 files changed

+113
-8
lines changed

src/to_typescript/structs.rs

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ use convert_case::{Case, Casing};
44

55
impl super::ToTypescript for syn::ItemStruct {
66
fn convert_to_ts(self, state: &mut BuildState, config: &crate::BuildSettings) {
7-
let export = if config.uses_type_interface { "" } else { "export " };
7+
let export = if config.uses_type_interface {
8+
""
9+
} else {
10+
"export "
11+
};
812
let casing = utils::get_attribute_arg("serde", "rename_all", &self.attrs);
913
let casing = utils::parse_serde_case(casing);
1014
state.types.push('\n');
@@ -14,27 +18,53 @@ impl super::ToTypescript for syn::ItemStruct {
1418

1519
let intersections = get_intersections(&self.fields);
1620

17-
match intersections {
18-
Some(intersections) => {
21+
match (
22+
intersections,
23+
matches!(self.fields, syn::Fields::Unnamed(_)),
24+
) {
25+
(Some(intersections), false) => {
1926
state.types.push_str(&format!(
20-
"{export}type {struct_name}{generics} = {intersections} & {{\n",
27+
"{export}type {struct_name}{generics} = {intersections} & ",
2128
export = export,
2229
struct_name = self.ident,
2330
generics = utils::extract_struct_generics(self.generics.clone()),
2431
intersections = intersections
2532
));
2633
}
27-
None => {
34+
(None, false) => {
2835
state.types.push_str(&format!(
29-
"{export}interface {interface_name}{generics} {{\n",
36+
"{export}interface {interface_name}{generics} ",
3037
interface_name = self.ident,
3138
generics = utils::extract_struct_generics(self.generics.clone())
3239
));
3340
}
41+
(None, true) => {
42+
state.types.push_str(&format!(
43+
"{export}type {struct_name}{generics} = ",
44+
export = export,
45+
struct_name = self.ident,
46+
generics = utils::extract_struct_generics(self.generics.clone()),
47+
));
48+
}
49+
(Some(_), true) => {
50+
if crate::DEBUG.try_get().is_some_and(|d| *d) {
51+
println!(
52+
"#[tsync] failed for struct {}. cannot flatten fields of tuple struct",
53+
self.ident
54+
);
55+
}
56+
return;
57+
}
58+
}
59+
60+
if let syn::Fields::Unnamed(unnamed) = self.fields {
61+
process_tuple_fields(unnamed, state);
62+
} else {
63+
state.types.push_str("{\n");
64+
process_fields(self.fields, state, 2, casing);
65+
state.types.push('}');
3466
}
3567

36-
process_fields(self.fields, state, 2, casing);
37-
state.types.push('}');
3868
state.types.push('\n');
3969
}
4070
}
@@ -48,6 +78,11 @@ pub fn process_fields(
4878
let space = utils::build_indentation(indentation_amount);
4979
let case = case.into();
5080
for field in fields {
81+
debug_assert!(
82+
field.ident.is_some(),
83+
"struct fields should have names, found unnamed field"
84+
);
85+
5186
// Check if the field has the serde flatten attribute, if so, skip it
5287
let has_flatten_attr = utils::get_attribute_arg("serde", "flatten", &field.attrs).is_some();
5388
if has_flatten_attr {
@@ -77,6 +112,41 @@ pub fn process_fields(
77112
}
78113
}
79114

115+
/// Process tuple fields
116+
///
117+
/// NOTE: Currently, this function does not handle comments or attributes on tuple fields.
118+
///
119+
/// # Example
120+
///
121+
/// ```ignore
122+
/// struct Todo(String, u32);
123+
/// ```
124+
///
125+
/// should become
126+
///
127+
/// ```ignore
128+
/// type Todo = [string, number];
129+
/// ```
130+
pub(crate) fn process_tuple_fields(fields: syn::FieldsUnnamed, state: &mut BuildState) {
131+
let out = fields
132+
.unnamed
133+
.into_iter()
134+
.map(|field| {
135+
let field_type = convert_type(&field.ty);
136+
format!("{field_type}", field_type = field_type.ts_type)
137+
})
138+
.collect::<Vec<String>>();
139+
140+
if out.is_empty() {
141+
return;
142+
} else if out.len() == 1 {
143+
state.types.push_str(&format!("{}", out[0]));
144+
return;
145+
} else {
146+
state.types.push_str(&format!("[ {} ]", out.join(", ")));
147+
}
148+
}
149+
80150
fn get_intersections(fields: &syn::Fields) -> Option<String> {
81151
let mut types = Vec::new();
82152

test/issue-43/rust.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// #[tsync]
2+
// struct HasTuple1 {
3+
// foo: i32,
4+
// bar: Option<(String, i32)>,
5+
// }
6+
7+
// #[tsync]
8+
// struct HasTuple2 {
9+
// foo: i32,
10+
// bar: (String, i32),
11+
// }
12+
13+
#[tsync]
14+
struct IsTuple(i32, String);
15+
16+
#[tsync]
17+
struct IsTupleComplex(i32, String, (String, (i32, i32)));

test/issue-43/tsync.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
4+
5+
cd $SCRIPT_DIR
6+
7+
cargo run -- -i rust.rs -o typescript.d.ts
8+
cargo run -- -i rust.rs -o typescript.ts

test/issue-43/typescript.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* This file is generated and managed by tsync */
2+
3+
type IsTuple = [ number, string ]
4+
5+
type IsTupleComplex = [ number, string, unknown ]

test/issue-43/typescript.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* This file is generated and managed by tsync */
2+
3+
export type IsTuple = [ number, string ]
4+
5+
export type IsTupleComplex = [ number, string, unknown ]

0 commit comments

Comments
 (0)