Add prototype of wasm-bindgen-typescript

This commit is contained in:
Santiago Pastorino 2018-06-04 16:44:47 -03:00
parent a02f9e0eed
commit fa8961e56a
No known key found for this signature in database
GPG Key ID: 88C941CDA1D46432
11 changed files with 332 additions and 0 deletions

View File

@ -37,6 +37,7 @@ wasm-bindgen-cli-support = { path = "crates/cli-support", version = '=0.2.11' }
[workspace]
members = [
"crates/cli",
"crates/typescript",
"crates/webidl",
"examples/hello_world",
"examples/smorgasboard",

View 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 }

View 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
```

View 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"
}
}

View 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"
}

View 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()));
}

View 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,
}

View 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;

View 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);
}

View 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",
}
}

View 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"
]
}