WIP generating API reference docs with @microsoft/api-extractor
This commit is contained in:
39
api-extractor.json
Normal file
39
api-extractor.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||||
|
"mainEntryPointFilePath": "<projectFolder>/lib/client.d.ts",
|
||||||
|
"bundledPackages": [
|
||||||
|
"@elastic/*"
|
||||||
|
],
|
||||||
|
"apiReport": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"docModel": {
|
||||||
|
"enabled": true,
|
||||||
|
"apiJsonFilePath": "<projectFolder>/api-extractor/<unscopedPackageName>.api.json",
|
||||||
|
"includeForgottenExports": true
|
||||||
|
},
|
||||||
|
"dtsRollup": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"tsdocMetadata": {
|
||||||
|
"enabled": true,
|
||||||
|
"tsdocMetadataFilePath": "<projectFolder>/api-extractor/tsdoc-metadata.json"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"compilerMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extractorMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tsdocMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -57,6 +57,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@elastic/request-converter": "8.16.2",
|
"@elastic/request-converter": "8.16.2",
|
||||||
|
"@microsoft/api-extractor": "^7.47.11",
|
||||||
|
"@microsoft/api-extractor-model": "^7.29.8",
|
||||||
"@sinonjs/fake-timers": "github:sinonjs/fake-timers#0bfffc1",
|
"@sinonjs/fake-timers": "github:sinonjs/fake-timers#0bfffc1",
|
||||||
"@types/debug": "4.1.12",
|
"@types/debug": "4.1.12",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "0.7.34",
|
||||||
|
|||||||
371
scripts/docgen.mjs
Normal file
371
scripts/docgen.mjs
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import * as Extractor from '@microsoft/api-extractor-model'
|
||||||
|
|
||||||
|
const header = `////////
|
||||||
|
===========================================================================================================================
|
||||||
|
|| ||
|
||||||
|
|| ||
|
||||||
|
|| ||
|
||||||
|
|| ██████╗ ███████╗ █████╗ ██████╗ ███╗ ███╗███████╗ ||
|
||||||
|
|| ██╔══██╗██╔════╝██╔══██╗██╔══██╗████╗ ████║██╔════╝ ||
|
||||||
|
|| ██████╔╝█████╗ ███████║██║ ██║██╔████╔██║█████╗ ||
|
||||||
|
|| ██╔══██╗██╔══╝ ██╔══██║██║ ██║██║╚██╔╝██║██╔══╝ ||
|
||||||
|
|| ██║ ██║███████╗██║ ██║██████╔╝██║ ╚═╝ ██║███████╗ ||
|
||||||
|
|| ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚══════╝ ||
|
||||||
|
|| ||
|
||||||
|
|| ||
|
||||||
|
|| This file is autogenerated, DO NOT send pull requests that changes this file directly. ||
|
||||||
|
|| You should update the script that does the generation, which can be found in scripts/docgen.mjs. ||
|
||||||
|
|| ||
|
||||||
|
|| ||
|
||||||
|
|| ||
|
||||||
|
===========================================================================================================================
|
||||||
|
////////
|
||||||
|
|
||||||
|
++++
|
||||||
|
<style>
|
||||||
|
.lang-ts a.xref {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
++++
|
||||||
|
`
|
||||||
|
|
||||||
|
const linkedRefs = new Set()
|
||||||
|
const documented = new Set()
|
||||||
|
|
||||||
|
function nodesToText (nodes) {
|
||||||
|
let text = ''
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.kind === 'Paragraph') {
|
||||||
|
for (const pNode of node.nodes) {
|
||||||
|
if (pNode.text) {
|
||||||
|
text += pNode.text + ' '
|
||||||
|
} else if (pNode.kind === 'CodeSpan') {
|
||||||
|
text += '`' + pNode.code + '`'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = text.replace(/\s+/g, ' ')
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
const skippableReferences = [
|
||||||
|
'Record',
|
||||||
|
'URL',
|
||||||
|
'Array',
|
||||||
|
'Promise',
|
||||||
|
'inspect.custom',
|
||||||
|
'http.IncomingHttpHeaders',
|
||||||
|
]
|
||||||
|
|
||||||
|
function generatePropertyType (tokens) {
|
||||||
|
let code = ''
|
||||||
|
tokens.forEach(token => {
|
||||||
|
if (token.kind === 'Reference' && !skippableReferences.includes(token.text)) {
|
||||||
|
let { text } = token
|
||||||
|
if (text.startsWith('T.')) {
|
||||||
|
text = text.split('.')[1]
|
||||||
|
} else if (text.startsWith('TB.')) {
|
||||||
|
text = text.split('.')[1] + '_2'
|
||||||
|
}
|
||||||
|
linkedRefs.add(text)
|
||||||
|
code += `<<${text}>>`
|
||||||
|
} else {
|
||||||
|
code += token.text.replace(/\n/g, '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return code.replace(/^export (declare )?/, '').replace(/\s+/g, ' ').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDescription (comment) {
|
||||||
|
let code = ''
|
||||||
|
|
||||||
|
if (comment == null) return code
|
||||||
|
|
||||||
|
const { summarySection, customBlocks } = comment
|
||||||
|
|
||||||
|
if (summarySection != null || customBlocks != null) {
|
||||||
|
if (summarySection != null) {
|
||||||
|
const summary = nodesToText(summarySection.nodes)
|
||||||
|
code += `${summary}\n\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customBlocks != null) {
|
||||||
|
let defaultValue = ''
|
||||||
|
for (const block of customBlocks) {
|
||||||
|
if (block.blockTag.tagNameWithUpperCase === '@DEFAULTVALUE') {
|
||||||
|
defaultValue = nodesToText(block.content.nodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (defaultValue.length > 0) {
|
||||||
|
code += `Default value: ${defaultValue}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return code.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateApiFunction (spec) {
|
||||||
|
let code = `[[${spec.displayName}_${spec.overloadIndex ?? ''}]]\n`
|
||||||
|
code += '[source,ts,subs=+macro]\n'
|
||||||
|
code += '----\n'
|
||||||
|
code += generatePropertyType(spec.excerptTokens)
|
||||||
|
code += '\n'
|
||||||
|
code += '----\n'
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateInterface (spec) {
|
||||||
|
let code = `[[${spec.displayName}]]\n`
|
||||||
|
code += `== Interface ${spec.displayName}\n\n`
|
||||||
|
code += '[%autowidth]\n'
|
||||||
|
code += '|===\n'
|
||||||
|
code += '|Name |Type |Description\n\n'
|
||||||
|
|
||||||
|
for (const member of spec.members) {
|
||||||
|
if (member.propertyTypeExcerpt == null) continue
|
||||||
|
code += `|\`${member.displayName}\`\n`
|
||||||
|
code += `|${generatePropertyType(member.propertyTypeExcerpt.spannedTokens)}\n`
|
||||||
|
code += `|${generateDescription(member.tsdocComment, false)}\n`
|
||||||
|
code += '|===\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateClass(spec) {
|
||||||
|
let code = `[[${spec.displayName}]]\n`
|
||||||
|
code += `== ${spec.displayName}\n`
|
||||||
|
|
||||||
|
code += '\n=== Constructor\n\n'
|
||||||
|
const cons = spec.members.filter(m => m.kind === 'Constructor')
|
||||||
|
for (const con of cons) {
|
||||||
|
code += '[source,ts,subs=+macros]\n'
|
||||||
|
code += '----\n'
|
||||||
|
code += generatePropertyType(con.excerptTokens).replace(/^constructor/, `new ${spec.displayName}`)
|
||||||
|
code += '\n'
|
||||||
|
code += '----\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate properties
|
||||||
|
const props = spec.members.filter(m => m.kind === 'Property')
|
||||||
|
if (props.length > 0) {
|
||||||
|
code += '\n=== Properties\n'
|
||||||
|
code += '[%autowidth]\n'
|
||||||
|
code += '|===\n'
|
||||||
|
code += '|Name |Type |Description\n\n'
|
||||||
|
for (const prop of props) {
|
||||||
|
if (prop.propertyTypeExcerpt == null) continue
|
||||||
|
if (prop.displayName.startsWith('[k')) continue
|
||||||
|
|
||||||
|
code += `|\`${prop.displayName}\`\n`
|
||||||
|
code += `|${generatePropertyType(prop.propertyTypeExcerpt.spannedTokens)}\n`
|
||||||
|
code += `|${generateDescription(prop.tsdocComment, false)}\n`
|
||||||
|
code += '|===\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate methods
|
||||||
|
const methods = spec.members.filter(m => m.kind === 'Method')
|
||||||
|
if (methods.length > 0) {
|
||||||
|
code += '\n=== Methods\n'
|
||||||
|
code += '[%autowidth]\n'
|
||||||
|
code += '|===\n'
|
||||||
|
code += '|Name |Signature |Description\n\n'
|
||||||
|
for (const method of methods) {
|
||||||
|
code += `|\`${method.displayName}\`\n`
|
||||||
|
code += `|\`${generatePropertyType(method.excerptTokens)}\`\n`
|
||||||
|
code += `|${generateDescription(method.tsdocComment, false)}\n`
|
||||||
|
code += '|===\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateAlias(spec) {
|
||||||
|
let code = `[[${spec.displayName}]]\n`
|
||||||
|
code += '[discrete]\n'
|
||||||
|
code += `== \`${spec.displayName}\`\n`
|
||||||
|
code += '[source,ts,subs=+macros]'
|
||||||
|
code += '----\n'
|
||||||
|
code += `${generatePropertyType(spec.excerpt.tokens)}\n`
|
||||||
|
code += '----\n'
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates documentation for ClientOptions interface
|
||||||
|
* @param spec {Extractor.ApiItem}
|
||||||
|
* @returns {string} Asciidoc markup
|
||||||
|
*/
|
||||||
|
// function generateClientOptions (spec) {
|
||||||
|
// let code = `[reference-client-options-interface]\n\n== ClientOptions\n\n${header}\n\n`
|
||||||
|
// code += `[[${spec.displayName}]]\n`
|
||||||
|
// code += `=== ${spec.displayName}\n\n`
|
||||||
|
// code += generateInterface(spec)
|
||||||
|
// return code
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const standardTypes = {
|
||||||
|
// 'TlsConnectionOptions': 'https://nodejs.org/api/tls.html#tlsconnectoptions-callback[Node.js TLS connection options]',
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param spec {Extractor.ApiItem}
|
||||||
|
* @param model {Extractor.ApiModel}
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
// function generateClientOptionsReference (spec, model) {
|
||||||
|
// let code = `${header}\n\n`
|
||||||
|
// for (const member of spec.members) {
|
||||||
|
// for (const token of member.excerptTokens) {
|
||||||
|
// if (token.kind === 'Reference' && !documented.has(token.text)) {
|
||||||
|
// documented.add(token.text)
|
||||||
|
// code += `[discrete]\n`
|
||||||
|
// code += `[[${token.text}]]\n`
|
||||||
|
// code += `=== ${token.text}\n\n`
|
||||||
|
//
|
||||||
|
// const item = model.packages[0].entryPoints[0].members.find(member => member.displayName === token.text)
|
||||||
|
// if (item != null) {
|
||||||
|
// code += generateDescription(item.tsdocComment, false)
|
||||||
|
// switch (item.kind) {
|
||||||
|
// case 'Interface':
|
||||||
|
// code += generateInterface(item)
|
||||||
|
// break;
|
||||||
|
// case 'TypeAlias':
|
||||||
|
// code += generateAlias(item)
|
||||||
|
// break
|
||||||
|
// case 'Class':
|
||||||
|
// console.log('Class', token.text)
|
||||||
|
// code += generateClass(item)
|
||||||
|
// break
|
||||||
|
// default:
|
||||||
|
// code += 'Undocumented type\n'
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// } else if (standardTypes[token.text] != null) {
|
||||||
|
// code += `${standardTypes[token.text]}\n`
|
||||||
|
// } else {
|
||||||
|
// code += 'Unknown\n'
|
||||||
|
// }
|
||||||
|
// code += '\n'
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return code
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates documentation for the Client class
|
||||||
|
* @param spec {Extractor.ApiItem}
|
||||||
|
* @returns {string} Asciidoc markup
|
||||||
|
*/
|
||||||
|
// function generateClientDocs (spec) {
|
||||||
|
// let code = `[reference-client-class]\n\n== Client\n\n${header}\n\n`
|
||||||
|
//
|
||||||
|
// // generate constructor and client options
|
||||||
|
// code += '[discrete]\n'
|
||||||
|
// code += '=== Constructor\n\n'
|
||||||
|
// code += '[source,ts,subs=+macros]\n'
|
||||||
|
// code += '----\n'
|
||||||
|
// code += 'new Client(options: <<ClientOptions>>): Client\n'
|
||||||
|
// code += '----\n\n'
|
||||||
|
//
|
||||||
|
// // generate methods
|
||||||
|
// code += '[discrete]\n'
|
||||||
|
// code += '=== Methods\n\n'
|
||||||
|
// for (const method of spec.members.filter(m => m.kind === 'Method')) {
|
||||||
|
// code += `[[Client.${method.displayName}]]\n`
|
||||||
|
// code += '[discrete]\n'
|
||||||
|
// code += `==== Client.${method.displayName}\n\n`
|
||||||
|
// code += 'TODO\n\n'
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // generate properties
|
||||||
|
// code += '[discrete]\n'
|
||||||
|
// code += '=== Properties\n\n'
|
||||||
|
// for (const prop of spec.members.filter(m => m.kind === 'Property')) {
|
||||||
|
// code += `[[Client.${prop.displayName}]]\n`
|
||||||
|
// code += '[discrete]\n'
|
||||||
|
// code += `==== Client.${prop.displayName}\n\n`
|
||||||
|
// code += 'TODO\n\n'
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return code
|
||||||
|
// }
|
||||||
|
|
||||||
|
async function write (name, code) {
|
||||||
|
const filePath = path.join(import.meta.dirname, '..', 'docs', 'reference2', `${name}.asciidoc`)
|
||||||
|
// console.log(`writing ${filePath}`)
|
||||||
|
await fs.writeFile(filePath, code, 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start () {
|
||||||
|
const model = new Extractor.ApiModel()
|
||||||
|
const pkg = model.loadPackage(path.join(import.meta.dirname, '..', 'api-extractor', 'elasticsearch.api.json'))
|
||||||
|
const entry = pkg.entryPoints[0]
|
||||||
|
|
||||||
|
for (const member of entry.members) {
|
||||||
|
if (member.displayName.endsWith('_2')) continue
|
||||||
|
switch (member.kind) {
|
||||||
|
case 'Class':
|
||||||
|
await write(member.displayName, generateClass(member))
|
||||||
|
break
|
||||||
|
case 'Interface':
|
||||||
|
await write(member.displayName, generateInterface(member))
|
||||||
|
break
|
||||||
|
case 'TypeAlias':
|
||||||
|
await write(member.displayName, generateAlias(member))
|
||||||
|
break
|
||||||
|
case 'Function':
|
||||||
|
if (member.fileUrlPath.startsWith('lib/api/api')) {
|
||||||
|
// if (member.displayName === 'CountApi') console.log(member)
|
||||||
|
// TODO: drop this: That stuff
|
||||||
|
// TODO: sub name with `client.foo.bar`
|
||||||
|
await write(`${member.displayName}_${member.overloadIndex ?? ''}`, generateApiFunction(member))
|
||||||
|
// TODO: generate rollup page for each override
|
||||||
|
}
|
||||||
|
// console.log(member)
|
||||||
|
// process.exit(0)
|
||||||
|
break
|
||||||
|
case 'Namespace':
|
||||||
|
case 'Variable':
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.log('unsupported type', member.kind, member.displayName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// TODO: generate rollup page that includes a whole API namespace's functions, requests, responses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
||||||
|
.then(() => process.exit(0))
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user