diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 60d7bc8fd..f62a3ebd5 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -2,6 +2,7 @@ use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::ToTokens; use shared; use syn; +use util; #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Default)] @@ -412,6 +413,11 @@ impl Program { ty: class.clone(), kind: MethodKind::Normal, } + } else if let Some(cls) = wasm.opts.static_method_of() { + let class = cls.to_string(); + let kind = MethodKind::Static; + let ty = util::ident_ty(cls.clone()); + ImportFunctionKind::Method { class, ty, kind } } else if wasm.opts.constructor() { let class = match wasm.ret { Some(ref ty) => ty, @@ -794,7 +800,7 @@ impl Struct { ty: field.ty.clone(), getter: Ident::new(&getter, Span::call_site()), setter: Ident::new(&setter, Span::call_site()), - comments + comments, }); } } @@ -888,6 +894,16 @@ impl BindgenAttrs { }) } + fn static_method_of(&self) -> Option<&Ident> { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::StaticMethodOf(c) => Some(c), + _ => None, + }) + .next() + } + fn method(&self) -> bool { self.attrs.iter().any(|a| match a { BindgenAttr::Method => true, @@ -980,6 +996,7 @@ pub enum BindgenAttr { Catch, Constructor, Method, + StaticMethodOf(Ident), JsNamespace(Ident), Module(String), Version(String), @@ -999,6 +1016,13 @@ impl syn::synom::Synom for BindgenAttr { | call!(term, "method") => { |_| BindgenAttr::Method } | + do_parse!( + call!(term, "static_method_of") >> + punct!(=) >> + cls: call!(term2ident) >> + (cls) + )=> { BindgenAttr::StaticMethodOf } + | do_parse!( call!(term, "getter") >> val: option!(do_parse!( diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index 19ad1957c..60a513daf 100755 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -12,3 +12,4 @@ extern crate wasm_bindgen_shared as shared; pub mod ast; mod codegen; +pub mod util; diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs new file mode 100644 index 000000000..8773c4abf --- /dev/null +++ b/crates/backend/src/util.rs @@ -0,0 +1,69 @@ +use std::iter::FromIterator; + +use ast; +use proc_macro2::{self, Ident}; +use syn; + +fn is_rust_keyword(name: &str) -> bool { + match name { + "abstract" | "alignof" | "as" | "become" | "box" | "break" | "const" | "continue" + | "crate" | "do" | "else" | "enum" | "extern" | "false" | "final" | "fn" | "for" | "if" + | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" | "move" | "mut" + | "offsetof" | "override" | "priv" | "proc" | "pub" | "pure" | "ref" | "return" + | "Self" | "self" | "sizeof" | "static" | "struct" | "super" | "trait" | "true" + | "type" | "typeof" | "unsafe" | "unsized" | "use" | "virtual" | "where" | "while" + | "yield" | "bool" | "_" => true, + _ => false, + } +} + +// Create an `Ident`, possibly mangling it if it conflicts with a Rust keyword. +pub fn rust_ident(name: &str) -> Ident { + if is_rust_keyword(name) { + Ident::new(&format!("{}_", name), proc_macro2::Span::call_site()) + } else { + raw_ident(name) + } +} + +// Create an `Ident` without checking to see if it conflicts with a Rust +// keyword. +pub fn raw_ident(name: &str) -> Ident { + Ident::new(name, proc_macro2::Span::call_site()) +} + +/// Create a path type from the given segments. For example an iterator yielding +/// the idents `[foo, bar, baz]` will result in the path type `foo::bar::baz`. +pub fn simple_path_ty(segments: I) -> syn::Type +where + I: IntoIterator, +{ + let segments: Vec<_> = segments + .into_iter() + .map(|i| syn::PathSegment { + ident: i, + arguments: syn::PathArguments::None, + }) + .collect(); + + syn::TypePath { + qself: None, + path: syn::Path { + leading_colon: None, + segments: syn::punctuated::Punctuated::from_iter(segments), + }, + }.into() +} + +pub fn ident_ty(ident: Ident) -> syn::Type { + simple_path_ty(Some(ident)) +} + +pub fn wrap_import_function(function: ast::ImportFunction) -> ast::Import { + ast::Import { + module: None, + version: None, + js_namespace: None, + kind: ast::ImportKind::Function(function), + } +} diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index c024cd93c..950207eb5 100755 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -18,19 +18,19 @@ extern crate syn; extern crate wasm_bindgen_backend as backend; extern crate webidl; +mod util; + use std::fs; use std::io::{self, Read}; -use std::iter; use std::path::Path; +use backend::util::{ident_ty, rust_ident, wrap_import_function}; use failure::ResultExt; use quote::ToTokens; -mod util; - use util::{ - create_basic_method, create_function, create_getter, create_setter, ident_ty, rust_ident, - webidl_ty_to_syn_ty, wrap_import_function, TypePosition, + create_basic_method, create_function, create_getter, create_setter, webidl_ty_to_syn_ty, + TypePosition, }; /// Either `Ok(t)` or `Err(failure::Error)`. diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index c8d2eb5a1..5b197dde1 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -1,64 +1,12 @@ -use std::iter::{self, FromIterator}; +use std::iter; use backend; +use backend::util::{ident_ty, raw_ident, rust_ident, simple_path_ty}; use heck::SnakeCase; -use proc_macro2::{self, Ident}; +use proc_macro2::Ident; use syn; use webidl; -fn is_rust_keyword(name: &str) -> bool { - match name { - "abstract" | "alignof" | "as" | "become" | "box" | "break" | "const" | "continue" - | "crate" | "do" | "else" | "enum" | "extern" | "false" | "final" | "fn" | "for" | "if" - | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" | "move" | "mut" - | "offsetof" | "override" | "priv" | "proc" | "pub" | "pure" | "ref" | "return" - | "Self" | "self" | "sizeof" | "static" | "struct" | "super" | "trait" | "true" - | "type" | "typeof" | "unsafe" | "unsized" | "use" | "virtual" | "where" | "while" - | "yield" | "bool" | "_" => true, - _ => false, - } -} - -// Create an `Ident`, possibly mangling it if it conflicts with a Rust keyword. -pub fn rust_ident(name: &str) -> Ident { - if is_rust_keyword(name) { - Ident::new(&format!("{}_", name), proc_macro2::Span::call_site()) - } else { - raw_ident(name) - } -} - -// Create an `Ident` without checking to see if it conflicts with a Rust -// keyword. -fn raw_ident(name: &str) -> Ident { - Ident::new(name, proc_macro2::Span::call_site()) -} - -fn simple_path_ty(segments: I) -> syn::Type -where - I: IntoIterator, -{ - let segments: Vec<_> = segments - .into_iter() - .map(|i| syn::PathSegment { - ident: i, - arguments: syn::PathArguments::None, - }) - .collect(); - - syn::TypePath { - qself: None, - path: syn::Path { - leading_colon: None, - segments: syn::punctuated::Punctuated::from_iter(segments), - }, - }.into() -} - -pub fn ident_ty(ident: Ident) -> syn::Type { - simple_path_ty(Some(ident)) -} - fn shared_ref(ty: syn::Type) -> syn::Type { syn::TypeReference { and_token: Default::default(), @@ -337,12 +285,3 @@ pub fn create_setter( vec![backend::ast::BindgenAttr::Setter(Some(raw_ident(name)))], ) } - -pub fn wrap_import_function(function: backend::ast::ImportFunction) -> backend::ast::Import { - backend::ast::Import { - module: None, - version: None, - js_namespace: None, - kind: backend::ast::ImportKind::Function(function), - } -} diff --git a/src/js.rs b/src/js.rs index bf8b175fc..093382f4f 100644 --- a/src/js.rs +++ b/src/js.rs @@ -278,12 +278,6 @@ extern { extern { pub type Object; - /// The Object constructor creates an object wrapper. - /// - /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object - #[wasm_bindgen(constructor)] - pub fn new() -> Object; - /// The `hasOwnProperty()` method returns a boolean indicating whether the /// object has the specified property as its own property (as opposed to /// inheriting it). @@ -292,6 +286,33 @@ extern { #[wasm_bindgen(method, js_name = hasOwnProperty)] pub fn has_own_property(this: &Object, property: &JsValue) -> bool; + /// The isPrototypeOf() method checks if an object exists in another + /// object's prototype chain. + /// + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf + #[wasm_bindgen(method, js_name = isPrototypeOf)] + pub fn is_prototype_of(this: &Object, value: &JsValue) -> bool; + + /// The Object.keys() method returns an array of a given object's property + /// names, in the same order as we get with a normal loop. + /// + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys + #[wasm_bindgen(static_method_of = Object)] + pub fn keys(object: &Object) -> Array; + + /// The Object constructor creates an object wrapper. + /// + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object + #[wasm_bindgen(constructor)] + pub fn new() -> Object; + + /// The propertyIsEnumerable() method returns a Boolean indicating + /// whether the specified property is enumerable. + /// + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable + #[wasm_bindgen(method, js_name = propertyIsEnumerable)] + pub fn property_is_enumerable(this: &Object, property: &JsValue) -> bool; + /// The toLocaleString() method returns a string representing the object. /// This method is meant to be overridden by derived objects for locale-specific /// purposes. @@ -306,20 +327,6 @@ extern { #[wasm_bindgen(method, js_name = toString)] pub fn to_string(this: &Object) -> JsString; - /// The isPrototypeOf() method checks if an object exists in another - /// object's prototype chain. - /// - /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf - #[wasm_bindgen(method, js_name = isPrototypeOf)] - pub fn is_prototype_of(this: &Object, value: &JsValue) -> bool; - - /// The propertyIsEnumerable() method returns a Boolean indicating - /// whether the specified property is enumerable. - /// - /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable - #[wasm_bindgen(method, js_name = propertyIsEnumerable)] - pub fn property_is_enumerable(this: &Object, property: &JsValue) -> bool; - /// The valueOf() method returns the primitive value of the /// specified object. /// @@ -334,6 +341,13 @@ extern { #[wasm_bindgen(js_name = JsString)] pub type JsString; + /// The String object's charAt() method returns a new string consisting of the single + /// UTF-16 code unit located at the specified offset into the string. + /// + /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt + #[wasm_bindgen(method, js_class = "String", js_name = charAt)] + pub fn char_at(this: &JsString, index: u32) -> JsString; + /// The slice() method extracts a section of a string and returns it as a /// new string, without modifying the original string. /// diff --git a/tests/all/js_globals/JsString.rs b/tests/all/js_globals/JsString.rs index 45c78a73e..cc435d161 100755 --- a/tests/all/js_globals/JsString.rs +++ b/tests/all/js_globals/JsString.rs @@ -2,6 +2,35 @@ use project; +#[test] +fn char_at() { + project() + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section)] + + extern crate wasm_bindgen; + use wasm_bindgen::prelude::*; + use wasm_bindgen::js; + + #[wasm_bindgen] + pub fn string_char_at(this: &js::JsString, index: u32) -> js::JsString { + this.char_at(index) + } + "#) + .file("test.ts", r#" + import * as assert from "assert"; + import * as wasm from "./out"; + + var anyString = 'Brave new world'; + + export function test() { + assert.equal(wasm.string_char_at(anyString, 0), "B"); + assert.equal(wasm.string_char_at(anyString, 999), ""); + } + "#) + .test() +} + #[test] fn slice() { project() diff --git a/tests/all/js_globals/Object.rs b/tests/all/js_globals/Object.rs index e4fc92e71..2ca6a093c 100755 --- a/tests/all/js_globals/Object.rs +++ b/tests/all/js_globals/Object.rs @@ -123,6 +123,33 @@ fn is_prototype_of() { .test() } +#[test] +fn keys() { + project() + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section)] + + extern crate wasm_bindgen; + use wasm_bindgen::prelude::*; + use wasm_bindgen::js; + + #[wasm_bindgen] + pub fn keys(obj: &js::Object) -> js::Array { + js::Object::keys(obj) + } + "#) + .file("test.ts", r#" + import * as assert from "assert"; + import * as wasm from "./out"; + + export function test() { + const obj = { a: 1, b: 2, c: 3 }; + assert.deepStrictEqual(wasm.keys(obj), ["a", "b", "c"]); + } + "#) + .test() +} + #[test] fn property_is_enumerable() { project()