Skip to content

Commit 065a4b2

Browse files
authored
Merge pull request #228 from spastorino/wasm-bindgen-typescript
Add prototype of wasm-bindgen-typescript
2 parents a02f9e0 + fa8961e commit 065a4b2

11 files changed

+332
-0
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ wasm-bindgen-cli-support = { path = "crates/cli-support", version = '=0.2.11' }
3737
[workspace]
3838
members = [
3939
"crates/cli",
40+
"crates/typescript",
4041
"crates/webidl",
4142
"examples/hello_world",
4243
"examples/smorgasboard",

crates/typescript/Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "wasm-bindgen-typescript"
3+
version = "0.1.0"
4+
authors = ["Santiago Pastorino <[email protected]>"]
5+
6+
[dependencies]
7+
serde = "1.0"
8+
serde_derive = "1.0"
9+
serde_json = "1.0"
10+
11+
proc-macro2 = "0.4"
12+
quote = "0.6"
13+
syn = { version = "0.14", default-features = false }
14+
wasm-bindgen = { path = "../..", default-features = false }
15+
wasm-bindgen-backend = { path = "../backend", default-features = false }

crates/typescript/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# TypeScript file
2+
3+
Copy your TypeScript file over the one in `ts/index.d.ts`
4+
5+
# Run
6+
7+
```
8+
$ npm install -g @microsoft/api-extractor
9+
$ cargo run
10+
```

crates/typescript/api-extractor.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"$schema": "https://dev.office.com/json-schemas/api-extractor/api-extractor.schema.json",
3+
"compiler" : {
4+
"configType": "tsconfig",
5+
"rootFolder": "."
6+
},
7+
"project": {
8+
"entryPointSourceFile": "ts/index.d.ts"
9+
}
10+
}

crates/typescript/package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "wasm",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"directories": {
7+
"lib": "lib",
8+
"test": "test"
9+
},
10+
"dependencies": {
11+
"@microsoft/api-extractor": "^5.6.3"
12+
},
13+
"devDependencies": {},
14+
"scripts": {
15+
"test": "echo \"Error: no test specified\" && exit 1"
16+
},
17+
"keywords": [],
18+
"author": "",
19+
"license": "ISC"
20+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use std::process::Command;
2+
3+
pub(crate) fn run() {
4+
let output = Command::new("api-extractor")
5+
.arg("run")
6+
.output()
7+
.expect("api-extractor not installed?");
8+
9+
let out = if output.status.success() {
10+
output.stdout
11+
} else {
12+
output.stderr
13+
};
14+
15+
print!("{}", String::from_utf8_lossy(out.as_slice()));
16+
}

crates/typescript/src/definitions.rs

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use std::collections::HashMap;
2+
3+
// Public API types for a TypeScript project based on
4+
// https://github.com/Microsoft/web-build-tools/blob/master/apps/api-extractor/src/api/api-json.schema.json
5+
//
6+
// There are some attributes that are omitted because they are not relevant to
7+
// us.
8+
#[derive(Serialize, Deserialize, Debug)]
9+
pub(crate) struct TsPackage {
10+
kind: String,
11+
name: String,
12+
pub(crate) exports: HashMap<String, TsExport>,
13+
}
14+
15+
#[derive(Serialize, Deserialize, Debug)]
16+
#[serde(tag = "kind")]
17+
pub(crate) enum TsExport {
18+
#[serde(rename = "class")]
19+
TsClass {
20+
members: HashMap<String, TsClassMembers>,
21+
},
22+
23+
#[serde(rename = "function")]
24+
TsFunction {
25+
parameters: HashMap<String, TsMethodProperty>,
26+
#[serde(rename = "returnValue")]
27+
return_value: TsReturnValue,
28+
},
29+
30+
//TODO: implement ...
31+
//{ "$ref": "#/definitions/interfaceApiItem" },
32+
//{ "$ref": "#/definitions/namespaceApiItem" },
33+
//{ "$ref": "#/definitions/enumApiItem" },
34+
}
35+
36+
#[derive(Serialize, Deserialize, Debug)]
37+
#[serde(tag = "kind")]
38+
pub(crate) enum TsClassMembers {
39+
#[serde(rename = "property")]
40+
TsProperty {
41+
#[serde(rename = "isStatic")]
42+
is_static: bool,
43+
#[serde(rename = "isReadOnly")]
44+
is_read_only: bool,
45+
#[serde(rename = "type")]
46+
property_type: String,
47+
},
48+
49+
#[serde(rename = "constructor")]
50+
TsConstructor {
51+
parameters: HashMap<String, TsMethodProperty>,
52+
},
53+
54+
#[serde(rename = "method")]
55+
TsMethod {
56+
#[serde(rename = "accessModifier")]
57+
access_modifier: String,
58+
#[serde(rename = "isStatic")]
59+
is_static: bool,
60+
parameters: HashMap<String, TsMethodProperty>,
61+
#[serde(rename = "returnValue")]
62+
return_value: TsReturnValue,
63+
},
64+
}
65+
66+
#[derive(Serialize, Deserialize, Debug)]
67+
pub(crate) struct TsMethodProperty {
68+
name: String,
69+
#[serde(rename = "type")]
70+
pub(crate) property_type: String,
71+
#[serde(rename = "isSpread")]
72+
is_spread: bool,
73+
#[serde(rename = "isOptional")]
74+
is_optional: bool,
75+
}
76+
77+
#[derive(Serialize, Deserialize, Debug)]
78+
pub(crate) struct TsReturnValue {
79+
#[serde(rename = "type")]
80+
pub(crate) property_type: String,
81+
}

crates/typescript/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
extern crate proc_macro2;
2+
extern crate serde;
3+
#[macro_use] extern crate serde_derive;
4+
extern crate serde_json;
5+
extern crate syn;
6+
extern crate wasm_bindgen_backend as backend;
7+
8+
pub mod api_extractor;
9+
pub mod definitions;
10+
pub mod parser;

crates/typescript/src/main.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
extern crate proc_macro2;
2+
extern crate quote;
3+
extern crate wasm_bindgen_typescript;
4+
5+
use wasm_bindgen_typescript::parser;
6+
7+
use proc_macro2::TokenStream;
8+
use quote::ToTokens;
9+
10+
fn main() {
11+
let program = parser::ts_to_program("dist/wasm.api.json");
12+
13+
let mut tokens = TokenStream::new();
14+
program.to_tokens(&mut tokens);
15+
println!("{}", tokens);
16+
}

crates/typescript/src/parser.rs

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
use std::collections::HashMap;
2+
use std::fs::File;
3+
use std::io::Read;
4+
5+
use backend::{self};
6+
use backend::ast::{BindgenAttrs, Export, Function};
7+
use proc_macro2::{self};
8+
use serde_json::{self};
9+
use syn::{self};
10+
11+
use api_extractor;
12+
use definitions::*;
13+
14+
pub fn ts_to_program(file_name: &str) -> backend::ast::Program {
15+
api_extractor::run();
16+
17+
let ts_package = parse_json(file_name);
18+
19+
let mut program = backend::ast::Program::default();
20+
21+
for (name, export) in ts_package.exports {
22+
match export {
23+
TsExport::TsClass { members } => {
24+
for (member_name, member) in members {
25+
match member {
26+
TsClassMembers::TsProperty { .. } => {}
27+
TsClassMembers::TsConstructor { .. } => {}
28+
TsClassMembers::TsMethod { parameters, return_value, .. } => {
29+
let function = build_function(member_name, parameters, return_value);
30+
31+
program.exports.push(Export {
32+
class: Some(syn::Ident::new(&name, proc_macro2::Span::call_site())),
33+
method: true,
34+
mutable: false,
35+
constructor: None,
36+
function,
37+
});
38+
}
39+
}
40+
}
41+
}
42+
43+
TsExport::TsFunction { parameters, return_value } => {
44+
let function = build_function(name, parameters, return_value);
45+
46+
program.exports.push(Export {
47+
class: None,
48+
method: false,
49+
mutable: false,
50+
constructor: None,
51+
function,
52+
});
53+
}
54+
}
55+
}
56+
57+
program
58+
}
59+
60+
fn parse_json(file_name: &str) -> TsPackage {
61+
let mut file = File::open(file_name).unwrap();
62+
let mut data = String::new();
63+
file.read_to_string(&mut data).unwrap();
64+
65+
serde_json::from_str(&data).unwrap()
66+
}
67+
68+
fn build_function(name: String, parameters: HashMap<String, TsMethodProperty>, return_value: TsReturnValue) -> Function {
69+
let arguments = parameters.iter().map( |(_name, property)| {
70+
let property_type = rust_type(&property.property_type);
71+
72+
let mut segments = syn::punctuated::Punctuated::new();
73+
segments.push(syn::PathSegment {
74+
ident: syn::Ident::new(property_type, proc_macro2::Span::call_site()),
75+
arguments: syn::PathArguments::None,
76+
});
77+
78+
syn::Type::Path(syn::TypePath {
79+
qself: None,
80+
path: syn::Path {
81+
leading_colon: None,
82+
segments,
83+
}
84+
})
85+
}).collect::<Vec<_>>();
86+
87+
let ret_property_type = rust_type(&return_value.property_type);
88+
89+
let mut ret_segments = syn::punctuated::Punctuated::new();
90+
ret_segments.push(syn::PathSegment {
91+
ident: syn::Ident::new(ret_property_type, proc_macro2::Span::call_site()),
92+
arguments: syn::PathArguments::None,
93+
});
94+
95+
let ret = syn::Type::Path(syn::TypePath {
96+
qself: None,
97+
path: syn::Path {
98+
leading_colon: None,
99+
segments: ret_segments,
100+
}
101+
});
102+
103+
let rust_decl = Box::new(syn::FnDecl {
104+
fn_token: Default::default(),
105+
generics: Default::default(),
106+
paren_token: Default::default(),
107+
//TODO investigate if inputs should be taken from arguments
108+
inputs: Default::default(),
109+
variadic: None,
110+
output: syn::ReturnType::Type(Default::default(), Box::new(ret.clone())),
111+
});
112+
113+
Function {
114+
name: syn::Ident::new(&name, proc_macro2::Span::call_site()),
115+
arguments,
116+
ret: Some(ret),
117+
opts: BindgenAttrs::default(),
118+
rust_attrs: Vec::new(),
119+
rust_decl,
120+
rust_vis: syn::Visibility::Public(syn::VisPublic {
121+
pub_token: Default::default(),
122+
}),
123+
}
124+
}
125+
126+
// TODO: implement this properly
127+
fn rust_type(js_type: &str) -> &'static str {
128+
match js_type {
129+
"string" => "String",
130+
"number" => "String",
131+
"boolean" => "bool",
132+
"symbol" => "String",
133+
"object" => "String",
134+
"function" => "String",
135+
"void" => "String",
136+
_ => "String",
137+
}
138+
}

crates/typescript/tsconfig.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"version": "2.4.2",
3+
"compilerOptions": {
4+
"lib": ["es5", "es6"],
5+
"target": "es6",
6+
"module": "commonjs",
7+
"moduleResolution": "node",
8+
"emitDecoratorMetadata": true,
9+
"experimentalDecorators": true,
10+
"sourceMap": true
11+
},
12+
"exclude": [
13+
"node_modules"
14+
]
15+
}

0 commit comments

Comments
 (0)