feat(napi): output Rust doc comments in definitions as jsdoc comments

This commit is contained in:
Tim Fish 2021-11-29 04:54:45 +00:00 committed by GitHub
parent a25f0b990c
commit 18d2743862
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 251 additions and 73 deletions

View file

@ -329,6 +329,7 @@ interface TypeDef {
name: string
def: string
js_mod?: string
js_doc: string
}
async function processIntermediateTypeFile(
@ -358,49 +359,65 @@ export class ExternalObject<T> {
const allDefs = lines.map((line) => JSON.parse(line) as TypeDef)
function convertDefs(defs: TypeDef[], nested = false): string {
const classes = new Map<string, string>()
const classes = new Map<string, { def: string; js_doc: string }>()
const impls = new Map<string, string>()
let dts = ''
const lineStart = nested ? ' ' : ''
const nest = nested ? 2 : 0
defs.forEach((def) => {
switch (def.kind) {
case 'struct':
if (!nested) {
idents.push(def.name)
}
classes.set(def.name, def.def)
classes.set(def.name, { def: def.def, js_doc: def.js_doc })
break
case 'impl':
impls.set(def.name, def.def)
impls.set(def.name, `${def.js_doc}${def.def}`)
break
case 'interface':
dts += `${lineStart}interface ${def.name} {\n${indentLines(
def.def,
nested ? 4 : 2,
)}\n}\n`
dts +=
indentLines(`${def.js_doc}export interface ${def.name} {`, nest) +
'\n'
dts += indentLines(def.def, nest + 2) + '\n'
dts += indentLines(`}`, nest) + '\n'
break
case 'enum':
dts +=
indentLines(`${def.js_doc}export enum ${def.name} {`, nest) + '\n'
dts += indentLines(def.def, nest + 2) + '\n'
dts += indentLines(`}`, nest) + '\n'
break
default:
if (!nested) {
idents.push(def.name)
}
dts += lineStart + def.def + '\n'
dts += indentLines(`${def.js_doc}${def.def}`, nest) + '\n'
}
})
for (const [name, classDef] of classes.entries()) {
for (const [name, { js_doc, def }] of classes.entries()) {
const implDef = impls.get(name)
dts += `${lineStart}export class ${name} {\n${indentLines(
classDef,
nested ? 4 : 2,
)}`
dts += indentLines(`${js_doc}export class ${name} {`, nest)
if (def) {
dts += '\n' + indentLines(def, nest + 2)
}
if (implDef) {
dts += `\n${indentLines(implDef, nested ? 4 : 2)}`
dts += '\n' + indentLines(implDef, nest + 2)
}
dts += `\n${lineStart}}\n`
if (def || implDef) {
dts += '\n'
} else {
dts += ` `
}
dts += indentLines(`}`, nest) + '\n'
}
return dts
}
@ -413,12 +430,7 @@ export class ExternalObject<T> {
),
).reduce((acc, [mod, defs]) => {
idents.push(mod)
return (
acc +
`export namespace ${mod} {
${convertDefs(defs, true)}
}\n`
)
return acc + `export namespace ${mod} {\n${convertDefs(defs, true)}}\n`
}, '')
await unlinkAsync(source)
@ -429,7 +441,11 @@ ${convertDefs(defs, true)}
function indentLines(input: string, spaces: number) {
return input
.split('\n')
.map((line) => ''.padEnd(spaces, ' ') + line.trim())
.map(
(line) =>
''.padEnd(spaces, ' ') +
(line.startsWith(' *') ? line.trimEnd() : line.trim()),
)
.join('\n')
}
@ -442,9 +458,10 @@ async function writeJsBinding(
if (distFileName) {
const template = createJsBinding(localName, packageName)
const declareCodes = `const { ${idents.join(', ')} } = nativeBinding\n`
const exportsCode = idents.reduce((acc, cur) => {
return `${acc}\nmodule.exports.${cur} = ${cur}`
}, '')
const exportsCode = idents.reduce(
(acc, cur) => `${acc}\nmodule.exports.${cur} = ${cur}`,
'',
)
await writeFileAsync(
distFileName,
template + declareCodes + exportsCode + '\n',

View file

@ -18,6 +18,7 @@ pub struct NapiFn {
pub js_mod: Option<String>,
pub ts_args_type: Option<String>,
pub ts_return_type: Option<String>,
pub comments: Vec<String>,
}
#[derive(Debug, Clone)]
@ -58,6 +59,7 @@ pub struct NapiStruct {
pub is_tuple: bool,
pub kind: NapiStructKind,
pub js_mod: Option<String>,
pub comments: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
@ -74,6 +76,7 @@ pub struct NapiStructField {
pub ty: syn::Type,
pub getter: bool,
pub setter: bool,
pub comments: Vec<String>,
}
#[derive(Debug, Clone)]
@ -83,6 +86,7 @@ pub struct NapiImpl {
pub items: Vec<NapiFn>,
pub task_output_type: Option<Type>,
pub js_mod: Option<String>,
pub comments: Vec<String>,
}
#[derive(Debug, Clone)]
@ -91,6 +95,7 @@ pub struct NapiEnum {
pub js_name: String,
pub variants: Vec<NapiEnumVariant>,
pub js_mod: Option<String>,
pub comments: Vec<String>,
}
#[derive(Debug, Clone)]
@ -107,6 +112,7 @@ pub struct NapiConst {
pub type_name: Type,
pub value: Expr,
pub js_mod: Option<String>,
pub comments: Vec<String>,
}
#[derive(Debug, Clone)]

View file

@ -18,7 +18,6 @@ pub use typegen::*;
#[derive(Debug)]
pub struct Napi {
pub comments: Vec<String>,
pub item: NapiItem,
}

View file

@ -15,6 +15,51 @@ pub struct TypeDef {
pub name: String,
pub def: String,
pub js_mod: Option<String>,
pub js_doc: String,
}
pub fn js_doc_from_comments(comments: &[String]) -> String {
if comments.is_empty() {
return "".to_owned();
}
if comments.len() == 1 {
return format!("/**{} */\n", comments[0]);
}
format!(
"/**\n{} */\n",
comments
.iter()
.map(|c| format!(" *{}\n", c))
.collect::<Vec<String>>()
.join("")
)
}
fn escape_json(src: &str) -> String {
use std::fmt::Write;
let mut escaped = String::with_capacity(src.len());
let mut utf16_buf = [0u16; 2];
for c in src.chars() {
match c {
'\x08' => escaped += "\\b",
'\x0c' => escaped += "\\f",
'\n' => escaped += "\\n",
'\r' => escaped += "\\r",
'\t' => escaped += "\\t",
'"' => escaped += "\\\"",
'\\' => escaped += "\\",
c if c.is_ascii_graphic() => escaped.push(c),
c => {
let encoded = c.encode_utf16(&mut utf16_buf);
for utf16 in encoded {
write!(&mut escaped, "\\u{:04X}", utf16).unwrap();
}
}
}
}
escaped
}
impl ToString for TypeDef {
@ -25,8 +70,12 @@ impl ToString for TypeDef {
"".to_owned()
};
format!(
r#"{{"kind": "{}", "name": "{}", "def": "{}"{}}}"#,
self.kind, self.name, self.def, js_mod,
r#"{{"kind": "{}", "name": "{}", "js_doc": "{}", "def": "{}"{}}}"#,
self.kind,
self.name,
escape_json(&self.js_doc),
escape_json(&self.def),
js_mod,
)
}
}

View file

@ -1,6 +1,6 @@
use super::{ToTypeDef, TypeDef};
use crate::{ty_to_ts_type, NapiConst};
use crate::{js_doc_from_comments, ty_to_ts_type, NapiConst};
impl ToTypeDef for NapiConst {
fn to_type_def(&self) -> TypeDef {
@ -13,6 +13,7 @@ impl ToTypeDef for NapiConst {
ty_to_ts_type(&self.type_name, false).0
),
js_mod: self.js_mod.to_owned(),
js_doc: js_doc_from_comments(&self.comments),
}
}
}

View file

@ -1,16 +1,13 @@
use super::{ToTypeDef, TypeDef};
use crate::NapiEnum;
use crate::{js_doc_from_comments, NapiEnum};
impl ToTypeDef for NapiEnum {
fn to_type_def(&self) -> TypeDef {
TypeDef {
kind: "enum".to_owned(),
name: self.js_name.to_owned(),
def: format!(
r"export enum {js_name} {{ {variants} }}",
js_name = &self.js_name,
variants = self.gen_ts_variants()
),
def: self.gen_ts_variants(),
js_doc: js_doc_from_comments(&self.comments),
js_mod: self.js_mod.to_owned(),
}
}
@ -21,8 +18,15 @@ impl NapiEnum {
self
.variants
.iter()
.map(|v| format!("{} = {}", v.name, v.val))
.map(|v| {
format!(
"{}{} = {}",
js_doc_from_comments(&v.comments),
v.name,
v.val,
)
})
.collect::<Vec<_>>()
.join(", ")
.join(",\n ")
}
}

View file

@ -3,7 +3,7 @@ use quote::ToTokens;
use syn::Pat;
use super::{ty_to_ts_type, ToTypeDef, TypeDef};
use crate::{CallbackArg, FnKind, NapiFn};
use crate::{js_doc_from_comments, CallbackArg, FnKind, NapiFn};
impl ToTypeDef for NapiFn {
fn to_type_def(&self) -> TypeDef {
@ -27,6 +27,7 @@ impl ToTypeDef for NapiFn {
name: self.js_name.clone(),
def,
js_mod: self.js_mod.to_owned(),
js_doc: js_doc_from_comments(&self.comments),
}
}
}

View file

@ -7,6 +7,7 @@ impl ToTypeDef for NapiMod {
name: self.js_name.clone(),
def: "".to_owned(),
js_mod: None,
js_doc: "".to_owned(),
}
}
}

View file

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::collections::HashMap;
use super::{ToTypeDef, TypeDef};
use crate::{ty_to_ts_type, NapiImpl, NapiStruct, NapiStructKind};
use crate::{js_doc_from_comments, ty_to_ts_type, NapiImpl, NapiStruct, NapiStructKind};
thread_local! {
pub(crate) static TASK_STRUCTS: RefCell<HashMap<String, String>> = Default::default();
@ -24,6 +24,7 @@ impl ToTypeDef for NapiStruct {
name: self.js_name.to_owned(),
def: self.gen_ts_class(),
js_mod: self.js_mod.to_owned(),
js_doc: js_doc_from_comments(&self.comments),
}
}
}
@ -42,10 +43,17 @@ impl ToTypeDef for NapiImpl {
def: self
.items
.iter()
.map(|f| f.to_type_def().def)
.map(|f| {
format!(
"{}{}",
js_doc_from_comments(&f.comments),
f.to_type_def().def
)
})
.collect::<Vec<_>>()
.join("\\n"),
js_mod: self.js_mod.to_owned(),
js_doc: "".to_string(),
}
}
}
@ -60,6 +68,10 @@ impl NapiStruct {
.map(|f| {
let mut field_str = String::from("");
if !f.comments.is_empty() {
field_str.push_str(&js_doc_from_comments(&f.comments))
}
if !f.setter {
field_str.push_str("readonly ")
}

View file

@ -572,6 +572,7 @@ fn napi_fn_from_decl(
kind: fn_kind(opts),
fn_self,
parent: parent.cloned(),
comments: extract_doc_comments(&attrs),
attrs,
strict: opts.strict().is_some(),
js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
@ -693,7 +694,6 @@ impl ConvertToAST for syn::ItemFn {
)?;
Ok(Napi {
comments: vec![],
item: NapiItem::Fn(func),
})
}
@ -758,12 +758,13 @@ impl ConvertToAST for syn::ItemStruct {
ty: field.ty.clone(),
getter: !ignored,
setter: !(ignored || readonly),
comments: extract_doc_comments(&field.attrs),
})
}
record_struct(&struct_name, js_name.clone(), &opts);
Diagnostic::from_vec(errors).map(|()| Napi {
comments: vec![],
item: NapiItem::Struct(NapiStruct {
js_name,
name: struct_name,
@ -772,6 +773,7 @@ impl ConvertToAST for syn::ItemStruct {
is_tuple,
kind: struct_kind,
js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
comments: extract_doc_comments(&self.attrs),
}),
})
}
@ -842,13 +844,13 @@ impl ConvertToAST for syn::ItemImpl {
}
Ok(Napi {
comments: vec![],
item: NapiItem::Impl(NapiImpl {
name: struct_name,
js_name: struct_js_name,
items,
task_output_type,
js_mod: impl_opts.namespace().map(|(m, _)| m.to_owned()),
comments: extract_doc_comments(&self.attrs),
}),
})
}
@ -925,24 +927,22 @@ impl ConvertToAST for syn::ItemEnum {
};
last_variant_val = val;
let comments = extract_doc_comments(&v.attrs);
Ok(NapiEnumVariant {
name: v.ident.clone(),
val,
comments,
comments: extract_doc_comments(&v.attrs),
})
})
.collect::<BindgenResult<Vec<NapiEnumVariant>>>()?;
let comments = extract_doc_comments(&self.attrs);
Ok(Napi {
comments,
item: NapiItem::Enum(NapiEnum {
name: self.ident.clone(),
js_name,
variants,
js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
comments: extract_doc_comments(&self.attrs),
}),
})
}
@ -952,7 +952,6 @@ impl ConvertToAST for syn::ItemConst {
fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult<Napi> {
match self.vis {
Visibility::Public(_) => Ok(Napi {
comments: vec![],
item: NapiItem::Const(NapiConst {
name: self.ident.clone(),
js_name: opts
@ -961,6 +960,7 @@ impl ConvertToAST for syn::ItemConst {
type_name: *self.ty.clone(),
value: *self.expr.clone(),
js_mod: opts.namespace().map(|(m, _)| m.to_owned()),
comments: extract_doc_comments(&self.attrs),
}),
}),
_ => bail_span!(self, "only public const allowed"),

View file

@ -16,8 +16,10 @@ Generated by [AVA](https://avajs.dev).
[K: symbol]: T␊
}␊
}␊
/** This is a const */␊
export const DEFAULT_COST: number␊
export function getWords(): Array<string>
/** Gets some numbers */␊
export function getNums(): Array<number>
export function sumNums(nums: Array<number>): number␊
export function readFileAsync(path: string): Promise<Buffer>
@ -26,16 +28,35 @@ Generated by [AVA](https://avajs.dev).
export function createBigInt(): BigInt␊
export function createBigIntI64(): BigInt␊
export function getCwd(callback: (arg0: string) => void): void␊
/** napi = { version = 2, features = ["serde-json"] } */␊
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊
export function eitherStringOrNumber(input: string | number): number␊
export function returnEither(input: number): string | number␊
export function either3(input: string | number | boolean): number␊
interface Obj {␊
export interface Obj {␊
v: string | number␊
}␊
export function either4(input: string | number | boolean | Obj): number␊
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }␊
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }␊
/** default enum values are continuos i32s start from 0 */␊
export enum Kind {␊
/** Barks */␊
Dog = 0,␊
/** Kills birds */␊
Cat = 1,␊
/** Tasty */␊
Duck = 2␊
}␊
/** You could break the step and for an new continuous value. */␊
export enum CustomNumEnum {␊
One = 1,␊
Two = 2,␊
Three = 3,␊
Four = 4,␊
Six = 6,␊
Eight = 8,␊
Nine = 9,␊
Ten = 10␊
}␊
export function enumToI32(e: CustomNumEnum): number␊
export function throwError(): void␊
export function createExternal(size: number): ExternalObject<number>
@ -53,8 +74,10 @@ Generated by [AVA](https://avajs.dev).
export function getUndefined(): void␊
export function getNull(): JsNull␊
export function asyncPlus100(p: Promise<number>): Promise<number>
interface PackageJson {␊
/** This is an interface for package.json */␊
export interface PackageJson {␊
name: string␊
/** The version of the package */␊
version: string␊
dependencies?: Record<string, any> | undefined | null␊
devDependencies?: Record<string, any> | undefined | null␊
@ -73,22 +96,34 @@ Generated by [AVA](https://avajs.dev).
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void␊
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void␊
export function getBuffer(): Buffer␊
/**␊
* \`constructor\` option for \`struct\` requires all fields to be public,␊
* otherwise tag impl fn as constructor␊
* #[napi(constructor)]␊
*/␊
export class Animal {␊
/** Kind of animal */␊
readonly kind: Kind␊
/** This is the constructor */␊
constructor(kind: Kind, name: string)␊
/** This is a factory method */␊
static withKind(kind: Kind): Animal␊
get name(): string␊
set name(name: string)␊
/**␊
* This is a␊
* multi-line comment␊
* with an emoji 🚀␊
*/␊
whoami(): string␊
/** This is static... */␊
static getDogKind(): Kind␊
}␊
/** Smoking test for type generation */␊
export class Blake2BHasher {␊
static withKey(key: Blake2bKey): Blake2BHasher␊
}␊
export class Blake2BKey {␊
}␊
export class Blake2BKey { }␊
export class Context {␊
maybeNeed?: boolean | undefined | null␊
constructor()␊
@ -103,18 +138,18 @@ Generated by [AVA](https://avajs.dev).
export namespace xxh3 {␊
export const ALIGNMENT: number␊
export function xxh3_64(input: Buffer): BigInt␊
/** xxh128 function */␊
export function xxh128(input: Buffer): BigInt␊
/** Xxh3 class */␊
export class Xxh3 {␊
constructor()␊
/** update */␊
update(input: Buffer): void␊
digest(): BigInt␊
}␊
}␊
export namespace xxh2 {␊
export function xxh2Plus(a: number, b: number): number␊
export function xxh3Xxh64Alias(input: Buffer): BigInt␊
}␊
`

View file

@ -6,8 +6,10 @@ export class ExternalObject<T> {
[K: symbol]: T
}
}
/** This is a const */
export const DEFAULT_COST: number
export function getWords(): Array<string>
/** Gets some numbers */
export function getNums(): Array<number>
export function sumNums(nums: Array<number>): number
export function readFileAsync(path: string): Promise<Buffer>
@ -16,16 +18,35 @@ export function bigintAdd(a: BigInt, b: BigInt): BigInt
export function createBigInt(): BigInt
export function createBigIntI64(): BigInt
export function getCwd(callback: (arg0: string) => void): void
/** napi = { version = 2, features = ["serde-json"] } */
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void
export function eitherStringOrNumber(input: string | number): number
export function returnEither(input: number): string | number
export function either3(input: string | number | boolean): number
interface Obj {
export interface Obj {
v: string | number
}
export function either4(input: string | number | boolean | Obj): number
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }
/** default enum values are continuos i32s start from 0 */
export enum Kind {
/** Barks */
Dog = 0,
/** Kills birds */
Cat = 1,
/** Tasty */
Duck = 2
}
/** You could break the step and for an new continuous value. */
export enum CustomNumEnum {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Six = 6,
Eight = 8,
Nine = 9,
Ten = 10
}
export function enumToI32(e: CustomNumEnum): number
export function throwError(): void
export function createExternal(size: number): ExternalObject<number>
@ -43,8 +64,10 @@ export function getGlobal(): typeof global
export function getUndefined(): void
export function getNull(): JsNull
export function asyncPlus100(p: Promise<number>): Promise<number>
interface PackageJson {
/** This is an interface for package.json */
export interface PackageJson {
name: string
/** The version of the package */
version: string
dependencies?: Record<string, any> | undefined | null
devDependencies?: Record<string, any> | undefined | null
@ -63,22 +86,34 @@ export function callThreadsafeFunction(callback: (...args: any[]) => any): void
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void
export function getBuffer(): Buffer
/**
* `constructor` option for `struct` requires all fields to be public,
* otherwise tag impl fn as constructor
* #[napi(constructor)]
*/
export class Animal {
/** Kind of animal */
readonly kind: Kind
/** This is the constructor */
constructor(kind: Kind, name: string)
/** This is a factory method */
static withKind(kind: Kind): Animal
get name(): string
set name(name: string)
/**
* This is a
* multi-line comment
* with an emoji 🚀
*/
whoami(): string
/** This is static... */
static getDogKind(): Kind
}
/** Smoking test for type generation */
export class Blake2BHasher {
static withKey(key: Blake2bKey): Blake2BHasher
}
export class Blake2BKey {
}
export class Blake2BKey { }
export class Context {
maybeNeed?: boolean | undefined | null
constructor()
@ -93,17 +128,17 @@ export class ClassWithFactory {
export namespace xxh3 {
export const ALIGNMENT: number
export function xxh3_64(input: Buffer): BigInt
/** xxh128 function */
export function xxh128(input: Buffer): BigInt
/** Xxh3 class */
export class Xxh3 {
constructor()
/** update */
update(input: Buffer): void
digest(): BigInt
}
}
export namespace xxh2 {
export function xxh2Plus(a: number, b: number): number
export function xxh3Xxh64Alias(input: Buffer): BigInt
}

View file

@ -4,6 +4,7 @@ fn get_words() -> Vec<&'static str> {
}
#[napi]
/// Gets some numbers
fn get_nums() -> Vec<u32> {
vec![1, 1, 2, 3, 5, 8]
}

View file

@ -8,17 +8,21 @@ use crate::r#enum::Kind;
#[napi]
pub struct Animal {
#[napi(readonly)]
/// Kind of animal
pub kind: Kind,
name: String,
}
#[napi]
impl Animal {
/// This is the constructor
#[napi(constructor)]
pub fn new(kind: Kind, name: String) -> Self {
Animal { kind, name }
}
/// This is a factory method
#[napi(factory)]
pub fn with_kind(kind: Kind) -> Self {
Animal {
@ -37,6 +41,9 @@ impl Animal {
self.name = name;
}
/// This is a
/// multi-line comment
/// with an emoji 🚀
#[napi]
pub fn whoami(&self) -> String {
match self.kind {
@ -49,6 +56,7 @@ impl Animal {
}
#[napi]
/// This is static...
pub fn get_dog_kind() -> Kind {
Kind::Dog
}

View file

@ -3,8 +3,11 @@ use napi::bindgen_prelude::*;
/// default enum values are continuos i32s start from 0
#[napi]
pub enum Kind {
/// Barks
Dog,
/// Kills birds
Cat,
/// Tasty
Duck,
}

View file

@ -15,6 +15,7 @@ mod xxh3 {
}
#[napi]
/// xxh128 function
pub fn xxh128(input: Buffer) -> u128 {
let mut h: u128 = 0;
for i in input.as_ref() {
@ -24,6 +25,7 @@ mod xxh3 {
}
#[napi]
/// Xxh3 class
pub struct Xxh3 {
inner: BigInt,
}
@ -41,6 +43,7 @@ mod xxh3 {
}
#[napi]
/// update
pub fn update(&mut self, input: Buffer) {
for i in input.as_ref() {
self.inner = BigInt {

View file

@ -4,6 +4,7 @@ extern crate napi_derive;
extern crate serde_derive;
#[napi]
/// This is a const
pub const DEFAULT_COST: u32 = 12;
mod array;

View file

@ -4,8 +4,10 @@ use std::fs;
#[napi(object)]
#[derive(Serialize, Deserialize, Debug)]
/// This is an interface for package.json
struct PackageJson {
pub name: String,
/// The version of the package
pub version: String,
pub dependencies: Option<Map<String, Value>>,
#[serde(rename = "devDependencies")]