From bd5185345983d5991fd6013926dbe42ff382253b Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 25 Feb 2022 20:22:44 +0100 Subject: [PATCH 1/6] docs: generate docs for fake() and unique() --- docs/api/.gitignore | 1 - docs/api/fake.md | 11 -- scripts/apidoc.ts | 259 +++++++++++++++++++++++++++----------------- src/fake.ts | 4 +- src/unique.ts | 1 + 5 files changed, 164 insertions(+), 112 deletions(-) delete mode 100644 docs/api/fake.md diff --git a/docs/api/.gitignore b/docs/api/.gitignore index d77f28d2ebc..0b938bbecd2 100644 --- a/docs/api/.gitignore +++ b/docs/api/.gitignore @@ -1,4 +1,3 @@ *.md -!fake.md !localization.md *.ts diff --git a/docs/api/fake.md b/docs/api/fake.md deleted file mode 100644 index 8fae509adab..00000000000 --- a/docs/api/fake.md +++ /dev/null @@ -1,11 +0,0 @@ -# Fake - -Useful generator method `faker.fake()` for combining faker API methods using a mustache string format. - -```js -faker.fake('{{name.lastName}}, {{name.firstName}} {{name.suffix}}'); -// Wintheiser, Shaylee Sr. - -faker.fake('{{company.bs}} is short for {{address.streetName}}'); -// cutting-edge leverage web services is short for Flatley Rue -``` diff --git a/scripts/apidoc.ts b/scripts/apidoc.ts index fd9ebcd0d25..5d78f842be5 100644 --- a/scripts/apidoc.ts +++ b/scripts/apidoc.ts @@ -83,6 +83,113 @@ function mdToHtml(md: string): string { } } +function analyzeSignature( + signature: TypeDoc.SignatureReflection, + moduleName: string, + methodName: string +): Method { + const parameters: MethodParameter[] = []; + + // Collect Type Parameters + const typeParameters = signature.typeParameters || []; + const signatureTypeParameters: string[] = []; + for (const parameter of typeParameters) { + signatureTypeParameters.push(parameter.name); + parameters.push({ + name: parameter.name, + description: mdToHtml(toBlock(parameter.comment)), + }); + } + + // 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; + } + + signatureParameters.push( + parameterName + ': ' + parameterType + parameterDefaultSignatureText + ); + parameters.push({ + name: parameter.name, + type: parameterType, + default: parameterDefault, + description: mdToHtml(toBlock(parameter.comment)), + }); + } + + // Generate usage section + + let signatureTypeParametersString = ''; + if (signatureTypeParameters.length !== 0) { + signatureTypeParametersString = `<${signatureTypeParameters.join(', ')}>`; + } + const signatureParametersString = signatureParameters.join(', '); + + let examples: string; + if (moduleName) { + examples = `faker.${moduleName}.${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type.toString()}\n`; + } else { + examples = `faker.${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type.toString()}\n`; + } + faker.seed(0); + if (!requiresArgs && moduleName) { + try { + let example = JSON.stringify(faker[moduleName][methodName]()); + if (example.length > 50) { + example = example.substring(0, 47) + '...'; + } + + examples += `faker.${moduleName}.${methodName}()`; + examples += (example ? ` // => ${example}` : '') + '\n'; + } catch (error) { + // Ignore the error => hide the example call + result. + } + } + const exampleTags = + signature?.comment?.tags + .filter((tag) => tag.tagName === 'example') + .map((tag) => tag.text.trimEnd()) || []; + + if (exampleTags.length > 0) { + examples += exampleTags.join('\n').trim() + '\n'; + } + + const seeAlsos = + signature.comment?.tags + .filter((t) => t.tagName === 'see') + .map((t) => t.text.trim()) ?? []; + + const prettyMethodName = prettifyMethodName(methodName); + return { + name: methodName, + title: prettyMethodName, + description: mdToHtml(toBlock(signature.comment)), + parameters: parameters, + returns: signature.type.toString(), + examples: mdToHtml('```ts\n' + examples + '```'), + deprecated: signature.comment?.hasTag('deprecated') ?? false, + seeAlsos, + }; +} + async function build(): Promise { const app = new TypeDoc.Application(); @@ -110,7 +217,6 @@ async function build(): Promise { .getChildrenByKind(TypeDoc.ReflectionKind.Class); const modulesPages: Array<{ text: string; link: string }> = []; - modulesPages.push({ text: 'Fake', link: '/api/fake.html' }); modulesPages.push({ text: 'Localization', link: '/api/localization.html' }); // Generate module file @@ -135,106 +241,10 @@ async function build(): Promise { TypeDoc.ReflectionKind.Method )) { const methodName = method.name; - const prettyMethodName = prettifyMethodName(methodName); - console.debug(`- method ${prettyMethodName}`); + console.debug(`- method ${methodName}`); const signature = method.signatures[0]; - const parameters: MethodParameter[] = []; - - // Collect Type Parameters - const typeParameters = signature.typeParameters || []; - const signatureTypeParameters: string[] = []; - for (const parameter of typeParameters) { - signatureTypeParameters.push(parameter.name); - parameters.push({ - name: parameter.name, - description: mdToHtml(toBlock(parameter.comment)), - }); - } - - // 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; - } - - signatureParameters.push( - parameterName + ': ' + parameterType + parameterDefaultSignatureText - ); - parameters.push({ - name: parameter.name, - type: parameterType, - default: parameterDefault, - description: mdToHtml(toBlock(parameter.comment)), - }); - } - - // Generate usage section - - let signatureTypeParametersString = ''; - if (signatureTypeParameters.length !== 0) { - signatureTypeParametersString = `<${signatureTypeParameters.join( - ', ' - )}>`; - } - const signatureParametersString = signatureParameters.join(', '); - - let examples = `faker.${lowerModuleName}.${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type.toString()}\n`; - faker.seed(0); - if (!requiresArgs) { - try { - let example = JSON.stringify(faker[lowerModuleName][methodName]()); - if (example.length > 50) { - example = example.substring(0, 47) + '...'; - } - - examples += `faker.${lowerModuleName}.${methodName}()`; - examples += (example ? ` // => ${example}` : '') + '\n'; - } catch (error) { - // Ignore the error => hide the example call + result. - } - } - const exampleTags = - signature?.comment?.tags - .filter((tag) => tag.tagName === 'example') - .map((tag) => tag.text.trimEnd()) || []; - - if (exampleTags.length > 0) { - examples += exampleTags.join('\n').trim() + '\n'; - } - - const seeAlsos = - signature.comment?.tags - .filter((t) => t.tagName === 'see') - .map((t) => t.text.trim()) ?? []; - - methods.push({ - name: methodName, - title: prettyMethodName, - description: mdToHtml(toBlock(signature.comment)), - parameters: parameters, - returns: signature.type.toString(), - examples: mdToHtml('```ts\n' + examples + '```'), - deprecated: signature.comment?.hasTag('deprecated') ?? false, - seeAlsos, - }); + methods.push(analyzeSignature(signature, lowerModuleName, methodName)); } // Write api docs page @@ -281,6 +291,57 @@ async function build(): Promise { writeFileSync(resolve(pathOutputDir, lowerModuleName + '.ts'), contentTs); } + const directs = project + .getChildrenByKind(TypeDoc.ReflectionKind.Class) + .filter((ref) => ref.name === 'Faker')[0] + .getChildrenByKind(TypeDoc.ReflectionKind.Property) + .filter((ref) => ['fake', 'unique'].includes(ref.name)); + + for (const direct of directs) { + const methodName = direct.name; + const upperMethodName = + methodName.substring(0, 1).toUpperCase() + methodName.substring(1); + const signature = (direct.type as TypeDoc.ReflectionType).declaration + .signatures[0]; + modulesPages.push({ + text: upperMethodName, + link: `/api/${methodName}.html`, + }); + const method = analyzeSignature(signature, undefined, methodName); + + // Write api docs page + let content = ` + + + + `.replace(/\n +/g, '\n'); + + content = format(content, prettierMarkdown); + + writeFileSync(resolve(pathOutputDir, methodName + '.md'), content); + + // Write api docs data + + let contentTs = ` + import type { Method } from '../.vitepress/components/api-docs/method'; + + export const ${methodName}: Method[] = ${JSON.stringify( + [method], + null, + 2 + )}`; + + contentTs = format(contentTs, prettierTypescript); + + writeFileSync(resolve(pathOutputDir, methodName + '.ts'), contentTs); + } + // Write api-pages.mjs console.log('Updating api-pages.mjs'); modulesPages.sort((a, b) => a.text.localeCompare(b.text)); diff --git a/src/fake.ts b/src/fake.ts index a20aae11d45..d56b4794741 100644 --- a/src/fake.ts +++ b/src/fake.ts @@ -30,7 +30,9 @@ export class Fake { * * Please note that is NOT possible to use any non-faker methods or plain js script in there. * - * @param str The format string that will get interpolated. + * @param str The format string that will get interpolated. May not be empty. + * + * @see faker.helpers.mustache() to use custom functions for resolution. * * @example * faker.fake('{{name.lastName}}') // 'Barrows' diff --git a/src/unique.ts b/src/unique.ts index 62402df077a..d63c3766cc9 100644 --- a/src/unique.ts +++ b/src/unique.ts @@ -28,6 +28,7 @@ export class Unique { * Generates a unique result using the results of the given method. * Used unique entries will be stored internally and filtered from subsequent calls. * + * @template Method The type of the method to execute. * @param method The method used to generate the values. * @param args The arguments used to call the method. * @param opts The optional options used to configure this method. From 1f26c4f4b16f0bc66a4597466809513c8e5c23c2 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 25 Feb 2022 22:32:26 +0100 Subject: [PATCH 2/6] docs: split script into multiple files --- scripts/apidoc.ts | 340 ++------------------------------ scripts/apidoc/apiDocsWriter.ts | 135 +++++++++++++ scripts/apidoc/directMethods.ts | 56 ++++++ scripts/apidoc/moduleMethods.ts | 67 +++++++ scripts/apidoc/signature.ts | 164 +++++++++++++++ scripts/apidoc/utils.ts | 8 + 6 files changed, 441 insertions(+), 329 deletions(-) create mode 100644 scripts/apidoc/apiDocsWriter.ts create mode 100644 scripts/apidoc/directMethods.ts create mode 100644 scripts/apidoc/moduleMethods.ts create mode 100644 scripts/apidoc/signature.ts create mode 100644 scripts/apidoc/utils.ts diff --git a/scripts/apidoc.ts b/scripts/apidoc.ts index 5d78f842be5..cb4ed2a4e12 100644 --- a/scripts/apidoc.ts +++ b/scripts/apidoc.ts @@ -1,195 +1,13 @@ -import { writeFileSync } from 'fs'; import { resolve } from 'path'; -import type { Options } from 'prettier'; -import { format } from 'prettier'; -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'; +import { writeApiPagesIndex } from './apidoc/apiDocsWriter'; +import { processDirectMethods } from './apidoc/directMethods'; +import { processModuleMethods } from './apidoc/moduleMethods'; +import type { PageIndex } from './apidoc/utils'; +import { pathOutputDir } from './apidoc/utils'; -const pathRoot = resolve(__dirname, '..'); -const pathDocsDir = resolve(pathRoot, 'docs'); -const pathDocsApiPages = resolve(pathDocsDir, '.vitepress', 'api-pages.mjs'); -const pathOutputDir = resolve(pathDocsDir, 'api'); 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: [], -}; - -export function prettifyMethodName(method: string): string { - return ( - // Capitalize and insert space before upper case characters - method.substring(0, 1).toUpperCase() + - method.substring(1).replace(/([A-Z]+)/g, ' $1') - ); -} - -function toBlock(comment?: TypeDoc.Comment): string { - return ( - (comment?.shortText.trim() || 'Missing') + - (comment?.text ? '\n\n' + comment.text : '') - ); -} - -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'); - } -} - -function analyzeSignature( - signature: TypeDoc.SignatureReflection, - moduleName: string, - methodName: string -): Method { - const parameters: MethodParameter[] = []; - - // Collect Type Parameters - const typeParameters = signature.typeParameters || []; - const signatureTypeParameters: string[] = []; - for (const parameter of typeParameters) { - signatureTypeParameters.push(parameter.name); - parameters.push({ - name: parameter.name, - description: mdToHtml(toBlock(parameter.comment)), - }); - } - - // 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; - } - - signatureParameters.push( - parameterName + ': ' + parameterType + parameterDefaultSignatureText - ); - parameters.push({ - name: parameter.name, - type: parameterType, - default: parameterDefault, - description: mdToHtml(toBlock(parameter.comment)), - }); - } - - // Generate usage section - - let signatureTypeParametersString = ''; - if (signatureTypeParameters.length !== 0) { - signatureTypeParametersString = `<${signatureTypeParameters.join(', ')}>`; - } - const signatureParametersString = signatureParameters.join(', '); - - let examples: string; - if (moduleName) { - examples = `faker.${moduleName}.${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type.toString()}\n`; - } else { - examples = `faker.${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type.toString()}\n`; - } - faker.seed(0); - if (!requiresArgs && moduleName) { - try { - let example = JSON.stringify(faker[moduleName][methodName]()); - if (example.length > 50) { - example = example.substring(0, 47) + '...'; - } - - examples += `faker.${moduleName}.${methodName}()`; - examples += (example ? ` // => ${example}` : '') + '\n'; - } catch (error) { - // Ignore the error => hide the example call + result. - } - } - const exampleTags = - signature?.comment?.tags - .filter((tag) => tag.tagName === 'example') - .map((tag) => tag.text.trimEnd()) || []; - - if (exampleTags.length > 0) { - examples += exampleTags.join('\n').trim() + '\n'; - } - - const seeAlsos = - signature.comment?.tags - .filter((t) => t.tagName === 'see') - .map((t) => t.text.trim()) ?? []; - - const prettyMethodName = prettifyMethodName(methodName); - return { - name: methodName, - title: prettyMethodName, - description: mdToHtml(toBlock(signature.comment)), - parameters: parameters, - returns: signature.type.toString(), - examples: mdToHtml('```ts\n' + examples + '```'), - deprecated: signature.comment?.hasTag('deprecated') ?? false, - seeAlsos, - }; -} - async function build(): Promise { const app = new TypeDoc.Application(); @@ -209,151 +27,15 @@ async function build(): Promise { // Project may not have converted correctly return; } - // Useful for analyzing the content + // Useful for manually analyzing the content await app.generateJson(project, pathOutputJson); + console.log(pathOutputDir); - const modules = project - .getChildrenByKind(TypeDoc.ReflectionKind.Namespace)[0] - .getChildrenByKind(TypeDoc.ReflectionKind.Class); - - const modulesPages: Array<{ text: string; link: string }> = []; + const modulesPages: PageIndex = []; modulesPages.push({ text: 'Localization', link: '/api/localization.html' }); - - // Generate module file - for (const module of modules) { - const moduleName = module.name.replace('_', ''); - const lowerModuleName = - moduleName.substring(0, 1).toLowerCase() + moduleName.substring(1); - if (faker[lowerModuleName] === undefined) { - continue; - } - console.log(`Processing Module ${moduleName}`); - - modulesPages.push({ - text: moduleName, - link: `/api/${lowerModuleName}.html`, - }); - - const methods: Method[] = []; - - // Generate method section - for (const method of module.getChildrenByKind( - TypeDoc.ReflectionKind.Method - )) { - const methodName = method.name; - console.debug(`- method ${methodName}`); - const signature = method.signatures[0]; - - methods.push(analyzeSignature(signature, lowerModuleName, methodName)); - } - - // Write api docs page - let content = ` - - - # ${moduleName} - - - - - ::: v-pre - - ${toBlock(module.comment)} - - ::: - - - `.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); - } - - const directs = project - .getChildrenByKind(TypeDoc.ReflectionKind.Class) - .filter((ref) => ref.name === 'Faker')[0] - .getChildrenByKind(TypeDoc.ReflectionKind.Property) - .filter((ref) => ['fake', 'unique'].includes(ref.name)); - - for (const direct of directs) { - const methodName = direct.name; - const upperMethodName = - methodName.substring(0, 1).toUpperCase() + methodName.substring(1); - const signature = (direct.type as TypeDoc.ReflectionType).declaration - .signatures[0]; - modulesPages.push({ - text: upperMethodName, - link: `/api/${methodName}.html`, - }); - const method = analyzeSignature(signature, undefined, methodName); - - // Write api docs page - let content = ` - - - - `.replace(/\n +/g, '\n'); - - content = format(content, prettierMarkdown); - - writeFileSync(resolve(pathOutputDir, methodName + '.md'), content); - - // Write api docs data - - let contentTs = ` - import type { Method } from '../.vitepress/components/api-docs/method'; - - export const ${methodName}: Method[] = ${JSON.stringify( - [method], - null, - 2 - )}`; - - contentTs = format(contentTs, prettierTypescript); - - writeFileSync(resolve(pathOutputDir, methodName + '.ts'), contentTs); - } - - // Write api-pages.mjs - console.log('Updating api-pages.mjs'); - modulesPages.sort((a, b) => a.text.localeCompare(b.text)); - let apiPagesContent = ` - // This file is automatically generated. - // Run '${scriptCommand}' to update - export const apiPages = ${JSON.stringify(modulesPages)}; - `.replace(/\n +/, '\n'); - - apiPagesContent = format(apiPagesContent, prettierBabel); - - writeFileSync(pathDocsApiPages, apiPagesContent); + modulesPages.push(...processModuleMethods(project)); + modulesPages.push(...processDirectMethods(project)); + writeApiPagesIndex(modulesPages); } build().catch(console.error); diff --git a/scripts/apidoc/apiDocsWriter.ts b/scripts/apidoc/apiDocsWriter.ts new file mode 100644 index 00000000000..cb1c93af04e --- /dev/null +++ b/scripts/apidoc/apiDocsWriter.ts @@ -0,0 +1,135 @@ +import { writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import type { Options } from 'prettier'; +import { format } from 'prettier'; +import prettierConfig from '../../.prettierrc.cjs'; +import type { Method } from '../../docs/.vitepress/components/api-docs/method'; +import type { PageIndex } from './utils'; +import { pathDocsDir, pathOutputDir } from './utils'; + +const pathDocsApiPages = resolve(pathDocsDir, '.vitepress', 'api-pages.mjs'); + +const scriptCommand = 'pnpm run generate:api-docs'; + +const prettierMarkdown: Options = { + ...prettierConfig, + parser: 'markdown', +}; + +const prettierTypescript: Options = { + ...prettierConfig, + parser: 'typescript', +}; + +const prettierBabel: Options = { + ...prettierConfig, + parser: 'babel', +}; + +/** + * Writes the api page for the given module to the correct location. + * + * @param moduleName The name of the module to write the docs for. + * @param lowerModuleName The lowercase name of the module. + * @param comment The module comments. + */ +export function writeApiDocsMethodPage( + moduleName: string, + lowerModuleName: string, + comment: string +): void { + // Write api docs page + let content = ` + + + # ${moduleName} + + + + + ::: v-pre + + ${comment} + + ::: + + + `.replace(/\n +/g, '\n'); + + content = format(content, prettierMarkdown); + + writeFileSync(resolve(pathOutputDir, lowerModuleName + '.md'), content); +} + +/** + * Writes the api page for the given method to the correct location. + * + * @param methodName The name of the method to write the docs for. + */ +export function writeApiDocsDirectPage(methodName: string): void { + let content = ` + + + + `.replace(/\n +/g, '\n'); + + content = format(content, prettierMarkdown); + + writeFileSync(resolve(pathOutputDir, methodName + '.md'), content); +} + +/** + * Writes the api docs data to correct location. + * + * @param lowerModuleName The lowercase name of the module. + * @param methods The methods data to save. + */ +export function writeApiDocsData( + lowerModuleName: string, + methods: Method[] +): void { + 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); +} + +/** + * Writes the api docs index to correct location. + * + * @param pages The pages to write into the index. + */ +export function writeApiPagesIndex(pages: PageIndex): void { + // Write api-pages.mjs + console.log('Updating api-pages.mjs'); + pages.sort((a, b) => a.text.localeCompare(b.text)); + let apiPagesContent = ` + // This file is automatically generated. + // Run '${scriptCommand}' to update + export const apiPages = ${JSON.stringify(pages)}; + `.replace(/\n +/, '\n'); + + apiPagesContent = format(apiPagesContent, prettierBabel); + + writeFileSync(pathDocsApiPages, apiPagesContent); +} diff --git a/scripts/apidoc/directMethods.ts b/scripts/apidoc/directMethods.ts new file mode 100644 index 00000000000..616226ee522 --- /dev/null +++ b/scripts/apidoc/directMethods.ts @@ -0,0 +1,56 @@ +import * as TypeDoc from 'typedoc'; +import { writeApiDocsData, writeApiDocsDirectPage } from './apiDocsWriter'; +import { analyzeSignature } from './signature'; +import type { Page, PageIndex } from './utils'; + +/** + * Analyzes and writes the documentation for direct methods such as `faker.fake()`. + * + * @param project The project used to extract the direct methods. + * @returns The generated pages. + */ +export function processDirectMethods( + project: TypeDoc.ProjectReflection +): PageIndex { + const pages: PageIndex = []; + + const directs = project + .getChildrenByKind(TypeDoc.ReflectionKind.Class) + .filter((ref) => ref.name === 'Faker')[0] + .getChildrenByKind(TypeDoc.ReflectionKind.Property) + .filter((ref) => ['fake', 'unique'].includes(ref.name)); + + for (const direct of directs) { + pages.push(processDirectMethod(direct)); + } + + return pages; +} + +/** + * Analyzes and writes the documentation for a direct method such as `faker.fake()`. + * + * @param direct The direct method to process. + * @returns The generated pages. + */ +export function processDirectMethod( + direct: TypeDoc.DeclarationReflection +): Page { + const methodName = direct.name; + const upperMethodName = + methodName.substring(0, 1).toUpperCase() + methodName.substring(1); + console.log(`Processing Direct: ${upperMethodName}`); + + const signature = (direct.type as TypeDoc.ReflectionType).declaration + .signatures[0]; + + writeApiDocsDirectPage(methodName); + writeApiDocsData(methodName, [ + analyzeSignature(signature, undefined, methodName), + ]); + + return { + text: upperMethodName, + link: `/api/${methodName}.html`, + }; +} diff --git a/scripts/apidoc/moduleMethods.ts b/scripts/apidoc/moduleMethods.ts new file mode 100644 index 00000000000..cd9af8b7dab --- /dev/null +++ b/scripts/apidoc/moduleMethods.ts @@ -0,0 +1,67 @@ +import * as TypeDoc from 'typedoc'; +import type { Method } from '../../docs/.vitepress/components/api-docs/method'; +import faker from '../../src'; +import { writeApiDocsData, writeApiDocsMethodPage } from './apiDocsWriter'; +import { analyzeSignature, toBlock } from './signature'; +import type { PageIndex } from './utils'; + +/** + * Analyzes and writes the documentation for modules and their methods such as `faker.animal.cat()`. + * + * @param project The project used to extract the modules. + * @returns The generated pages. + */ +export function processModuleMethods( + project: TypeDoc.ProjectReflection +): PageIndex { + const modules = project + .getChildrenByKind(TypeDoc.ReflectionKind.Namespace)[0] + .getChildrenByKind(TypeDoc.ReflectionKind.Class); + + const pages: PageIndex = []; + // Generate module file + for (const module of modules) { + pages.push(...processModuleMethod(module)); + } + + return pages; +} + +/** + * Analyzes and writes the documentation for a module and its methods such as `faker.animal.cat()`. + * + * @param direct The module to process. + * @returns The generated pages. + */ +function processModuleMethod(module: TypeDoc.DeclarationReflection): PageIndex { + const moduleName = module.name.replace('_', ''); + const lowerModuleName = + moduleName.substring(0, 1).toLowerCase() + moduleName.substring(1); + if (faker[lowerModuleName] === undefined) { + return []; + } + console.log(`Processing Module ${moduleName}`); + + const methods: Method[] = []; + + // Generate method section + for (const method of module.getChildrenByKind( + TypeDoc.ReflectionKind.Method + )) { + const methodName = method.name; + console.debug(`- ${methodName}`); + const signature = method.signatures[0]; + + methods.push(analyzeSignature(signature, lowerModuleName, methodName)); + } + + writeApiDocsMethodPage(moduleName, lowerModuleName, toBlock(module.comment)); + writeApiDocsData(lowerModuleName, methods); + + return [ + { + text: moduleName, + link: `/api/${lowerModuleName}.html`, + }, + ]; +} diff --git a/scripts/apidoc/signature.ts b/scripts/apidoc/signature.ts new file mode 100644 index 00000000000..858a65a1654 --- /dev/null +++ b/scripts/apidoc/signature.ts @@ -0,0 +1,164 @@ +import sanitizeHtml from 'sanitize-html'; +import type * as TypeDoc from 'typedoc'; +import { createMarkdownRenderer } from 'vitepress'; +import type { + Method, + MethodParameter, +} from '../../docs/.vitepress/components/api-docs/method'; +import faker from '../../src'; +import { pathOutputDir } from './utils'; +// TODO ST-DDT 2022-02-20: Actually import this/fix module import errors +// import vitepressConfig from '../../docs/.vitepress/config'; + +export function prettifyMethodName(method: string): string { + return ( + // Capitalize and insert space before upper case characters + method.substring(0, 1).toUpperCase() + + method.substring(1).replace(/([A-Z]+)/g, ' $1') + ); +} + +export function toBlock(comment?: TypeDoc.Comment): string { + return ( + (comment?.shortText.trim() || 'Missing') + + (comment?.text ? '\n\n' + comment.text : '') + ); +} + +const markdown = createMarkdownRenderer( + pathOutputDir + // TODO ST-DDT 2022-02-20: Actually import this/fix module import errors + // vitepressConfig.markdown +); + +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 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'); + } +} + +export function analyzeSignature( + signature: TypeDoc.SignatureReflection, + moduleName: string, + methodName: string +): Method { + const parameters: MethodParameter[] = []; + + // Collect Type Parameters + const typeParameters = signature.typeParameters || []; + const signatureTypeParameters: string[] = []; + for (const parameter of typeParameters) { + signatureTypeParameters.push(parameter.name); + parameters.push({ + name: parameter.name, + description: mdToHtml(toBlock(parameter.comment)), + }); + } + + // 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; + } + + signatureParameters.push( + parameterName + ': ' + parameterType + parameterDefaultSignatureText + ); + parameters.push({ + name: parameter.name, + type: parameterType, + default: parameterDefault, + description: mdToHtml(toBlock(parameter.comment)), + }); + } + + // Generate usage section + + let signatureTypeParametersString = ''; + if (signatureTypeParameters.length !== 0) { + signatureTypeParametersString = `<${signatureTypeParameters.join(', ')}>`; + } + const signatureParametersString = signatureParameters.join(', '); + + let examples: string; + if (moduleName) { + examples = `faker.${moduleName}.${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type.toString()}\n`; + } else { + examples = `faker.${methodName}${signatureTypeParametersString}(${signatureParametersString}): ${signature.type.toString()}\n`; + } + faker.seed(0); + if (!requiresArgs && moduleName) { + try { + let example = JSON.stringify(faker[moduleName][methodName]()); + if (example.length > 50) { + example = example.substring(0, 47) + '...'; + } + + examples += `faker.${moduleName}.${methodName}()`; + examples += (example ? ` // => ${example}` : '') + '\n'; + } catch (error) { + // Ignore the error => hide the example call + result. + } + } + const exampleTags = + signature?.comment?.tags + .filter((tag) => tag.tagName === 'example') + .map((tag) => tag.text.trimEnd()) || []; + + if (exampleTags.length > 0) { + examples += exampleTags.join('\n').trim() + '\n'; + } + + const seeAlsos = + signature.comment?.tags + .filter((t) => t.tagName === 'see') + .map((t) => t.text.trim()) ?? []; + + const prettyMethodName = prettifyMethodName(methodName); + return { + name: methodName, + title: prettyMethodName, + description: mdToHtml(toBlock(signature.comment)), + parameters: parameters, + returns: signature.type.toString(), + examples: mdToHtml('```ts\n' + examples + '```'), + deprecated: signature.comment?.hasTag('deprecated') ?? false, + seeAlsos, + }; +} diff --git a/scripts/apidoc/utils.ts b/scripts/apidoc/utils.ts new file mode 100644 index 00000000000..ab01f3ac351 --- /dev/null +++ b/scripts/apidoc/utils.ts @@ -0,0 +1,8 @@ +import { resolve } from 'path'; + +export type Page = { text: string; link: string }; +export type PageIndex = Array; + +const pathRoot = resolve(__dirname, '..', '..'); +export const pathDocsDir = resolve(pathRoot, 'docs'); +export const pathOutputDir = resolve(pathDocsDir, 'api'); From 8f0039650198c0a87e5216fbe75bc0cb3b3ac28c Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 25 Feb 2022 22:50:11 +0100 Subject: [PATCH 3/6] chore: rename method --- scripts/apidoc/apiDocsWriter.ts | 2 +- scripts/apidoc/moduleMethods.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/apidoc/apiDocsWriter.ts b/scripts/apidoc/apiDocsWriter.ts index cb1c93af04e..ff01ce0ced3 100644 --- a/scripts/apidoc/apiDocsWriter.ts +++ b/scripts/apidoc/apiDocsWriter.ts @@ -33,7 +33,7 @@ const prettierBabel: Options = { * @param lowerModuleName The lowercase name of the module. * @param comment The module comments. */ -export function writeApiDocsMethodPage( +export function writeApiDocsModulePage( moduleName: string, lowerModuleName: string, comment: string diff --git a/scripts/apidoc/moduleMethods.ts b/scripts/apidoc/moduleMethods.ts index cd9af8b7dab..89c42a33a56 100644 --- a/scripts/apidoc/moduleMethods.ts +++ b/scripts/apidoc/moduleMethods.ts @@ -1,7 +1,7 @@ import * as TypeDoc from 'typedoc'; import type { Method } from '../../docs/.vitepress/components/api-docs/method'; import faker from '../../src'; -import { writeApiDocsData, writeApiDocsMethodPage } from './apiDocsWriter'; +import { writeApiDocsData, writeApiDocsModulePage } from './apiDocsWriter'; import { analyzeSignature, toBlock } from './signature'; import type { PageIndex } from './utils'; @@ -55,7 +55,7 @@ function processModuleMethod(module: TypeDoc.DeclarationReflection): PageIndex { methods.push(analyzeSignature(signature, lowerModuleName, methodName)); } - writeApiDocsMethodPage(moduleName, lowerModuleName, toBlock(module.comment)); + writeApiDocsModulePage(moduleName, lowerModuleName, toBlock(module.comment)); writeApiDocsData(lowerModuleName, methods); return [ From a9779f370a86288e18a28fc4092003ab0b29875d Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 25 Feb 2022 23:22:24 +0100 Subject: [PATCH 4/6] chore: fix for vitepress v0.22.3 update --- scripts/apidoc/signature.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/apidoc/signature.ts b/scripts/apidoc/signature.ts index 858a65a1654..4b06da97fdc 100644 --- a/scripts/apidoc/signature.ts +++ b/scripts/apidoc/signature.ts @@ -26,9 +26,11 @@ export function toBlock(comment?: TypeDoc.Comment): string { } const markdown = createMarkdownRenderer( - pathOutputDir + pathOutputDir, + undefined, // TODO ST-DDT 2022-02-20: Actually import this/fix module import errors - // vitepressConfig.markdown + // vitepressConfig.markdown, + '' ); const htmlSanitizeOptions: sanitizeHtml.IOptions = { From 8d3fcc5fd13fb73977afa4cddbf885b35df7fbe9 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 25 Feb 2022 23:24:09 +0100 Subject: [PATCH 5/6] chore: apply Co-authored-by: Shinigami --- scripts/apidoc/apiDocsWriter.ts | 4 ++-- scripts/apidoc/utils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/apidoc/apiDocsWriter.ts b/scripts/apidoc/apiDocsWriter.ts index ff01ce0ced3..e2b4281620a 100644 --- a/scripts/apidoc/apiDocsWriter.ts +++ b/scripts/apidoc/apiDocsWriter.ts @@ -59,7 +59,7 @@ export function writeApiDocsModulePage( ::: - + `.replace(/\n +/g, '\n'); content = format(content, prettierMarkdown); @@ -82,7 +82,7 @@ export function writeApiDocsDirectPage(methodName: string): void { const methods = ref(${methodName}); - + `.replace(/\n +/g, '\n'); content = format(content, prettierMarkdown); diff --git a/scripts/apidoc/utils.ts b/scripts/apidoc/utils.ts index ab01f3ac351..ee38d58d120 100644 --- a/scripts/apidoc/utils.ts +++ b/scripts/apidoc/utils.ts @@ -1,4 +1,4 @@ -import { resolve } from 'path'; +import { resolve } from 'node:path'; export type Page = { text: string; link: string }; export type PageIndex = Array; From e110e79669710d581b42c85fdb9d06694efc299d Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sat, 26 Feb 2022 09:56:44 +0100 Subject: [PATCH 6/6] Use correct default value See https://github.com/vuejs/vitepress/issues/555#issuecomment-1051557728 --- scripts/apidoc/signature.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/apidoc/signature.ts b/scripts/apidoc/signature.ts index 4b06da97fdc..cd4e4e769f2 100644 --- a/scripts/apidoc/signature.ts +++ b/scripts/apidoc/signature.ts @@ -30,7 +30,7 @@ const markdown = createMarkdownRenderer( undefined, // TODO ST-DDT 2022-02-20: Actually import this/fix module import errors // vitepressConfig.markdown, - '' + '/' ); const htmlSanitizeOptions: sanitizeHtml.IOptions = {