diff options
| author | ST-DDT <[email protected]> | 2022-02-20 19:27:59 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-02-20 19:27:59 +0100 |
| commit | 4e066e8e1a43f47450d264a9c3af8a8620f70055 (patch) | |
| tree | 60a7f0e08a61e20c6330c4afa1a5f117bdd641c1 /scripts | |
| parent | d7f4751a1a07e32d2d7af1b2480cac8efe9650a6 (diff) | |
| download | faker-4e066e8e1a43f47450d264a9c3af8a8620f70055.tar.xz faker-4e066e8e1a43f47450d264a9c3af8a8620f70055.zip | |
docs: use vue components for api-docs (#446)
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/apidoc.ts | 293 |
1 files changed, 153 insertions, 140 deletions
diff --git a/scripts/apidoc.ts b/scripts/apidoc.ts index 81e0b510..93563248 100644 --- a/scripts/apidoc.ts +++ b/scripts/apidoc.ts @@ -1,9 +1,18 @@ -import * as TypeDoc from 'typedoc'; import { writeFileSync } from 'fs'; import { resolve } from 'path'; +import type { Options } from 'prettier'; import { format } from 'prettier'; -import options from '../.prettierrc.cjs'; +import sanitizeHtml from 'sanitize-html'; +import * as TypeDoc from 'typedoc'; +import { createMarkdownRenderer } from 'vitepress'; +import prettierConfig from '../.prettierrc.cjs'; +import type { + Method, + MethodParameter, +} from '../docs/.vitepress/components/api-docs/method'; import faker from '../src'; +// TODO ST-DDT 2022-02-20: Actually import this/fix module import errors +// import vitepressConfig from '../docs/.vitepress/config'; const pathRoot = resolve(__dirname, '..'); const pathDocsDir = resolve(pathRoot, 'docs'); @@ -13,6 +22,38 @@ const pathOutputJson = resolve(pathOutputDir, 'typedoc.json'); const scriptCommand = 'pnpm run generate:api-docs'; +const markdown = createMarkdownRenderer( + pathOutputDir + // TODO ST-DDT 2022-02-20: Actually import this/fix module import errors + // vitepressConfig.markdown +); + +const prettierMarkdown: Options = { + ...prettierConfig, + parser: 'markdown', +}; + +const prettierTypescript: Options = { + ...prettierConfig, + parser: 'typescript', +}; + +const prettierBabel: Options = { + ...prettierConfig, + parser: 'babel', +}; + +const htmlSanitizeOptions: sanitizeHtml.IOptions = { + allowedTags: ['a', 'code', 'div', 'li', 'span', 'p', 'pre', 'ul'], + allowedAttributes: { + a: ['href', 'target', 'rel'], + div: ['class'], + pre: ['v-pre'], + span: ['class'], + }, + selfClosing: [], +}; + function toBlock(comment?: TypeDoc.Comment): string { return ( (comment?.shortText.trim() || 'Missing') + @@ -20,38 +61,18 @@ function toBlock(comment?: TypeDoc.Comment): string { ); } -// https://stackoverflow.com/a/6234804/6897682 -function escapeHtml(unsafe: string): string { - return unsafe - .replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} - -function parameterRow( - name: string, - type?: string, - def?: string, - comment?: TypeDoc.Comment -): string { - def = def ? `<code>${def}</code>` : ''; - return `<tr> - <td>${escapeHtml(name)}</td> - <td>${escapeHtml(type)}</td> - <td>${def}</td> - <td> - -::: v-pre - -${toBlock(comment)} - -::: - - </td> -</tr> -`; +function mdToHtml(md: string): string { + const rawHtml = markdown.render(md); + const safeHtml: string = sanitizeHtml(rawHtml, htmlSanitizeOptions); + // Revert some escaped characters for comparison. + if (rawHtml.replace(/>/g, '>') === safeHtml.replace(/>/g, '>')) { + return safeHtml; + } else { + console.debug('Rejected unsafe md:', md); + console.error('Rejected unsafe html:', rawHtml.replace(/>/g, '>')); + console.error('Expected safe html:', safeHtml.replace(/>/g, '>')); + throw new Error('Found unsafe html'); + } } async function build(): Promise<void> { @@ -99,24 +120,12 @@ async function build(): Promise<void> { link: `/api/${lowerModuleName}.html`, }); - let content = ` - # ${moduleName} - - <!-- This file is automatically generated. --> - <!-- Run '${scriptCommand}' to update --> - - ::: v-pre - - ${toBlock(module.comment)} - - ::: - - `.replace(/\n +/g, '\n'); - - const methods = module.getChildrenByKind(TypeDoc.ReflectionKind.Method); + const methods: Method[] = []; // Generate method section - for (const method of methods) { + for (const method of module.getChildrenByKind( + TypeDoc.ReflectionKind.Method + )) { const methodName = method.name; const prettyMethodName = methodName.substring(0, 1).toUpperCase() + @@ -124,95 +133,64 @@ async function build(): Promise<void> { console.debug(`- method ${prettyMethodName}`); const signature = method.signatures[0]; - content += ` - ## ${prettyMethodName} + const parameters: MethodParameter[] = []; - ::: v-pre - - ${toBlock(signature.comment)} - - ::: - - `.replace(/\n +/g, '\n'); - - // Generate parameter section + // Collect Type Parameters const typeParameters = signature.typeParameters || []; - const parameters = signature.parameters || []; const signatureTypeParameters: string[] = []; - const signatureParameters: string[] = []; - let requiresArgs = false; - if (typeParameters.length !== 0 || parameters.length !== 0) { - content += `**Parameters** - -<table> - <thead> - <tr> - <th>Name</th> - <th>Type</th> - <th>Default</th> - <th>Description</th> - </tr> - </thead> - <tbody> -`; - - // typeParameters - typeParameters.forEach((parameter) => { - const parameterName = parameter.name; - - signatureTypeParameters.push(parameterName); - content += parameterRow( - `<${parameterName}>`, - '', - '', - parameter.comment - ); + for (const parameter of typeParameters) { + signatureTypeParameters.push(parameter.name); + parameters.push({ + name: parameter.name, + description: mdToHtml(toBlock(parameter.comment)), }); + } - // parameters - parameters.forEach((parameter, index) => { - const parameterDefault = parameter.defaultValue; - const parameterRequired = typeof parameterDefault === 'undefined'; - if (index == 0) { - requiresArgs = parameterRequired; - } - const parameterName = parameter.name + (parameterRequired ? '?' : ''); - const parameterType = parameter.type.toString(); + // Collect Parameters + const signatureParameters: string[] = []; + let requiresArgs = false; + for ( + let index = 0; + signature.parameters && index < signature.parameters.length; + index++ + ) { + const parameter = signature.parameters[index]; + + const parameterDefault = parameter.defaultValue; + const parameterRequired = typeof parameterDefault === 'undefined'; + if (index === 0) { + requiresArgs = parameterRequired; + } + const parameterName = parameter.name + (parameterRequired ? '?' : ''); + const parameterType = parameter.type.toString(); - let parameterDefaultSignatureText = ''; - if (!parameterRequired) { - parameterDefaultSignatureText = ' = ' + parameterDefault; - } + let parameterDefaultSignatureText = ''; + if (!parameterRequired) { + parameterDefaultSignatureText = ' = ' + parameterDefault; + } - signatureParameters.push( - parameterName + ': ' + parameterType + parameterDefaultSignatureText - ); - content += parameterRow( - parameterName, - parameterType, - parameterDefault, - parameter.comment - ); + signatureParameters.push( + parameterName + ': ' + parameterType + parameterDefaultSignatureText + ); + parameters.push({ + name: parameter.name, + type: parameterType, + default: parameterDefault, + description: mdToHtml(toBlock(parameter.comment)), }); - - content += ` </tbody> -</table> - -`; } - content += '**Returns:** ' + signature.type.toString() + '\n\n'; // Generate usage section - content += '````ts\n'; - - let signatureTypeParametersString = signatureTypeParameters.join(', '); - if (signatureTypeParametersString.length !== 0) { - signatureTypeParametersString = `<${signatureTypeParametersString}>`; + let signatureTypeParametersString = ''; + if (signatureTypeParameters.length !== 0) { + signatureTypeParametersString = `<${signatureTypeParameters.join( + ', ' + )}>`; } const signatureParametersString = signatureParameters.join(', '); - content += `faker.${lowerModuleName}.${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type.toString()}\n`; + let examples = `faker.${lowerModuleName}.${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type.toString()}\n`; faker.seed(0); if (!requiresArgs) { try { @@ -221,34 +199,72 @@ async function build(): Promise<void> { example = example.substring(0, 47) + '...'; } - content += `faker.${lowerModuleName}.${methodName}()`; - content += (example ? ` // => ${example}` : '') + '\n'; + examples += `faker.${lowerModuleName}.${methodName}()`; + examples += (example ? ` // => ${example}` : '') + '\n'; } catch (error) { // Ignore the error => hide the example call + result. } } - const examples = + const exampleTags = signature?.comment?.tags .filter((tag) => tag.tagName === 'example') .map((tag) => tag.text.trimEnd()) || []; - if (examples.length !== 0) { - content += examples.join('\n').trim() + '\n'; + if (exampleTags.length > 0) { + examples += exampleTags.join('\n').trim() + '\n'; } - content += '````\n\n'; + methods.push({ + name: prettyMethodName, + description: mdToHtml(toBlock(signature.comment)), + parameters: parameters, + returns: signature.type.toString(), + examples: mdToHtml('```ts\n' + examples + '```'), + }); } - // Format md + // Write api docs page + let content = ` + <script setup> + import ApiDocsMethod from '../.vitepress/components/api-docs/method.vue' + import { ${lowerModuleName} } from './${lowerModuleName}' + import { ref } from 'vue'; + + const methods = ref(${lowerModuleName}); + </script> - content = format(content, { - ...options, - parser: 'markdown', - }); + # ${moduleName} + + <!-- This file is automatically generated. --> + <!-- Run '${scriptCommand}' to update --> + + ::: v-pre + + ${toBlock(module.comment)} + + ::: - // Write to disk + <ApiDocsMethod v-for="method of methods" v-bind:key="method.name" :method="method" v-once /> + `.replace(/\n +/g, '\n'); + + content = format(content, prettierMarkdown); writeFileSync(resolve(pathOutputDir, lowerModuleName + '.md'), content); + + // Write api docs data + + let contentTs = ` + import type { Method } from '../.vitepress/components/api-docs/method'; + + export const ${lowerModuleName}: Method[] = ${JSON.stringify( + methods, + null, + 2 + )}`; + + contentTs = format(contentTs, prettierTypescript); + + writeFileSync(resolve(pathOutputDir, lowerModuleName + '.ts'), contentTs); } // Write api-pages.mjs @@ -260,10 +276,7 @@ async function build(): Promise<void> { export const apiPages = ${JSON.stringify(modulesPages)}; `.replace(/\n +/, '\n'); - apiPagesContent = format(apiPagesContent, { - ...options, - parser: 'babel', - }); + apiPagesContent = format(apiPagesContent, prettierBabel); writeFileSync(pathDocsApiPages, apiPagesContent); } |
