Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smarter enhancement of JSDoc comments with a JSDoc parser #4310

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 37 additions & 31 deletions crates/cli-support/src/js/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! exported functions, table elements, imports, etc. All function shims
//! generated by `wasm-bindgen` run through this type.

use crate::js::jsdoc::{JsDocTag, Optionality, ParamTag, ReturnsTag};
use crate::js::Context;
use crate::wit::InstructionData;
use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction};
Expand Down Expand Up @@ -69,7 +70,7 @@ pub struct JsBuilder<'a, 'b> {
pub struct JsFunction {
pub code: String,
pub ts_sig: String,
pub js_doc: String,
pub js_doc: Vec<JsDocTag>,
pub ts_arg_tys: Vec<String>,
pub ts_ret_ty: Option<String>,
pub ts_refs: HashSet<TsReference>,
Expand Down Expand Up @@ -276,7 +277,7 @@ impl<'a, 'b> Builder<'a, 'b> {
let js_doc = if generate_jsdoc {
self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty, variadic)
} else {
String::new()
Vec::new()
};

Ok(JsFunction {
Expand Down Expand Up @@ -383,50 +384,55 @@ impl<'a, 'b> Builder<'a, 'b> {
arg_tys: &[&AdapterType],
ts_ret: &Option<String>,
variadic: bool,
) -> String {
) -> Vec<JsDocTag> {
let (variadic_arg, fn_arg_names) = match arg_names.split_last() {
Some((last, args)) if variadic => (Some(last), args),
_ => (None, arg_names),
};

let mut omittable = true;
let mut js_doc_args = Vec::new();
fn to_ts_type(ty: &AdapterType) -> String {
let mut ret = String::new();
adapter2ts(ty, &mut ret, None);
ret
}

for (name, ty) in fn_arg_names.iter().zip(arg_tys).rev() {
let mut arg = "@param {".to_string();
let mut tags = Vec::new();

adapter2ts(ty, &mut arg, None);
arg.push_str("} ");
match ty {
AdapterType::Option(..) if omittable => {
arg.push('[');
arg.push_str(name);
arg.push(']');
}
_ => {
omittable = false;
arg.push_str(name);
}
}
arg.push('\n');
js_doc_args.push(arg);
let mut omittable = true;
for (name, ty) in fn_arg_names.iter().zip(arg_tys).rev() {
tags.push(JsDocTag::Param(ParamTag {
ty: Some(to_ts_type(ty)),
name: name.to_string(),
optional: match ty {
AdapterType::Option(..) if omittable => Optionality::Optional,
_ => {
omittable = false;
Optionality::Required
}
},
description: String::new(),
}));
}

let mut ret: String = js_doc_args.into_iter().rev().collect();
tags.reverse();

if let (Some(name), Some(ty)) = (variadic_arg, arg_tys.last()) {
ret.push_str("@param {...");
adapter2ts(ty, &mut ret, None);
ret.push_str("} ");
ret.push_str(name);
ret.push('\n');
tags.push(JsDocTag::Param(ParamTag {
ty: Some(format!("...{}", to_ts_type(ty))),
name: name.to_string(),
optional: Optionality::Required,
description: String::new(),
}));
}
if let Some(ts) = ts_ret {
if ts != "void" {
ret.push_str(&format!("@returns {{{}}}", ts));
tags.push(JsDocTag::Returns(ReturnsTag {
ty: Some(ts.to_string()),
description: String::new(),
}));
}
}
ret

tags
}
}

Expand Down
48 changes: 48 additions & 0 deletions crates/cli-support/src/js/ident.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// Returns whether a character has the Unicode `ID_Start` properly.
///
/// This is only ever-so-slightly different from `XID_Start` in a few edge
/// cases, so we handle those edge cases manually and delegate everything else
/// to `unicode-ident`.
fn is_unicode_id_start(c: char) -> bool {
match c {
'\u{037A}' | '\u{0E33}' | '\u{0EB3}' | '\u{309B}' | '\u{309C}' | '\u{FC5E}'
| '\u{FC5F}' | '\u{FC60}' | '\u{FC61}' | '\u{FC62}' | '\u{FC63}' | '\u{FDFA}'
| '\u{FDFB}' | '\u{FE70}' | '\u{FE72}' | '\u{FE74}' | '\u{FE76}' | '\u{FE78}'
| '\u{FE7A}' | '\u{FE7C}' | '\u{FE7E}' | '\u{FF9E}' | '\u{FF9F}' => true,
_ => unicode_ident::is_xid_start(c),
}
}

/// Returns whether a character has the Unicode `ID_Continue` properly.
///
/// This is only ever-so-slightly different from `XID_Continue` in a few edge
/// cases, so we handle those edge cases manually and delegate everything else
/// to `unicode-ident`.
fn is_unicode_id_continue(c: char) -> bool {
match c {
'\u{037A}' | '\u{309B}' | '\u{309C}' | '\u{FC5E}' | '\u{FC5F}' | '\u{FC60}'
| '\u{FC61}' | '\u{FC62}' | '\u{FC63}' | '\u{FDFA}' | '\u{FDFB}' | '\u{FE70}'
| '\u{FE72}' | '\u{FE74}' | '\u{FE76}' | '\u{FE78}' | '\u{FE7A}' | '\u{FE7C}'
| '\u{FE7E}' => true,
_ => unicode_ident::is_xid_continue(c),
}
}

pub fn is_ident_start(char: char) -> bool {
is_unicode_id_start(char) || char == '$' || char == '_'
}
pub fn is_ident_continue(char: char) -> bool {
is_unicode_id_continue(char) || char == '$' || char == '\u{200C}' || char == '\u{200D}'
}

/// Returns whether a string is a valid JavaScript identifier.
/// Defined at https://tc39.es/ecma262/#prod-IdentifierName.
pub fn is_valid_ident(name: &str) -> bool {
name.chars().enumerate().all(|(i, char)| {
if i == 0 {
is_ident_start(char)
} else {
is_ident_continue(char)
}
})
}
Loading