mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-05-30 14:11:21 +00:00
Add prototype of wasm-bindgen-typescript
This commit is contained in:
parent
a02f9e0eed
commit
fa8961e56a
@ -37,6 +37,7 @@ wasm-bindgen-cli-support = { path = "crates/cli-support", version = '=0.2.11' }
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/cli",
|
"crates/cli",
|
||||||
|
"crates/typescript",
|
||||||
"crates/webidl",
|
"crates/webidl",
|
||||||
"examples/hello_world",
|
"examples/hello_world",
|
||||||
"examples/smorgasboard",
|
"examples/smorgasboard",
|
||||||
|
15
crates/typescript/Cargo.toml
Normal file
15
crates/typescript/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "wasm-bindgen-typescript"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Santiago Pastorino <spastorino@gmail.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
proc-macro2 = "0.4"
|
||||||
|
quote = "0.6"
|
||||||
|
syn = { version = "0.14", default-features = false }
|
||||||
|
wasm-bindgen = { path = "../..", default-features = false }
|
||||||
|
wasm-bindgen-backend = { path = "../backend", default-features = false }
|
10
crates/typescript/README.md
Normal file
10
crates/typescript/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# TypeScript file
|
||||||
|
|
||||||
|
Copy your TypeScript file over the one in `ts/index.d.ts`
|
||||||
|
|
||||||
|
# Run
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm install -g @microsoft/api-extractor
|
||||||
|
$ cargo run
|
||||||
|
```
|
10
crates/typescript/api-extractor.json
Normal file
10
crates/typescript/api-extractor.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://dev.office.com/json-schemas/api-extractor/api-extractor.schema.json",
|
||||||
|
"compiler" : {
|
||||||
|
"configType": "tsconfig",
|
||||||
|
"rootFolder": "."
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"entryPointSourceFile": "ts/index.d.ts"
|
||||||
|
}
|
||||||
|
}
|
20
crates/typescript/package.json
Normal file
20
crates/typescript/package.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "wasm",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib",
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/api-extractor": "^5.6.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
16
crates/typescript/src/api_extractor.rs
Normal file
16
crates/typescript/src/api_extractor.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub(crate) fn run() {
|
||||||
|
let output = Command::new("api-extractor")
|
||||||
|
.arg("run")
|
||||||
|
.output()
|
||||||
|
.expect("api-extractor not installed?");
|
||||||
|
|
||||||
|
let out = if output.status.success() {
|
||||||
|
output.stdout
|
||||||
|
} else {
|
||||||
|
output.stderr
|
||||||
|
};
|
||||||
|
|
||||||
|
print!("{}", String::from_utf8_lossy(out.as_slice()));
|
||||||
|
}
|
81
crates/typescript/src/definitions.rs
Normal file
81
crates/typescript/src/definitions.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// Public API types for a TypeScript project based on
|
||||||
|
// https://github.com/Microsoft/web-build-tools/blob/master/apps/api-extractor/src/api/api-json.schema.json
|
||||||
|
//
|
||||||
|
// There are some attributes that are omitted because they are not relevant to
|
||||||
|
// us.
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub(crate) struct TsPackage {
|
||||||
|
kind: String,
|
||||||
|
name: String,
|
||||||
|
pub(crate) exports: HashMap<String, TsExport>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(tag = "kind")]
|
||||||
|
pub(crate) enum TsExport {
|
||||||
|
#[serde(rename = "class")]
|
||||||
|
TsClass {
|
||||||
|
members: HashMap<String, TsClassMembers>,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[serde(rename = "function")]
|
||||||
|
TsFunction {
|
||||||
|
parameters: HashMap<String, TsMethodProperty>,
|
||||||
|
#[serde(rename = "returnValue")]
|
||||||
|
return_value: TsReturnValue,
|
||||||
|
},
|
||||||
|
|
||||||
|
//TODO: implement ...
|
||||||
|
//{ "$ref": "#/definitions/interfaceApiItem" },
|
||||||
|
//{ "$ref": "#/definitions/namespaceApiItem" },
|
||||||
|
//{ "$ref": "#/definitions/enumApiItem" },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(tag = "kind")]
|
||||||
|
pub(crate) enum TsClassMembers {
|
||||||
|
#[serde(rename = "property")]
|
||||||
|
TsProperty {
|
||||||
|
#[serde(rename = "isStatic")]
|
||||||
|
is_static: bool,
|
||||||
|
#[serde(rename = "isReadOnly")]
|
||||||
|
is_read_only: bool,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
property_type: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[serde(rename = "constructor")]
|
||||||
|
TsConstructor {
|
||||||
|
parameters: HashMap<String, TsMethodProperty>,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[serde(rename = "method")]
|
||||||
|
TsMethod {
|
||||||
|
#[serde(rename = "accessModifier")]
|
||||||
|
access_modifier: String,
|
||||||
|
#[serde(rename = "isStatic")]
|
||||||
|
is_static: bool,
|
||||||
|
parameters: HashMap<String, TsMethodProperty>,
|
||||||
|
#[serde(rename = "returnValue")]
|
||||||
|
return_value: TsReturnValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub(crate) struct TsMethodProperty {
|
||||||
|
name: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) property_type: String,
|
||||||
|
#[serde(rename = "isSpread")]
|
||||||
|
is_spread: bool,
|
||||||
|
#[serde(rename = "isOptional")]
|
||||||
|
is_optional: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub(crate) struct TsReturnValue {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) property_type: String,
|
||||||
|
}
|
10
crates/typescript/src/lib.rs
Normal file
10
crates/typescript/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
extern crate proc_macro2;
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate syn;
|
||||||
|
extern crate wasm_bindgen_backend as backend;
|
||||||
|
|
||||||
|
pub mod api_extractor;
|
||||||
|
pub mod definitions;
|
||||||
|
pub mod parser;
|
16
crates/typescript/src/main.rs
Normal file
16
crates/typescript/src/main.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
extern crate proc_macro2;
|
||||||
|
extern crate quote;
|
||||||
|
extern crate wasm_bindgen_typescript;
|
||||||
|
|
||||||
|
use wasm_bindgen_typescript::parser;
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::ToTokens;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let program = parser::ts_to_program("dist/wasm.api.json");
|
||||||
|
|
||||||
|
let mut tokens = TokenStream::new();
|
||||||
|
program.to_tokens(&mut tokens);
|
||||||
|
println!("{}", tokens);
|
||||||
|
}
|
138
crates/typescript/src/parser.rs
Normal file
138
crates/typescript/src/parser.rs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use backend::{self};
|
||||||
|
use backend::ast::{BindgenAttrs, Export, Function};
|
||||||
|
use proc_macro2::{self};
|
||||||
|
use serde_json::{self};
|
||||||
|
use syn::{self};
|
||||||
|
|
||||||
|
use api_extractor;
|
||||||
|
use definitions::*;
|
||||||
|
|
||||||
|
pub fn ts_to_program(file_name: &str) -> backend::ast::Program {
|
||||||
|
api_extractor::run();
|
||||||
|
|
||||||
|
let ts_package = parse_json(file_name);
|
||||||
|
|
||||||
|
let mut program = backend::ast::Program::default();
|
||||||
|
|
||||||
|
for (name, export) in ts_package.exports {
|
||||||
|
match export {
|
||||||
|
TsExport::TsClass { members } => {
|
||||||
|
for (member_name, member) in members {
|
||||||
|
match member {
|
||||||
|
TsClassMembers::TsProperty { .. } => {}
|
||||||
|
TsClassMembers::TsConstructor { .. } => {}
|
||||||
|
TsClassMembers::TsMethod { parameters, return_value, .. } => {
|
||||||
|
let function = build_function(member_name, parameters, return_value);
|
||||||
|
|
||||||
|
program.exports.push(Export {
|
||||||
|
class: Some(syn::Ident::new(&name, proc_macro2::Span::call_site())),
|
||||||
|
method: true,
|
||||||
|
mutable: false,
|
||||||
|
constructor: None,
|
||||||
|
function,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TsExport::TsFunction { parameters, return_value } => {
|
||||||
|
let function = build_function(name, parameters, return_value);
|
||||||
|
|
||||||
|
program.exports.push(Export {
|
||||||
|
class: None,
|
||||||
|
method: false,
|
||||||
|
mutable: false,
|
||||||
|
constructor: None,
|
||||||
|
function,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
program
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_json(file_name: &str) -> TsPackage {
|
||||||
|
let mut file = File::open(file_name).unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
file.read_to_string(&mut data).unwrap();
|
||||||
|
|
||||||
|
serde_json::from_str(&data).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_function(name: String, parameters: HashMap<String, TsMethodProperty>, return_value: TsReturnValue) -> Function {
|
||||||
|
let arguments = parameters.iter().map( |(_name, property)| {
|
||||||
|
let property_type = rust_type(&property.property_type);
|
||||||
|
|
||||||
|
let mut segments = syn::punctuated::Punctuated::new();
|
||||||
|
segments.push(syn::PathSegment {
|
||||||
|
ident: syn::Ident::new(property_type, proc_macro2::Span::call_site()),
|
||||||
|
arguments: syn::PathArguments::None,
|
||||||
|
});
|
||||||
|
|
||||||
|
syn::Type::Path(syn::TypePath {
|
||||||
|
qself: None,
|
||||||
|
path: syn::Path {
|
||||||
|
leading_colon: None,
|
||||||
|
segments,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let ret_property_type = rust_type(&return_value.property_type);
|
||||||
|
|
||||||
|
let mut ret_segments = syn::punctuated::Punctuated::new();
|
||||||
|
ret_segments.push(syn::PathSegment {
|
||||||
|
ident: syn::Ident::new(ret_property_type, proc_macro2::Span::call_site()),
|
||||||
|
arguments: syn::PathArguments::None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let ret = syn::Type::Path(syn::TypePath {
|
||||||
|
qself: None,
|
||||||
|
path: syn::Path {
|
||||||
|
leading_colon: None,
|
||||||
|
segments: ret_segments,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let rust_decl = Box::new(syn::FnDecl {
|
||||||
|
fn_token: Default::default(),
|
||||||
|
generics: Default::default(),
|
||||||
|
paren_token: Default::default(),
|
||||||
|
//TODO investigate if inputs should be taken from arguments
|
||||||
|
inputs: Default::default(),
|
||||||
|
variadic: None,
|
||||||
|
output: syn::ReturnType::Type(Default::default(), Box::new(ret.clone())),
|
||||||
|
});
|
||||||
|
|
||||||
|
Function {
|
||||||
|
name: syn::Ident::new(&name, proc_macro2::Span::call_site()),
|
||||||
|
arguments,
|
||||||
|
ret: Some(ret),
|
||||||
|
opts: BindgenAttrs::default(),
|
||||||
|
rust_attrs: Vec::new(),
|
||||||
|
rust_decl,
|
||||||
|
rust_vis: syn::Visibility::Public(syn::VisPublic {
|
||||||
|
pub_token: Default::default(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement this properly
|
||||||
|
fn rust_type(js_type: &str) -> &'static str {
|
||||||
|
match js_type {
|
||||||
|
"string" => "String",
|
||||||
|
"number" => "String",
|
||||||
|
"boolean" => "bool",
|
||||||
|
"symbol" => "String",
|
||||||
|
"object" => "String",
|
||||||
|
"function" => "String",
|
||||||
|
"void" => "String",
|
||||||
|
_ => "String",
|
||||||
|
}
|
||||||
|
}
|
15
crates/typescript/tsconfig.json
Normal file
15
crates/typescript/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "2.4.2",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es5", "es6"],
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user