mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-07-31 20:11:55 +00:00
Copy doc comments from Rust to JS (#265)
* backend comments complete * better matching * gen comments * Add example * Move test bindings gen to own fn * move build step into build fn * add fn to read js, refactor gen_bindings/test to allow for this * Add comments test * Update readmes * add comments to travis * fix broken tests * +x on build.sh * fix wbg cmd in build.sh * Address fitzgen's comments
This commit is contained in:
committed by
Nick Fitzgerald
parent
3ad9bac599
commit
19d6cf1488
@@ -20,6 +20,7 @@ pub struct Export {
|
||||
pub mutable: bool,
|
||||
pub constructor: Option<String>,
|
||||
pub function: Function,
|
||||
pub comments: Vec<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||
@@ -82,6 +83,7 @@ pub struct Function {
|
||||
pub struct Struct {
|
||||
pub name: Ident,
|
||||
pub fields: Vec<StructField>,
|
||||
pub comments: Vec<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||
@@ -92,12 +94,14 @@ pub struct StructField {
|
||||
pub ty: syn::Type,
|
||||
pub getter: Ident,
|
||||
pub setter: Ident,
|
||||
pub comments: Vec<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||
pub struct Enum {
|
||||
pub name: Ident,
|
||||
pub variants: Vec<Variant>,
|
||||
pub comments: Vec<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||
@@ -150,6 +154,7 @@ impl Program {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let comments = extract_doc_comments(&f.attrs);
|
||||
f.to_tokens(tokens);
|
||||
self.exports.push(Export {
|
||||
class: None,
|
||||
@@ -157,6 +162,7 @@ impl Program {
|
||||
mutable: false,
|
||||
constructor: None,
|
||||
function: Function::from(f, opts),
|
||||
comments,
|
||||
});
|
||||
}
|
||||
syn::Item::Struct(mut s) => {
|
||||
@@ -237,6 +243,7 @@ impl Program {
|
||||
}
|
||||
|
||||
let opts = BindgenAttrs::find(&mut method.attrs);
|
||||
let comments = extract_doc_comments(&method.attrs);
|
||||
let is_constructor = opts.constructor();
|
||||
let constructor = if is_constructor {
|
||||
Some(method.sig.ident.to_string())
|
||||
@@ -259,6 +266,7 @@ impl Program {
|
||||
mutable: mutable.unwrap_or(false),
|
||||
constructor,
|
||||
function,
|
||||
comments,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -299,9 +307,11 @@ impl Program {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let comments = extract_doc_comments(&item.attrs);
|
||||
self.enums.push(Enum {
|
||||
name: item.ident,
|
||||
variants,
|
||||
comments,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -585,6 +595,7 @@ impl Export {
|
||||
method: self.method,
|
||||
constructor: self.constructor.clone(),
|
||||
function: self.function.shared(),
|
||||
comments: self.comments.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,6 +605,7 @@ impl Enum {
|
||||
shared::Enum {
|
||||
name: self.name.to_string(),
|
||||
variants: self.variants.iter().map(|v| v.shared()).collect(),
|
||||
comments: self.comments.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -750,6 +762,7 @@ impl Struct {
|
||||
let getter = shared::struct_field_get(&ident, &name_str);
|
||||
let setter = shared::struct_field_set(&ident, &name_str);
|
||||
let opts = BindgenAttrs::find(&mut field.attrs);
|
||||
let comments = extract_doc_comments(&field.attrs);
|
||||
fields.push(StructField {
|
||||
opts,
|
||||
name: name.clone(),
|
||||
@@ -757,12 +770,15 @@ impl Struct {
|
||||
ty: field.ty.clone(),
|
||||
getter: Ident::new(&getter, Span::call_site()),
|
||||
setter: Ident::new(&setter, Span::call_site()),
|
||||
comments
|
||||
});
|
||||
}
|
||||
}
|
||||
let comments: Vec<String> = extract_doc_comments(&s.attrs);
|
||||
Struct {
|
||||
name: s.ident.clone(),
|
||||
fields,
|
||||
comments,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,6 +786,7 @@ impl Struct {
|
||||
shared::Struct {
|
||||
name: self.name.to_string(),
|
||||
fields: self.fields.iter().map(|s| s.shared()).collect(),
|
||||
comments: self.comments.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -779,6 +796,7 @@ impl StructField {
|
||||
shared::StructField {
|
||||
name: self.name.to_string(),
|
||||
readonly: self.opts.readonly(),
|
||||
comments: self.comments.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1072,3 +1090,30 @@ fn replace_self(name: &Ident, item: &mut syn::ImplItem) {
|
||||
|
||||
syn::visit_mut::VisitMut::visit_impl_item_mut(&mut Walk(name), item);
|
||||
}
|
||||
|
||||
/// Extract the documentation comments from a Vec of attributes
|
||||
fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter_map(|a| {
|
||||
// if the path segments include an ident of "doc" we know this
|
||||
// this is a doc comment
|
||||
if a.path.segments.iter().any(|s| s.ident.to_string() == "doc") {
|
||||
Some(
|
||||
// We want to filter out any Puncts so just grab the Literals
|
||||
a.tts.clone().into_iter().filter_map(|t| match t {
|
||||
TokenTree::Literal(lit) => {
|
||||
// this will always return the quoted string, we deal with
|
||||
// that in the cli when we read in the comments
|
||||
Some(lit.to_string())
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
//Fold up the [[String]] iter we created into Vec<String>
|
||||
.fold(vec![], |mut acc, a| {acc.extend(a); acc})
|
||||
}
|
@@ -35,6 +35,7 @@ pub struct Context<'a> {
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExportedClass {
|
||||
comments: String,
|
||||
contents: String,
|
||||
typescript: String,
|
||||
constructor: Option<String>,
|
||||
@@ -42,6 +43,7 @@ pub struct ExportedClass {
|
||||
}
|
||||
|
||||
struct ClassField {
|
||||
comments: String,
|
||||
name: String,
|
||||
readonly: bool,
|
||||
}
|
||||
@@ -52,9 +54,12 @@ pub struct SubContext<'a, 'b: 'a> {
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
fn export(&mut self, name: &str, contents: &str) {
|
||||
fn export(&mut self, name: &str, contents: &str, comments: Option<String>) {
|
||||
let contents = deindent(contents);
|
||||
let contents = contents.trim();
|
||||
if let Some(ref c) = comments {
|
||||
self.globals.push_str(c);
|
||||
}
|
||||
let global = if self.config.nodejs {
|
||||
if contents.starts_with("class") {
|
||||
format!("{1}\nmodule.exports.{0} = {0};\n", name, contents)
|
||||
@@ -396,7 +401,7 @@ impl<'a> Context<'a> {
|
||||
.with_context(|_| {
|
||||
format!("failed to generate internal JS function `{}`", name)
|
||||
})?;
|
||||
self.export(name, &contents);
|
||||
self.export(name, &contents, None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -461,7 +466,7 @@ impl<'a> Context<'a> {
|
||||
function(ptr) {{
|
||||
return addHeapObject({}.__construct(ptr));
|
||||
}}
|
||||
", name));
|
||||
", name), None);
|
||||
}
|
||||
|
||||
for field in class.fields.iter() {
|
||||
@@ -484,7 +489,10 @@ impl<'a> Context<'a> {
|
||||
.method(true)
|
||||
.ret(&Some(descriptor))?
|
||||
.finish("", &format!("wasm.{}", wasm_getter));
|
||||
|
||||
if !dst.ends_with("\n") {
|
||||
dst.push_str("\n");
|
||||
}
|
||||
dst.push_str(&field.comments);
|
||||
dst.push_str("get ");
|
||||
dst.push_str(&field.name);
|
||||
dst.push_str(&get);
|
||||
@@ -504,13 +512,12 @@ impl<'a> Context<'a> {
|
||||
}}
|
||||
", shared::free_function(&name)));
|
||||
ts_dst.push_str("free(): void;\n");
|
||||
|
||||
dst.push_str(&class.contents);
|
||||
ts_dst.push_str(&class.typescript);
|
||||
dst.push_str("}\n");
|
||||
ts_dst.push_str("}\n");
|
||||
|
||||
self.export(&name, &dst);
|
||||
self.export(&name, &dst, Some(class.comments.clone()));
|
||||
self.typescript.push_str(&ts_dst);
|
||||
|
||||
Ok(())
|
||||
@@ -534,7 +541,7 @@ impl<'a> Context<'a> {
|
||||
|
||||
fn rewrite_imports(&mut self, module_name: &str) {
|
||||
for (name, contents) in self._rewrite_imports(module_name) {
|
||||
self.export(&name, &contents);
|
||||
self.export(&name, &contents, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,7 +698,7 @@ impl<'a> Context<'a> {
|
||||
return;
|
||||
throw new Error('stack is not currently empty');
|
||||
}
|
||||
");
|
||||
", None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,7 +722,7 @@ impl<'a> Context<'a> {
|
||||
throw new Error('slab is not currently empty');
|
||||
}}
|
||||
}}
|
||||
", initial_values.len()));
|
||||
", initial_values.len()), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1406,7 +1413,7 @@ impl<'a> Context<'a> {
|
||||
|
||||
// Ensure a blank line between adjacent items, and ensure everything is
|
||||
// terminated with a newline.
|
||||
while !self.globals.ends_with("\n\n\n") {
|
||||
while !self.globals.ends_with("\n\n\n") && !self.globals.ends_with("*/\n") {
|
||||
self.globals.push_str("\n");
|
||||
}
|
||||
self.globals.push_str(s);
|
||||
@@ -1452,14 +1459,16 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
self.generate_enum(e);
|
||||
}
|
||||
for s in self.program.structs.iter() {
|
||||
self.cx.exported_classes
|
||||
let mut class = self.cx.exported_classes
|
||||
.entry(s.name.clone())
|
||||
.or_insert_with(Default::default)
|
||||
.fields
|
||||
.extend(s.fields.iter().map(|s| {
|
||||
.or_insert_with(Default::default);
|
||||
class.comments = format_doc_comments(&s.comments);
|
||||
class.fields
|
||||
.extend(s.fields.iter().map(|f| {
|
||||
ClassField {
|
||||
name: s.name.clone(),
|
||||
readonly: s.readonly,
|
||||
name: f.name.clone(),
|
||||
readonly: f.readonly,
|
||||
comments: format_doc_comments(&f.comments),
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -1477,7 +1486,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
let (js, ts) = Js2Rust::new(&export.function.name, self.cx)
|
||||
.process(descriptor.unwrap_function())?
|
||||
.finish("function", &format!("wasm.{}", export.function.name));
|
||||
self.cx.export(&export.function.name, &js);
|
||||
self.cx.export(&export.function.name, &js, Some(format_doc_comments(&export.comments)));
|
||||
self.cx.globals.push_str("\n");
|
||||
self.cx.typescript.push_str("export ");
|
||||
self.cx.typescript.push_str(&ts);
|
||||
@@ -1498,6 +1507,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
.finish("", &format!("wasm.{}", wasm_name));
|
||||
let class = self.cx.exported_classes.entry(class_name.to_string())
|
||||
.or_insert(ExportedClass::default());
|
||||
class.contents.push_str(&format_doc_comments(&export.comments));
|
||||
if !export.method {
|
||||
class.contents.push_str("static ");
|
||||
class.typescript.push_str("static ");
|
||||
@@ -1514,7 +1524,6 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
1 => Some(constructors[0].clone()),
|
||||
x @ _ => bail!("there must be only one constructor, not {}", x),
|
||||
};
|
||||
|
||||
class.contents.push_str(&export.function.name);
|
||||
class.contents.push_str(&js);
|
||||
class.contents.push_str("\n");
|
||||
@@ -1593,7 +1602,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
function() {{
|
||||
return addHeapObject({});
|
||||
}}
|
||||
", obj));
|
||||
", obj), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1688,7 +1697,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
.catch(import.catch)
|
||||
.process(descriptor.unwrap_function())?
|
||||
.finish(&target);
|
||||
self.cx.export(&import.shim, &js);
|
||||
self.cx.export(&import.shim, &js, None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1698,7 +1707,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
for variant in enum_.variants.iter() {
|
||||
variants.push_str(&format!("{}:{},", variant.name, variant.value));
|
||||
}
|
||||
self.cx.export(&enum_.name, &format!("Object.freeze({{ {} }})", variants));
|
||||
self.cx.export(&enum_.name, &format!("Object.freeze({{ {} }})", variants), Some(format_doc_comments(&enum_.comments)));
|
||||
self.cx.typescript.push_str(&format!("export enum {} {{", enum_.name));
|
||||
|
||||
variants.clear();
|
||||
@@ -1764,3 +1773,9 @@ fn deindent(s: &str) -> String {
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
|
||||
fn format_doc_comments(comments: &Vec<String>) -> String {
|
||||
let body: String = comments.iter().map(|c| format!("*{}\n", c.trim_matches('"'))).collect();
|
||||
format!("/**\n{}*/\n", body)
|
||||
}
|
@@ -64,12 +64,14 @@ pub struct Export {
|
||||
pub method: bool,
|
||||
pub constructor: Option<String>,
|
||||
pub function: Function,
|
||||
pub comments: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Enum {
|
||||
pub name: String,
|
||||
pub variants: Vec<EnumVariant>,
|
||||
pub comments: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
@@ -87,12 +89,14 @@ pub struct Function {
|
||||
pub struct Struct {
|
||||
pub name: String,
|
||||
pub fields: Vec<StructField>,
|
||||
pub comments: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct StructField {
|
||||
pub name: String,
|
||||
pub readonly: bool,
|
||||
pub comments: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn new_function(struct_name: &str) -> String {
|
||||
|
Reference in New Issue
Block a user