This repository has been archived on 2024-07-19. You can view files and clone it, but cannot push or open issues or pull requests.
works-on-my-machine/docs/generate.ts

112 lines
3.3 KiB
TypeScript
Raw Permalink Normal View History

import * as ts from 'typescript'
import { promises as fs } from 'fs'
import childProcess from 'child_process'
import path from 'path'
interface ExtractedNode {
name?: string
snippet?: string
comment?: string
type?: string
}
interface APIDocumentationNode {
sourcePath: string
targetPath: string
content: string
}
const API_DOC_PATH = './docs/API.md'
function formatFunctionDeclaration(node: ts.FunctionDeclaration, fullText: string): string {
const declarationStart = node.getStart()
const declarationEnd = node.body?.pos ?? declarationStart
return fullText.slice(declarationStart, declarationEnd)
}
/*
* Documentation extraction
*
* This extracts block comments and declared entities from source files
* and reformats it so publishable documentation can be generated from it.
*/
async function generateDocumentation() {
const rootPaths = process.argv.slice(2)
const output = childProcess.execSync(`find ${rootPaths.join(' ')} -name *.ts`, { encoding: 'utf8' })
const files: Array<APIDocumentationNode> = []
const sourcePaths = output.split('\n').filter((filePath) => Boolean(filePath))
for await (const sourcePath of sourcePaths) {
const rawContent = await fs.readFile(sourcePath, { encoding: 'utf8' })
const source = ts.createSourceFile(sourcePath, rawContent, ts.ScriptTarget.ES2015, true)
const fullText = source.getFullText()
const extractedNodes: Array<ExtractedNode> = []
ts.forEachChild(source, (node: ts.Node) => {
const extractedNode: ExtractedNode = {}
const commentRanges = ts.getLeadingCommentRanges(fullText, node.getFullStart())
const blockComments = (commentRanges ?? [])
.filter((comment) => comment.kind === ts.SyntaxKind.MultiLineCommentTrivia)
.map((r) => {
return fullText.slice(r.pos, r.end)
})
if (node.kind === ts.SyntaxKind.FunctionDeclaration) {
const functionDeclarationNode = node as ts.FunctionDeclaration
extractedNode.snippet = formatFunctionDeclaration(functionDeclarationNode, fullText)
extractedNode.name = String(functionDeclarationNode?.name?.escapedText ?? 'unknown function')
extractedNode.type = 'function'
} else if (node.kind === ts.SyntaxKind.ClassDeclaration) {
const classDeclarationNode = node as ts.ClassDeclaration
extractedNode.name = String(classDeclarationNode?.name?.escapedText ?? 'unknown class')
extractedNode.type = 'class'
}
if (blockComments.length === 0) return
extractedNode.comment = blockComments[0]
extractedNodes.push(extractedNode)
})
if (extractedNodes.length === 0) continue
const targetPath = `docs/api/${path.basename(sourcePath, '.ts')}.md`
let content = ''
content += `## ${sourcePath}\n\n`
for (const entry of extractedNodes) {
content += '---\n'
if (entry.name) content += `### ${entry?.type ?? ''} / ${entry.name}\n`
if (entry.snippet) content += `\`\`\`ts\n${entry.snippet}\n\`\`\`\n\n`
const entryDescription = (entry?.comment ?? '')
.split('\n')
.map((line) => line.replace(/(\/\*)|(\*\/)|(\*)/g, '').trim())
.join('\n')
content += `${entryDescription}\n\n`
}
files.push({
sourcePath,
targetPath,
content,
})
}
await fs.writeFile(API_DOC_PATH, '# API documentation\n---\n')
for await (const item of files) {
await fs.appendFile(API_DOC_PATH, item.content)
}
}
generateDocumentation()