Allow for js property inspection (#1876)

* Add support for #[wasm_bindgen(inspectable)]

This annotation generates a `toJSON` and `toString` implementation for
generated JavaScript classes which display all readable properties
available via the class or its getters

This is useful because wasm-bindgen classes currently serialize to
display one value named `ptr`, which does not model the properties of
the struct in Rust

This annotation addresses rustwasm/wasm-bindgen#1857

* Support console.log for inspectable attr in Nodejs

`#[wasm_bindgen(inspectable)]` now generates an implementation of
`[util.inspect.custom]` for the Node.js target only. This implementation
causes `console.log` and friends to yield the same class-style output,
but with all readable fields of the Rust struct displayed

* Reduce duplication in generated methods

Generated `toString` and `[util.inspect.custom]` methods now call
`toJSON` to reduce duplication

* Store module name in variable
This commit is contained in:
Katie
2019-11-26 10:39:57 -08:00
committed by Alex Crichton
parent 181b10be3f
commit df34cf843e
9 changed files with 228 additions and 0 deletions

View File

@ -57,6 +57,10 @@ pub struct ExportedClass {
typescript: String,
has_constructor: bool,
wrap_needed: bool,
/// Whether to generate helper methods for inspecting the class
is_inspectable: bool,
/// All readable properties of the class
readable_properties: Vec<String>,
/// Map from field name to type as a string plus whether it has a setter
typescript_fields: HashMap<String, (String, bool)>,
}
@ -644,6 +648,54 @@ impl<'a> Context<'a> {
));
}
// If the class is inspectable, generate `toJSON` and `toString`
// to expose all readable properties of the class. Otherwise,
// the class shows only the "ptr" property when logged or serialized
if class.is_inspectable {
// Creates a `toJSON` method which returns an object of all readable properties
// This object looks like { a: this.a, b: this.b }
dst.push_str(&format!(
"
toJSON() {{
return {{{}}};
}}
toString() {{
return JSON.stringify(this);
}}
",
class
.readable_properties
.iter()
.fold(String::from("\n"), |fields, field_name| {
format!("{}{name}: this.{name},\n", fields, name = field_name)
})
));
if self.config.mode.nodejs() {
// `util.inspect` must be imported in Node.js to define [inspect.custom]
let module_name = self.import_name(&JsImport {
name: JsImportName::Module {
module: "util".to_string(),
name: "inspect".to_string(),
},
fields: Vec::new(),
})?;
// Node.js supports a custom inspect function to control the
// output of `console.log` and friends. The constructor is set
// to display the class name as a typical JavaScript class would
dst.push_str(&format!(
"
[{}.custom]() {{
return Object.assign(Object.create({{constructor: this.constructor}}), this.toJSON());
}}
",
module_name
));
}
}
dst.push_str(&format!(
"
free() {{
@ -2723,6 +2775,7 @@ impl<'a> Context<'a> {
fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> {
let class = require_class(&mut self.exported_classes, &struct_.name);
class.comments = format_doc_comments(&struct_.comments, None);
class.is_inspectable = struct_.is_inspectable;
Ok(())
}
@ -2975,6 +3028,7 @@ impl ExportedClass {
/// generation is handled specially.
fn push_getter(&mut self, docs: &str, field: &str, js: &str, ret_ty: &str) {
self.push_accessor(docs, field, js, "get ", ret_ty);
self.readable_properties.push(field.to_string());
}
/// Used for adding a setter to a class, mainly to ensure that TypeScript

View File

@ -256,6 +256,8 @@ pub struct AuxStruct {
pub name: String,
/// The copied Rust comments to forward to JS
pub comments: String,
/// Whether to generate helper methods for inspecting the class
pub is_inspectable: bool,
}
/// All possible types of imports that can be imported by a wasm module.
@ -1238,6 +1240,7 @@ impl<'a> Context<'a> {
let aux = AuxStruct {
name: struct_.name.to_string(),
comments: concatenate_comments(&struct_.comments),
is_inspectable: struct_.is_inspectable,
};
self.aux.structs.push(aux);