Skip to content

Commit 19d6cf1

Browse files
FreeMasenfitzgen
authored andcommitted
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
1 parent 3ad9bac commit 19d6cf1

File tree

16 files changed

+555
-59
lines changed

16 files changed

+555
-59
lines changed

.travis.yml

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ matrix:
6161
(cd examples/char && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh)
6262
- |
6363
(cd examples/closures && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh)
64+
- |
65+
(cd examples/comments && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh)
6466
6567
6668
install:

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ members = [
5252
"examples/asm.js",
5353
"examples/char",
5454
"examples/import_js",
55+
"examples/comments"
5556
]
5657

5758
[profile.release]

crates/backend/src/ast.rs

+45
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub struct Export {
2020
pub mutable: bool,
2121
pub constructor: Option<String>,
2222
pub function: Function,
23+
pub comments: Vec<String>,
2324
}
2425

2526
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@@ -82,6 +83,7 @@ pub struct Function {
8283
pub struct Struct {
8384
pub name: Ident,
8485
pub fields: Vec<StructField>,
86+
pub comments: Vec<String>,
8587
}
8688

8789
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@@ -92,12 +94,14 @@ pub struct StructField {
9294
pub ty: syn::Type,
9395
pub getter: Ident,
9496
pub setter: Ident,
97+
pub comments: Vec<String>,
9598
}
9699

97100
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
98101
pub struct Enum {
99102
pub name: Ident,
100103
pub variants: Vec<Variant>,
104+
pub comments: Vec<String>,
101105
}
102106

103107
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@@ -150,13 +154,15 @@ impl Program {
150154
}
151155
_ => {}
152156
}
157+
let comments = extract_doc_comments(&f.attrs);
153158
f.to_tokens(tokens);
154159
self.exports.push(Export {
155160
class: None,
156161
method: false,
157162
mutable: false,
158163
constructor: None,
159164
function: Function::from(f, opts),
165+
comments,
160166
});
161167
}
162168
syn::Item::Struct(mut s) => {
@@ -237,6 +243,7 @@ impl Program {
237243
}
238244

239245
let opts = BindgenAttrs::find(&mut method.attrs);
246+
let comments = extract_doc_comments(&method.attrs);
240247
let is_constructor = opts.constructor();
241248
let constructor = if is_constructor {
242249
Some(method.sig.ident.to_string())
@@ -259,6 +266,7 @@ impl Program {
259266
mutable: mutable.unwrap_or(false),
260267
constructor,
261268
function,
269+
comments,
262270
});
263271
}
264272

@@ -299,9 +307,11 @@ impl Program {
299307
}
300308
})
301309
.collect();
310+
let comments = extract_doc_comments(&item.attrs);
302311
self.enums.push(Enum {
303312
name: item.ident,
304313
variants,
314+
comments,
305315
});
306316
}
307317

@@ -585,6 +595,7 @@ impl Export {
585595
method: self.method,
586596
constructor: self.constructor.clone(),
587597
function: self.function.shared(),
598+
comments: self.comments.clone(),
588599
}
589600
}
590601
}
@@ -594,6 +605,7 @@ impl Enum {
594605
shared::Enum {
595606
name: self.name.to_string(),
596607
variants: self.variants.iter().map(|v| v.shared()).collect(),
608+
comments: self.comments.clone(),
597609
}
598610
}
599611
}
@@ -750,26 +762,31 @@ impl Struct {
750762
let getter = shared::struct_field_get(&ident, &name_str);
751763
let setter = shared::struct_field_set(&ident, &name_str);
752764
let opts = BindgenAttrs::find(&mut field.attrs);
765+
let comments = extract_doc_comments(&field.attrs);
753766
fields.push(StructField {
754767
opts,
755768
name: name.clone(),
756769
struct_name: s.ident.clone(),
757770
ty: field.ty.clone(),
758771
getter: Ident::new(&getter, Span::call_site()),
759772
setter: Ident::new(&setter, Span::call_site()),
773+
comments
760774
});
761775
}
762776
}
777+
let comments: Vec<String> = extract_doc_comments(&s.attrs);
763778
Struct {
764779
name: s.ident.clone(),
765780
fields,
781+
comments,
766782
}
767783
}
768784

769785
fn shared(&self) -> shared::Struct {
770786
shared::Struct {
771787
name: self.name.to_string(),
772788
fields: self.fields.iter().map(|s| s.shared()).collect(),
789+
comments: self.comments.clone(),
773790
}
774791
}
775792
}
@@ -779,6 +796,7 @@ impl StructField {
779796
shared::StructField {
780797
name: self.name.to_string(),
781798
readonly: self.opts.readonly(),
799+
comments: self.comments.clone(),
782800
}
783801
}
784802
}
@@ -1072,3 +1090,30 @@ fn replace_self(name: &Ident, item: &mut syn::ImplItem) {
10721090

10731091
syn::visit_mut::VisitMut::visit_impl_item_mut(&mut Walk(name), item);
10741092
}
1093+
1094+
/// Extract the documentation comments from a Vec of attributes
1095+
fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
1096+
attrs
1097+
.iter()
1098+
.filter_map(|a| {
1099+
// if the path segments include an ident of "doc" we know this
1100+
// this is a doc comment
1101+
if a.path.segments.iter().any(|s| s.ident.to_string() == "doc") {
1102+
Some(
1103+
// We want to filter out any Puncts so just grab the Literals
1104+
a.tts.clone().into_iter().filter_map(|t| match t {
1105+
TokenTree::Literal(lit) => {
1106+
// this will always return the quoted string, we deal with
1107+
// that in the cli when we read in the comments
1108+
Some(lit.to_string())
1109+
},
1110+
_ => None,
1111+
})
1112+
)
1113+
} else {
1114+
None
1115+
}
1116+
})
1117+
//Fold up the [[String]] iter we created into Vec<String>
1118+
.fold(vec![], |mut acc, a| {acc.extend(a); acc})
1119+
}

crates/cli-support/src/js/mod.rs

+36-21
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ pub struct Context<'a> {
3535

3636
#[derive(Default)]
3737
pub struct ExportedClass {
38+
comments: String,
3839
contents: String,
3940
typescript: String,
4041
constructor: Option<String>,
4142
fields: Vec<ClassField>,
4243
}
4344

4445
struct ClassField {
46+
comments: String,
4547
name: String,
4648
readonly: bool,
4749
}
@@ -52,9 +54,12 @@ pub struct SubContext<'a, 'b: 'a> {
5254
}
5355

5456
impl<'a> Context<'a> {
55-
fn export(&mut self, name: &str, contents: &str) {
57+
fn export(&mut self, name: &str, contents: &str, comments: Option<String>) {
5658
let contents = deindent(contents);
5759
let contents = contents.trim();
60+
if let Some(ref c) = comments {
61+
self.globals.push_str(c);
62+
}
5863
let global = if self.config.nodejs {
5964
if contents.starts_with("class") {
6065
format!("{1}\nmodule.exports.{0} = {0};\n", name, contents)
@@ -396,7 +401,7 @@ impl<'a> Context<'a> {
396401
.with_context(|_| {
397402
format!("failed to generate internal JS function `{}`", name)
398403
})?;
399-
self.export(name, &contents);
404+
self.export(name, &contents, None);
400405
Ok(())
401406
}
402407

@@ -461,7 +466,7 @@ impl<'a> Context<'a> {
461466
function(ptr) {{
462467
return addHeapObject({}.__construct(ptr));
463468
}}
464-
", name));
469+
", name), None);
465470
}
466471

467472
for field in class.fields.iter() {
@@ -484,7 +489,10 @@ impl<'a> Context<'a> {
484489
.method(true)
485490
.ret(&Some(descriptor))?
486491
.finish("", &format!("wasm.{}", wasm_getter));
487-
492+
if !dst.ends_with("\n") {
493+
dst.push_str("\n");
494+
}
495+
dst.push_str(&field.comments);
488496
dst.push_str("get ");
489497
dst.push_str(&field.name);
490498
dst.push_str(&get);
@@ -504,13 +512,12 @@ impl<'a> Context<'a> {
504512
}}
505513
", shared::free_function(&name)));
506514
ts_dst.push_str("free(): void;\n");
507-
508515
dst.push_str(&class.contents);
509516
ts_dst.push_str(&class.typescript);
510517
dst.push_str("}\n");
511518
ts_dst.push_str("}\n");
512519

513-
self.export(&name, &dst);
520+
self.export(&name, &dst, Some(class.comments.clone()));
514521
self.typescript.push_str(&ts_dst);
515522

516523
Ok(())
@@ -534,7 +541,7 @@ impl<'a> Context<'a> {
534541

535542
fn rewrite_imports(&mut self, module_name: &str) {
536543
for (name, contents) in self._rewrite_imports(module_name) {
537-
self.export(&name, &contents);
544+
self.export(&name, &contents, None);
538545
}
539546
}
540547

@@ -691,7 +698,7 @@ impl<'a> Context<'a> {
691698
return;
692699
throw new Error('stack is not currently empty');
693700
}
694-
");
701+
", None);
695702
}
696703
}
697704

@@ -715,7 +722,7 @@ impl<'a> Context<'a> {
715722
throw new Error('slab is not currently empty');
716723
}}
717724
}}
718-
", initial_values.len()));
725+
", initial_values.len()), None);
719726
}
720727
}
721728

@@ -1406,7 +1413,7 @@ impl<'a> Context<'a> {
14061413

14071414
// Ensure a blank line between adjacent items, and ensure everything is
14081415
// terminated with a newline.
1409-
while !self.globals.ends_with("\n\n\n") {
1416+
while !self.globals.ends_with("\n\n\n") && !self.globals.ends_with("*/\n") {
14101417
self.globals.push_str("\n");
14111418
}
14121419
self.globals.push_str(s);
@@ -1452,14 +1459,16 @@ impl<'a, 'b> SubContext<'a, 'b> {
14521459
self.generate_enum(e);
14531460
}
14541461
for s in self.program.structs.iter() {
1455-
self.cx.exported_classes
1462+
let mut class = self.cx.exported_classes
14561463
.entry(s.name.clone())
1457-
.or_insert_with(Default::default)
1458-
.fields
1459-
.extend(s.fields.iter().map(|s| {
1464+
.or_insert_with(Default::default);
1465+
class.comments = format_doc_comments(&s.comments);
1466+
class.fields
1467+
.extend(s.fields.iter().map(|f| {
14601468
ClassField {
1461-
name: s.name.clone(),
1462-
readonly: s.readonly,
1469+
name: f.name.clone(),
1470+
readonly: f.readonly,
1471+
comments: format_doc_comments(&f.comments),
14631472
}
14641473
}));
14651474
}
@@ -1477,7 +1486,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
14771486
let (js, ts) = Js2Rust::new(&export.function.name, self.cx)
14781487
.process(descriptor.unwrap_function())?
14791488
.finish("function", &format!("wasm.{}", export.function.name));
1480-
self.cx.export(&export.function.name, &js);
1489+
self.cx.export(&export.function.name, &js, Some(format_doc_comments(&export.comments)));
14811490
self.cx.globals.push_str("\n");
14821491
self.cx.typescript.push_str("export ");
14831492
self.cx.typescript.push_str(&ts);
@@ -1498,6 +1507,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
14981507
.finish("", &format!("wasm.{}", wasm_name));
14991508
let class = self.cx.exported_classes.entry(class_name.to_string())
15001509
.or_insert(ExportedClass::default());
1510+
class.contents.push_str(&format_doc_comments(&export.comments));
15011511
if !export.method {
15021512
class.contents.push_str("static ");
15031513
class.typescript.push_str("static ");
@@ -1514,7 +1524,6 @@ impl<'a, 'b> SubContext<'a, 'b> {
15141524
1 => Some(constructors[0].clone()),
15151525
x @ _ => bail!("there must be only one constructor, not {}", x),
15161526
};
1517-
15181527
class.contents.push_str(&export.function.name);
15191528
class.contents.push_str(&js);
15201529
class.contents.push_str("\n");
@@ -1593,7 +1602,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
15931602
function() {{
15941603
return addHeapObject({});
15951604
}}
1596-
", obj));
1605+
", obj), None);
15971606
Ok(())
15981607
}
15991608

@@ -1688,7 +1697,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
16881697
.catch(import.catch)
16891698
.process(descriptor.unwrap_function())?
16901699
.finish(&target);
1691-
self.cx.export(&import.shim, &js);
1700+
self.cx.export(&import.shim, &js, None);
16921701
Ok(())
16931702
}
16941703

@@ -1698,7 +1707,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
16981707
for variant in enum_.variants.iter() {
16991708
variants.push_str(&format!("{}:{},", variant.name, variant.value));
17001709
}
1701-
self.cx.export(&enum_.name, &format!("Object.freeze({{ {} }})", variants));
1710+
self.cx.export(&enum_.name, &format!("Object.freeze({{ {} }})", variants), Some(format_doc_comments(&enum_.comments)));
17021711
self.cx.typescript.push_str(&format!("export enum {} {{", enum_.name));
17031712

17041713
variants.clear();
@@ -1764,3 +1773,9 @@ fn deindent(s: &str) -> String {
17641773
}
17651774
ret
17661775
}
1776+
1777+
1778+
fn format_doc_comments(comments: &Vec<String>) -> String {
1779+
let body: String = comments.iter().map(|c| format!("*{}\n", c.trim_matches('"'))).collect();
1780+
format!("/**\n{}*/\n", body)
1781+
}

0 commit comments

Comments
 (0)