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
11 changed files with 332 additions and 0 deletions

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