mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-25 06:02:13 +00:00
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
This commit is contained in:
parent
fc86589715
commit
826538922f
@ -264,6 +264,7 @@ pub struct Enum {
|
|||||||
pub struct Variant {
|
pub struct Variant {
|
||||||
pub name: Ident,
|
pub name: Ident,
|
||||||
pub value: u32,
|
pub value: u32,
|
||||||
|
pub comments: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -227,6 +227,7 @@ fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<
|
|||||||
EnumVariant {
|
EnumVariant {
|
||||||
name: intern.intern(&v.name),
|
name: intern.intern(&v.name),
|
||||||
value: v.value,
|
value: v.value,
|
||||||
|
comments: v.comments.iter().map(|s| &**s).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,9 +66,9 @@ pub struct ExportedClass {
|
|||||||
is_inspectable: bool,
|
is_inspectable: bool,
|
||||||
/// All readable properties of the class
|
/// All readable properties of the class
|
||||||
readable_properties: Vec<String>,
|
readable_properties: Vec<String>,
|
||||||
/// Map from field name to type as a string plus whether it has a setter
|
/// Map from field name to type as a string, docs plus whether it has a setter
|
||||||
/// and it is optional
|
/// and it is optional
|
||||||
typescript_fields: HashMap<String, (String, bool, bool)>,
|
typescript_fields: HashMap<String, (String, String, bool, bool)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
|
const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
|
||||||
@ -798,7 +798,8 @@ impl<'a> Context<'a> {
|
|||||||
let mut fields = class.typescript_fields.keys().collect::<Vec<_>>();
|
let mut fields = class.typescript_fields.keys().collect::<Vec<_>>();
|
||||||
fields.sort(); // make sure we have deterministic output
|
fields.sort(); // make sure we have deterministic output
|
||||||
for name in fields {
|
for name in fields {
|
||||||
let (ty, has_setter, is_optional) = &class.typescript_fields[name];
|
let (ty, docs, has_setter, is_optional) = &class.typescript_fields[name];
|
||||||
|
ts_dst.push_str(docs);
|
||||||
ts_dst.push_str(" ");
|
ts_dst.push_str(" ");
|
||||||
if !has_setter {
|
if !has_setter {
|
||||||
ts_dst.push_str("readonly ");
|
ts_dst.push_str("readonly ");
|
||||||
@ -816,6 +817,7 @@ impl<'a> Context<'a> {
|
|||||||
ts_dst.push_str("}\n");
|
ts_dst.push_str("}\n");
|
||||||
|
|
||||||
self.export(&name, &dst, Some(&class.comments))?;
|
self.export(&name, &dst, Some(&class.comments))?;
|
||||||
|
self.typescript.push_str(&class.comments);
|
||||||
self.typescript.push_str(&ts_dst);
|
self.typescript.push_str(&ts_dst);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -2901,10 +2903,23 @@ impl<'a> Context<'a> {
|
|||||||
self.typescript
|
self.typescript
|
||||||
.push_str(&format!("export enum {} {{", enum_.name));
|
.push_str(&format!("export enum {} {{", enum_.name));
|
||||||
}
|
}
|
||||||
for (name, value) in enum_.variants.iter() {
|
for (name, value, comments) in enum_.variants.iter() {
|
||||||
|
let variant_docs = if comments.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format_doc_comments(&comments, None)
|
||||||
|
};
|
||||||
|
if !variant_docs.is_empty() {
|
||||||
|
variants.push_str("\n");
|
||||||
|
variants.push_str(&variant_docs);
|
||||||
|
}
|
||||||
variants.push_str(&format!("{}:{},", name, value));
|
variants.push_str(&format!("{}:{},", name, value));
|
||||||
if enum_.generate_typescript {
|
if enum_.generate_typescript {
|
||||||
self.typescript.push_str(&format!("\n {},", name));
|
self.typescript.push_str("\n");
|
||||||
|
if !variant_docs.is_empty() {
|
||||||
|
self.typescript.push_str(&variant_docs);
|
||||||
|
}
|
||||||
|
self.typescript.push_str(&format!(" {},", name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if enum_.generate_typescript {
|
if enum_.generate_typescript {
|
||||||
@ -3227,7 +3242,7 @@ impl ExportedClass {
|
|||||||
fn push_getter(&mut self, docs: &str, field: &str, js: &str, ret_ty: Option<&str>) {
|
fn push_getter(&mut self, docs: &str, field: &str, js: &str, ret_ty: Option<&str>) {
|
||||||
self.push_accessor(docs, field, js, "get ");
|
self.push_accessor(docs, field, js, "get ");
|
||||||
if let Some(ret_ty) = ret_ty {
|
if let Some(ret_ty) = ret_ty {
|
||||||
self.push_accessor_ts(field, ret_ty);
|
self.push_accessor_ts(docs, field, ret_ty, false);
|
||||||
}
|
}
|
||||||
self.readable_properties.push(field.to_string());
|
self.readable_properties.push(field.to_string());
|
||||||
}
|
}
|
||||||
@ -3244,20 +3259,30 @@ impl ExportedClass {
|
|||||||
) {
|
) {
|
||||||
self.push_accessor(docs, field, js, "set ");
|
self.push_accessor(docs, field, js, "set ");
|
||||||
if let Some(ret_ty) = ret_ty {
|
if let Some(ret_ty) = ret_ty {
|
||||||
let (has_setter, is_optional) = self.push_accessor_ts(field, ret_ty);
|
let is_optional = self.push_accessor_ts(docs, field, ret_ty, true);
|
||||||
*has_setter = true;
|
|
||||||
*is_optional = might_be_optional_field;
|
*is_optional = might_be_optional_field;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_accessor_ts(&mut self, field: &str, ret_ty: &str) -> (&mut bool, &mut bool) {
|
fn push_accessor_ts(
|
||||||
let (ty, has_setter, is_optional) = self
|
&mut self,
|
||||||
|
docs: &str,
|
||||||
|
field: &str,
|
||||||
|
ret_ty: &str,
|
||||||
|
is_setter: bool,
|
||||||
|
) -> &mut bool {
|
||||||
|
let (ty, accessor_docs, has_setter, is_optional) = self
|
||||||
.typescript_fields
|
.typescript_fields
|
||||||
.entry(field.to_string())
|
.entry(field.to_string())
|
||||||
.or_insert_with(Default::default);
|
.or_insert_with(Default::default);
|
||||||
|
|
||||||
*ty = ret_ty.to_string();
|
*ty = ret_ty.to_string();
|
||||||
(has_setter, is_optional)
|
// Deterministic output: always use the getter's docs if available
|
||||||
|
if !docs.is_empty() && (accessor_docs.is_empty() || !is_setter) {
|
||||||
|
*accessor_docs = docs.to_owned();
|
||||||
|
}
|
||||||
|
*has_setter |= is_setter;
|
||||||
|
is_optional
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_accessor(&mut self, docs: &str, field: &str, js: &str, prefix: &str) {
|
fn push_accessor(&mut self, docs: &str, field: &str, js: &str, prefix: &str) {
|
||||||
|
@ -477,7 +477,8 @@ fn reset_indentation(s: &str) -> String {
|
|||||||
dst.push_str(line);
|
dst.push_str(line);
|
||||||
}
|
}
|
||||||
dst.push_str("\n");
|
dst.push_str("\n");
|
||||||
if line.ends_with('{') {
|
// Ignore { inside of comments and if it's an exported enum
|
||||||
|
if line.ends_with('{') && !line.starts_with('*') && !line.ends_with("Object.freeze({") {
|
||||||
indent += 1;
|
indent += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -766,7 +766,13 @@ impl<'a> Context<'a> {
|
|||||||
variants: enum_
|
variants: enum_
|
||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| (v.name.to_string(), v.value))
|
.map(|v| {
|
||||||
|
(
|
||||||
|
v.name.to_string(),
|
||||||
|
v.value,
|
||||||
|
concatenate_comments(&v.comments),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
generate_typescript: enum_.generate_typescript,
|
generate_typescript: enum_.generate_typescript,
|
||||||
};
|
};
|
||||||
@ -1511,9 +1517,5 @@ fn verify_schema_matches<'a>(data: &'a [u8]) -> Result<Option<&'a str>, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn concatenate_comments(comments: &[&str]) -> String {
|
fn concatenate_comments(comments: &[&str]) -> String {
|
||||||
comments
|
comments.iter().map(|&s| s).collect::<Vec<_>>().join("\n")
|
||||||
.iter()
|
|
||||||
.map(|s| s.trim_matches('"'))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
}
|
}
|
||||||
|
@ -132,9 +132,9 @@ pub struct AuxEnum {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
/// The copied Rust comments to forward to JS
|
/// The copied Rust comments to forward to JS
|
||||||
pub comments: String,
|
pub comments: String,
|
||||||
/// A list of variants with their name and value
|
/// A list of variants with their name, value and comments
|
||||||
/// and whether typescript bindings should be generated for each variant
|
/// and whether typescript bindings should be generated for each variant
|
||||||
pub variants: Vec<(String, u32)>,
|
pub variants: Vec<(String, u32, String)>,
|
||||||
/// Whether typescript bindings should be generated for this enum.
|
/// Whether typescript bindings should be generated for this enum.
|
||||||
pub generate_typescript: bool,
|
pub generate_typescript: bool,
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
use std::char;
|
||||||
|
use std::str::Chars;
|
||||||
|
|
||||||
use ast::OperationKind;
|
use ast::OperationKind;
|
||||||
use backend::ast;
|
use backend::ast;
|
||||||
@ -1131,9 +1133,11 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let comments = extract_doc_comments(&v.attrs);
|
||||||
Ok(ast::Variant {
|
Ok(ast::Variant {
|
||||||
name: v.ident.clone(),
|
name: v.ident.clone(),
|
||||||
value,
|
value,
|
||||||
|
comments,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, Diagnostic>>()?;
|
.collect::<Result<Vec<_>, Diagnostic>>()?;
|
||||||
@ -1324,9 +1328,8 @@ fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
|
|||||||
// We want to filter out any Puncts so just grab the Literals
|
// We want to filter out any Puncts so just grab the Literals
|
||||||
a.tokens.clone().into_iter().filter_map(|t| match t {
|
a.tokens.clone().into_iter().filter_map(|t| match t {
|
||||||
TokenTree::Literal(lit) => {
|
TokenTree::Literal(lit) => {
|
||||||
// this will always return the quoted string, we deal with
|
let quoted = lit.to_string();
|
||||||
// that in the cli when we read in the comments
|
Some(try_unescape("ed).unwrap_or_else(|| quoted))
|
||||||
Some(lit.to_string())
|
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
@ -1342,6 +1345,76 @@ fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unescapes a quoted string. char::escape_debug() was used to escape the text.
|
||||||
|
fn try_unescape(s: &str) -> Option<String> {
|
||||||
|
if s.is_empty() {
|
||||||
|
return Some(String::new());
|
||||||
|
}
|
||||||
|
let mut result = String::with_capacity(s.len());
|
||||||
|
let mut chars = s.chars();
|
||||||
|
for i in 0.. {
|
||||||
|
let c = match chars.next() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => {
|
||||||
|
if result.ends_with('"') {
|
||||||
|
result.pop();
|
||||||
|
}
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if i == 0 && c == '"' {
|
||||||
|
// ignore it
|
||||||
|
} else if c == '\\' {
|
||||||
|
let c = chars.next()?;
|
||||||
|
match c {
|
||||||
|
't' => result.push('\t'),
|
||||||
|
'r' => result.push('\r'),
|
||||||
|
'n' => result.push('\n'),
|
||||||
|
'\\' | '\'' | '"' => result.push(c),
|
||||||
|
'u' => {
|
||||||
|
if chars.next() != Some('{') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (c, next) = unescape_unicode(&mut chars)?;
|
||||||
|
result.push(c);
|
||||||
|
if next != '}' {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
|
||||||
|
let mut value = 0;
|
||||||
|
for i in 0..7 {
|
||||||
|
let c = chars.next()?;
|
||||||
|
let num = if c >= '0' && c <= '9' {
|
||||||
|
c as u32 - '0' as u32
|
||||||
|
} else if c >= 'a' && c <= 'f' {
|
||||||
|
c as u32 - 'a' as u32 + 10
|
||||||
|
} else if c >= 'A' && c <= 'F' {
|
||||||
|
c as u32 - 'A' as u32 + 10
|
||||||
|
} else {
|
||||||
|
if i == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let decoded = char::from_u32(value)?;
|
||||||
|
return Some((decoded, c));
|
||||||
|
};
|
||||||
|
if i >= 6 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
value = (value << 4) | num;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Check there are no lifetimes on the function.
|
/// Check there are no lifetimes on the function.
|
||||||
fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> {
|
fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> {
|
||||||
struct Walk {
|
struct Walk {
|
||||||
|
@ -106,6 +106,7 @@ macro_rules! shared_api {
|
|||||||
struct EnumVariant<'a> {
|
struct EnumVariant<'a> {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
value: u32,
|
value: u32,
|
||||||
|
comments: Vec<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Function<'a> {
|
struct Function<'a> {
|
||||||
|
@ -4,8 +4,16 @@ const assert = require('assert');
|
|||||||
exports.assert_comments_exist = function() {
|
exports.assert_comments_exist = function() {
|
||||||
const bindings_file = require.resolve('wasm-bindgen-test');
|
const bindings_file = require.resolve('wasm-bindgen-test');
|
||||||
const contents = fs.readFileSync(bindings_file);
|
const contents = fs.readFileSync(bindings_file);
|
||||||
assert.ok(contents.includes("* annotated function"));
|
assert.ok(contents.includes("* annotated function ✔️ \" \\ ' {"));
|
||||||
assert.ok(contents.includes("* annotated struct type"));
|
assert.ok(contents.includes("* annotated struct type"));
|
||||||
assert.ok(contents.includes("* annotated struct field"));
|
assert.ok(contents.includes("* annotated struct field b"));
|
||||||
|
assert.ok(contents.includes("* annotated struct field c"));
|
||||||
|
assert.ok(contents.includes("* annotated struct constructor"));
|
||||||
assert.ok(contents.includes("* annotated struct method"));
|
assert.ok(contents.includes("* annotated struct method"));
|
||||||
|
assert.ok(contents.includes("* annotated struct getter"));
|
||||||
|
assert.ok(contents.includes("* annotated struct setter"));
|
||||||
|
assert.ok(contents.includes("* annotated struct static method"));
|
||||||
|
assert.ok(contents.includes("* annotated enum type"));
|
||||||
|
assert.ok(contents.includes("* annotated enum variant 1"));
|
||||||
|
assert.ok(contents.includes("* annotated enum variant 2"));
|
||||||
};
|
};
|
||||||
|
@ -6,26 +6,65 @@ extern "C" {
|
|||||||
fn assert_comments_exist();
|
fn assert_comments_exist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// annotated function ✔️ " \ ' {
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
/// annotated function
|
|
||||||
pub fn annotated() -> String {
|
pub fn annotated() -> String {
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
/// annotated struct type
|
/// annotated struct type
|
||||||
|
#[wasm_bindgen]
|
||||||
pub struct Annotated {
|
pub struct Annotated {
|
||||||
a: String,
|
a: String,
|
||||||
/// annotated struct field
|
/// annotated struct field b
|
||||||
pub b: u32,
|
pub b: u32,
|
||||||
|
/// annotated struct field c
|
||||||
|
#[wasm_bindgen(readonly)]
|
||||||
|
pub c: u32,
|
||||||
|
d: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Annotated {
|
impl Annotated {
|
||||||
|
/// annotated struct constructor
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
a: String::new(),
|
||||||
|
b: 0,
|
||||||
|
c: 0,
|
||||||
|
d: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// annotated struct method
|
/// annotated struct method
|
||||||
pub fn get_a(&self) -> String {
|
pub fn get_a(&self) -> String {
|
||||||
self.a.clone()
|
self.a.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// annotated struct getter
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn d(&self) -> u32 {
|
||||||
|
self.d
|
||||||
|
}
|
||||||
|
|
||||||
|
/// annotated struct setter
|
||||||
|
#[wasm_bindgen(setter)]
|
||||||
|
pub fn set_d(&mut self, value: u32) {
|
||||||
|
self.d = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// annotated struct static method
|
||||||
|
pub fn static_method() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// annotated enum type
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub enum AnnotatedEnum {
|
||||||
|
/// annotated enum variant 1
|
||||||
|
Variant1,
|
||||||
|
/// annotated enum variant 2
|
||||||
|
Variant2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user