#!/usr/bin/env node // @ts-ignore import jsdoc from 'jsdoc-api' import * as fs from 'fs' const firstTagContentRegex = /<\w>([^<]+)<\/\w>([^]*)/ const jsdocReturnRegex = /\* @return {(.*)}/ const jsdocTypeRegex = /\* @type {(.*)}/ const files = fs.readdirSync('./').filter(file => /(?/g /** * @param {string} s */ const toSafeHtml = s => s.replace(_ltregex, '<').replace(_rtregex, '>') const READMEcontent = fs.readFileSync('./README.md', 'utf8') jsdoc.explain({ files, configure: '.jsdoc.json' }).then(/** @param {Array} json */ json => { const strBuilder = [] /** * @type {Object, name: string, description: string }>} */ const modules = {} json.forEach(item => { if (item.meta && item.meta.filename) { const mod = modules[item.meta.filename] || (modules[item.meta.filename] = { items: [], name: item.meta.filename.slice(0, -3), description: '' }) if (item.kind === 'module') { mod.name = item.name mod.description = item.description || '' } else { mod.items.push(item) } } }) /** * @type {Object} */ const classDescriptions = {} for (const fileName in modules) { const mod = modules[fileName] const items = mod.items const desc = firstTagContentRegex.exec(mod.description) const descHead = desc ? desc[1] : '' const descRest = desc ? desc[2] : '' strBuilder.push(`
[lib0/${mod.name}] ${descHead}`) strBuilder.push(`
import * as ${mod.name} from 'lib0/${fileName.slice(0, -3)}'
`) if (descRest.length > 0) { strBuilder.push(descRest) } strBuilder.push('
') for (let i = 0; i < items.length; i++) { const item = items[i] if (!item.ignore && item.scope !== 'inner' && item.name[0] !== '_' && item.longname.indexOf('~') < 0) { // strBuilder.push(JSON.stringify(item)) // output json for debugging switch (item.kind) { case 'class': { if (item.params == null) { classDescriptions[item.longname] = item.classdesc break } } // eslint-disable-next-line case 'constant': { if (item.params == null && item.returns == null) { const typeEval = jsdocTypeRegex.exec(item.comment) strBuilder.push(`${item.longname.slice(7)}${typeEval ? (': ' + toSafeHtml(typeEval[1])) : ''}
`) if (item.description) { strBuilder.push(`
${item.description}
`) } break } } // eslint-disable-next-line case 'function': { /** * @param {string} name */ const getOriginalParamTypeDecl = name => { const regval = new RegExp('@param {(.*)} \\[?' + name + '\\]?[^\\w]*').exec(item.comment) return regval ? regval[1] : null } if (item.params == null && item.returns == null) { break } const paramVal = (item.params || []).map(/** @param {any} ret */ ret => `${ret.name}: ${getOriginalParamTypeDecl(ret.name) || ret.type.names.join('|')}`).join(', ') const evalReturnRegex = jsdocReturnRegex.exec(item.comment) const returnVal = evalReturnRegex ? `: ${evalReturnRegex[1]}` : (item.returns ? item.returns.map(/** @param {any} r */ r => r.type.names.join('|')).join('|') : '') strBuilder.push(`${item.kind === 'class' ? 'new ' : ''}${item.longname.slice(7)}(${toSafeHtml(paramVal)})${toSafeHtml(returnVal)}
`) const desc = item.description || item.classdesc || classDescriptions[item.longname] || null if (desc) { strBuilder.push(`
${desc}
`) } break } case 'member': { if (item.type) { strBuilder.push(`${item.longname.slice(7)}: ${toSafeHtml(/** @type {RegExpExecArray} */ (jsdocTypeRegex.exec(item.comment))[1])}
`) if (item.description) { strBuilder.push(`
${item.description}
`) } } } } } } strBuilder.push('
') strBuilder.push('
') } const replaceReadme = READMEcontent.replace(/
[^]*<\/details>/, strBuilder.join('\n')) fs.writeFileSync('./README.md', replaceReadme) })