Skip to content

Commit 8265389

Browse files
authored
Copy more doc comments to JS/TS files, unescape comments (#2070)
* Copy more doc comments to JS/TS files, unescape comments * Move unescape code to macro-support
1 parent fc86589 commit 8265389

File tree

10 files changed

+179
-28
lines changed

10 files changed

+179
-28
lines changed

crates/backend/src/ast.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ pub struct Enum {
264264
pub struct Variant {
265265
pub name: Ident,
266266
pub value: u32,
267+
pub comments: Vec<String>,
267268
}
268269

269270
#[derive(Copy, Clone, Debug, PartialEq, Eq)]

crates/backend/src/encode.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<
227227
EnumVariant {
228228
name: intern.intern(&v.name),
229229
value: v.value,
230+
comments: v.comments.iter().map(|s| &**s).collect(),
230231
}
231232
}
232233

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

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ pub struct ExportedClass {
6666
is_inspectable: bool,
6767
/// All readable properties of the class
6868
readable_properties: Vec<String>,
69-
/// Map from field name to type as a string plus whether it has a setter
69+
/// Map from field name to type as a string, docs plus whether it has a setter
7070
/// and it is optional
71-
typescript_fields: HashMap<String, (String, bool, bool)>,
71+
typescript_fields: HashMap<String, (String, String, bool, bool)>,
7272
}
7373

7474
const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
@@ -798,7 +798,8 @@ impl<'a> Context<'a> {
798798
let mut fields = class.typescript_fields.keys().collect::<Vec<_>>();
799799
fields.sort(); // make sure we have deterministic output
800800
for name in fields {
801-
let (ty, has_setter, is_optional) = &class.typescript_fields[name];
801+
let (ty, docs, has_setter, is_optional) = &class.typescript_fields[name];
802+
ts_dst.push_str(docs);
802803
ts_dst.push_str(" ");
803804
if !has_setter {
804805
ts_dst.push_str("readonly ");
@@ -816,6 +817,7 @@ impl<'a> Context<'a> {
816817
ts_dst.push_str("}\n");
817818

818819
self.export(&name, &dst, Some(&class.comments))?;
820+
self.typescript.push_str(&class.comments);
819821
self.typescript.push_str(&ts_dst);
820822

821823
Ok(())
@@ -2901,10 +2903,23 @@ impl<'a> Context<'a> {
29012903
self.typescript
29022904
.push_str(&format!("export enum {} {{", enum_.name));
29032905
}
2904-
for (name, value) in enum_.variants.iter() {
2906+
for (name, value, comments) in enum_.variants.iter() {
2907+
let variant_docs = if comments.is_empty() {
2908+
String::new()
2909+
} else {
2910+
format_doc_comments(&comments, None)
2911+
};
2912+
if !variant_docs.is_empty() {
2913+
variants.push_str("\n");
2914+
variants.push_str(&variant_docs);
2915+
}
29052916
variants.push_str(&format!("{}:{},", name, value));
29062917
if enum_.generate_typescript {
2907-
self.typescript.push_str(&format!("\n {},", name));
2918+
self.typescript.push_str("\n");
2919+
if !variant_docs.is_empty() {
2920+
self.typescript.push_str(&variant_docs);
2921+
}
2922+
self.typescript.push_str(&format!(" {},", name));
29082923
}
29092924
}
29102925
if enum_.generate_typescript {
@@ -3227,7 +3242,7 @@ impl ExportedClass {
32273242
fn push_getter(&mut self, docs: &str, field: &str, js: &str, ret_ty: Option<&str>) {
32283243
self.push_accessor(docs, field, js, "get ");
32293244
if let Some(ret_ty) = ret_ty {
3230-
self.push_accessor_ts(field, ret_ty);
3245+
self.push_accessor_ts(docs, field, ret_ty, false);
32313246
}
32323247
self.readable_properties.push(field.to_string());
32333248
}
@@ -3244,20 +3259,30 @@ impl ExportedClass {
32443259
) {
32453260
self.push_accessor(docs, field, js, "set ");
32463261
if let Some(ret_ty) = ret_ty {
3247-
let (has_setter, is_optional) = self.push_accessor_ts(field, ret_ty);
3248-
*has_setter = true;
3262+
let is_optional = self.push_accessor_ts(docs, field, ret_ty, true);
32493263
*is_optional = might_be_optional_field;
32503264
}
32513265
}
32523266

3253-
fn push_accessor_ts(&mut self, field: &str, ret_ty: &str) -> (&mut bool, &mut bool) {
3254-
let (ty, has_setter, is_optional) = self
3267+
fn push_accessor_ts(
3268+
&mut self,
3269+
docs: &str,
3270+
field: &str,
3271+
ret_ty: &str,
3272+
is_setter: bool,
3273+
) -> &mut bool {
3274+
let (ty, accessor_docs, has_setter, is_optional) = self
32553275
.typescript_fields
32563276
.entry(field.to_string())
32573277
.or_insert_with(Default::default);
32583278

32593279
*ty = ret_ty.to_string();
3260-
(has_setter, is_optional)
3280+
// Deterministic output: always use the getter's docs if available
3281+
if !docs.is_empty() && (accessor_docs.is_empty() || !is_setter) {
3282+
*accessor_docs = docs.to_owned();
3283+
}
3284+
*has_setter |= is_setter;
3285+
is_optional
32613286
}
32623287

32633288
fn push_accessor(&mut self, docs: &str, field: &str, js: &str, prefix: &str) {

crates/cli-support/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,8 @@ fn reset_indentation(s: &str) -> String {
477477
dst.push_str(line);
478478
}
479479
dst.push_str("\n");
480-
if line.ends_with('{') {
480+
// Ignore { inside of comments and if it's an exported enum
481+
if line.ends_with('{') && !line.starts_with('*') && !line.ends_with("Object.freeze({") {
481482
indent += 1;
482483
}
483484
}

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,13 @@ impl<'a> Context<'a> {
766766
variants: enum_
767767
.variants
768768
.iter()
769-
.map(|v| (v.name.to_string(), v.value))
769+
.map(|v| {
770+
(
771+
v.name.to_string(),
772+
v.value,
773+
concatenate_comments(&v.comments),
774+
)
775+
})
770776
.collect(),
771777
generate_typescript: enum_.generate_typescript,
772778
};
@@ -1511,9 +1517,5 @@ fn verify_schema_matches<'a>(data: &'a [u8]) -> Result<Option<&'a str>, Error> {
15111517
}
15121518

15131519
fn concatenate_comments(comments: &[&str]) -> String {
1514-
comments
1515-
.iter()
1516-
.map(|s| s.trim_matches('"'))
1517-
.collect::<Vec<_>>()
1518-
.join("\n")
1520+
comments.iter().map(|&s| s).collect::<Vec<_>>().join("\n")
15191521
}

crates/cli-support/src/wit/nonstandard.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ pub struct AuxEnum {
132132
pub name: String,
133133
/// The copied Rust comments to forward to JS
134134
pub comments: String,
135-
/// A list of variants with their name and value
135+
/// A list of variants with their name, value and comments
136136
/// and whether typescript bindings should be generated for each variant
137-
pub variants: Vec<(String, u32)>,
137+
pub variants: Vec<(String, u32, String)>,
138138
/// Whether typescript bindings should be generated for this enum.
139139
pub generate_typescript: bool,
140140
}

crates/macro-support/src/parser.rs

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use std::cell::Cell;
2+
use std::char;
3+
use std::str::Chars;
24

35
use ast::OperationKind;
46
use backend::ast;
@@ -1131,9 +1133,11 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
11311133
),
11321134
};
11331135

1136+
let comments = extract_doc_comments(&v.attrs);
11341137
Ok(ast::Variant {
11351138
name: v.ident.clone(),
11361139
value,
1140+
comments,
11371141
})
11381142
})
11391143
.collect::<Result<Vec<_>, Diagnostic>>()?;
@@ -1324,9 +1328,8 @@ fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
13241328
// We want to filter out any Puncts so just grab the Literals
13251329
a.tokens.clone().into_iter().filter_map(|t| match t {
13261330
TokenTree::Literal(lit) => {
1327-
// this will always return the quoted string, we deal with
1328-
// that in the cli when we read in the comments
1329-
Some(lit.to_string())
1331+
let quoted = lit.to_string();
1332+
Some(try_unescape(&quoted).unwrap_or_else(|| quoted))
13301333
}
13311334
_ => None,
13321335
}),
@@ -1342,6 +1345,76 @@ fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
13421345
})
13431346
}
13441347

1348+
// Unescapes a quoted string. char::escape_debug() was used to escape the text.
1349+
fn try_unescape(s: &str) -> Option<String> {
1350+
if s.is_empty() {
1351+
return Some(String::new());
1352+
}
1353+
let mut result = String::with_capacity(s.len());
1354+
let mut chars = s.chars();
1355+
for i in 0.. {
1356+
let c = match chars.next() {
1357+
Some(c) => c,
1358+
None => {
1359+
if result.ends_with('"') {
1360+
result.pop();
1361+
}
1362+
return Some(result);
1363+
}
1364+
};
1365+
if i == 0 && c == '"' {
1366+
// ignore it
1367+
} else if c == '\\' {
1368+
let c = chars.next()?;
1369+
match c {
1370+
't' => result.push('\t'),
1371+
'r' => result.push('\r'),
1372+
'n' => result.push('\n'),
1373+
'\\' | '\'' | '"' => result.push(c),
1374+
'u' => {
1375+
if chars.next() != Some('{') {
1376+
return None;
1377+
}
1378+
let (c, next) = unescape_unicode(&mut chars)?;
1379+
result.push(c);
1380+
if next != '}' {
1381+
return None;
1382+
}
1383+
}
1384+
_ => return None,
1385+
}
1386+
} else {
1387+
result.push(c);
1388+
}
1389+
}
1390+
None
1391+
}
1392+
1393+
fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
1394+
let mut value = 0;
1395+
for i in 0..7 {
1396+
let c = chars.next()?;
1397+
let num = if c >= '0' && c <= '9' {
1398+
c as u32 - '0' as u32
1399+
} else if c >= 'a' && c <= 'f' {
1400+
c as u32 - 'a' as u32 + 10
1401+
} else if c >= 'A' && c <= 'F' {
1402+
c as u32 - 'A' as u32 + 10
1403+
} else {
1404+
if i == 0 {
1405+
return None;
1406+
}
1407+
let decoded = char::from_u32(value)?;
1408+
return Some((decoded, c));
1409+
};
1410+
if i >= 6 {
1411+
return None;
1412+
}
1413+
value = (value << 4) | num;
1414+
}
1415+
None
1416+
}
1417+
13451418
/// Check there are no lifetimes on the function.
13461419
fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> {
13471420
struct Walk {

crates/shared/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ macro_rules! shared_api {
106106
struct EnumVariant<'a> {
107107
name: &'a str,
108108
value: u32,
109+
comments: Vec<&'a str>,
109110
}
110111

111112
struct Function<'a> {

tests/wasm/comments.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,16 @@ const assert = require('assert');
44
exports.assert_comments_exist = function() {
55
const bindings_file = require.resolve('wasm-bindgen-test');
66
const contents = fs.readFileSync(bindings_file);
7-
assert.ok(contents.includes("* annotated function"));
7+
assert.ok(contents.includes("* annotated function ✔️ \" \\ ' {"));
88
assert.ok(contents.includes("* annotated struct type"));
9-
assert.ok(contents.includes("* annotated struct field"));
9+
assert.ok(contents.includes("* annotated struct field b"));
10+
assert.ok(contents.includes("* annotated struct field c"));
11+
assert.ok(contents.includes("* annotated struct constructor"));
1012
assert.ok(contents.includes("* annotated struct method"));
13+
assert.ok(contents.includes("* annotated struct getter"));
14+
assert.ok(contents.includes("* annotated struct setter"));
15+
assert.ok(contents.includes("* annotated struct static method"));
16+
assert.ok(contents.includes("* annotated enum type"));
17+
assert.ok(contents.includes("* annotated enum variant 1"));
18+
assert.ok(contents.includes("* annotated enum variant 2"));
1119
};

tests/wasm/comments.rs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,65 @@ extern "C" {
66
fn assert_comments_exist();
77
}
88

9+
/// annotated function ✔️ " \ ' {
910
#[wasm_bindgen]
10-
/// annotated function
1111
pub fn annotated() -> String {
1212
String::new()
1313
}
1414

15-
#[wasm_bindgen]
1615
/// annotated struct type
16+
#[wasm_bindgen]
1717
pub struct Annotated {
1818
a: String,
19-
/// annotated struct field
19+
/// annotated struct field b
2020
pub b: u32,
21+
/// annotated struct field c
22+
#[wasm_bindgen(readonly)]
23+
pub c: u32,
24+
d: u32,
2125
}
2226

2327
#[wasm_bindgen]
2428
impl Annotated {
29+
/// annotated struct constructor
30+
#[wasm_bindgen(constructor)]
31+
pub fn new() -> Self {
32+
Self {
33+
a: String::new(),
34+
b: 0,
35+
c: 0,
36+
d: 0,
37+
}
38+
}
39+
2540
/// annotated struct method
2641
pub fn get_a(&self) -> String {
2742
self.a.clone()
2843
}
44+
45+
/// annotated struct getter
46+
#[wasm_bindgen(getter)]
47+
pub fn d(&self) -> u32 {
48+
self.d
49+
}
50+
51+
/// annotated struct setter
52+
#[wasm_bindgen(setter)]
53+
pub fn set_d(&mut self, value: u32) {
54+
self.d = value
55+
}
56+
57+
/// annotated struct static method
58+
pub fn static_method() {}
59+
}
60+
61+
/// annotated enum type
62+
#[wasm_bindgen]
63+
pub enum AnnotatedEnum {
64+
/// annotated enum variant 1
65+
Variant1,
66+
/// annotated enum variant 2
67+
Variant2,
2968
}
3069

3170
#[wasm_bindgen_test]

0 commit comments

Comments
 (0)