From 23aa08800ac66a5b16e405d04d07ea538d16dd6b Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Mon, 23 Jan 2023 18:47:09 +0100 Subject: refactor: reorganize apidoc scripts and reuse them for tests (#1759) --- scripts/apidoc.ts | 3 +- scripts/apidoc/apiDocsWriter.ts | 40 +++----- scripts/apidoc/format.ts | 31 ++++++ scripts/apidoc/moduleMethods.ts | 47 ++------- scripts/apidoc/signature.ts | 6 +- scripts/apidoc/typedoc.ts | 210 ++++++++++++++++++++++++++++++++++++++++ scripts/apidoc/utils.ts | 165 +++---------------------------- 7 files changed, 278 insertions(+), 224 deletions(-) create mode 100644 scripts/apidoc/format.ts create mode 100644 scripts/apidoc/typedoc.ts (limited to 'scripts') diff --git a/scripts/apidoc.ts b/scripts/apidoc.ts index 921859e7..3e11572e 100644 --- a/scripts/apidoc.ts +++ b/scripts/apidoc.ts @@ -5,8 +5,9 @@ import { } from './apidoc/apiDocsWriter'; import { processModuleMethods } from './apidoc/moduleMethods'; import { initMarkdownRenderer } from './apidoc/signature'; +import { newTypeDocApp, patchProject } from './apidoc/typedoc'; import type { PageIndex } from './apidoc/utils'; -import { newTypeDocApp, patchProject, pathOutputDir } from './apidoc/utils'; +import { pathOutputDir } from './apidoc/utils'; const pathOutputJson = resolve(pathOutputDir, 'typedoc.json'); diff --git a/scripts/apidoc/apiDocsWriter.ts b/scripts/apidoc/apiDocsWriter.ts index cd8dd2e5..f18b7cd9 100644 --- a/scripts/apidoc/apiDocsWriter.ts +++ b/scripts/apidoc/apiDocsWriter.ts @@ -1,17 +1,16 @@ import { writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; import type { ProjectReflection } from 'typedoc'; -import { ReflectionKind } from 'typedoc'; import type { Method } from '../../docs/.vitepress/components/api-docs/method'; import type { APIGroup, APIItem } from '../../docs/api/api-types'; -import { extractModuleName, selectApiModules } from './moduleMethods'; -import type { PageIndex } from './utils'; +import { formatMarkdown, formatTypescript } from './format'; import { - formatMarkdown, - formatTypescript, - pathDocsDir, - pathOutputDir, -} from './utils'; + extractModuleName, + selectApiMethods, + selectApiModules, +} from './typedoc'; +import type { PageIndex } from './utils'; +import { pathDocsDir, pathOutputDir } from './utils'; const pathDocsApiPages = resolve(pathDocsDir, '.vitepress', 'api-pages.ts'); const pathDocsApiSearchIndex = resolve( @@ -139,28 +138,11 @@ export function writeApiSearchIndex(project: ProjectReflection): void { const apiSection: APIItem = { text: moduleName, link: moduleName.toLowerCase(), - headers: [], + headers: selectApiMethods(module).map((child) => ({ + anchor: child.name, + text: child.name, + })), }; - if (module.kind !== ReflectionKind.Property) { - apiSection.headers = module - .getChildrenByKind(ReflectionKind.Method) - .map((child) => ({ - anchor: child.name, - text: child.name, - })); - } else { - // TODO @Shinigami92 2022-08-17: Extract capitalization into own function - apiSection.text = - apiSection.text.substring(0, 1).toUpperCase() + - apiSection.text.substring(1); - - apiSection.headers = [ - { - anchor: module.name, - text: module.name, - }, - ]; - } return apiSection; }) diff --git a/scripts/apidoc/format.ts b/scripts/apidoc/format.ts new file mode 100644 index 00000000..0a502c5f --- /dev/null +++ b/scripts/apidoc/format.ts @@ -0,0 +1,31 @@ +import type { Options } from 'prettier'; +import { format } from 'prettier'; +import prettierConfig from '../../.prettierrc.cjs'; + +/** + * Formats markdown contents. + * + * @param text The text to format. + */ +export function formatMarkdown(text: string): string { + return format(text, prettierMarkdown); +} + +/** + * Formats typedoc contents. + * + * @param text The text to format. + */ +export function formatTypescript(text: string): string { + return format(text, prettierTypescript); +} + +const prettierMarkdown: Options = { + ...prettierConfig, + parser: 'markdown', +}; + +const prettierTypescript: Options = { + ...prettierConfig, + parser: 'typescript', +}; diff --git a/scripts/apidoc/moduleMethods.ts b/scripts/apidoc/moduleMethods.ts index dd4f528a..b52d4780 100644 --- a/scripts/apidoc/moduleMethods.ts +++ b/scripts/apidoc/moduleMethods.ts @@ -1,25 +1,15 @@ import type { DeclarationReflection, ProjectReflection } from 'typedoc'; -import { ReflectionKind } from 'typedoc'; import type { Method } from '../../docs/.vitepress/components/api-docs/method'; -import { faker } from '../../src'; import { writeApiDocsData, writeApiDocsModulePage } from './apiDocsWriter'; import { analyzeSignature, toBlock } from './signature'; +import { + extractModuleFieldName, + extractModuleName, + selectApiMethodSignatures, + selectApiModules, +} from './typedoc'; import type { PageIndex } from './utils'; -/** - * Selects the modules from the project that needs to be documented. - * - * @param project The project used to extract the modules. - * @returns The modules to document. - */ -export function selectApiModules( - project: ProjectReflection -): DeclarationReflection[] { - return project - .getChildrenByKind(ReflectionKind.Class) - .filter((module) => faker[extractModuleFieldName(module)] != null); -} - /** * Analyzes and writes the documentation for modules and their methods such as `faker.animal.cat()`. * @@ -37,24 +27,6 @@ export function processModuleMethods(project: ProjectReflection): PageIndex { return pages; } -export function extractModuleName(module: DeclarationReflection): string { - const { name } = module; - // TODO @ST-DDT 2022-10-16: Remove in v10. - // Typedoc prefers the name of the module that is exported first. - if (name === 'AddressModule') { - return 'Location'; - } else if (name === 'NameModule') { - return 'Person'; - } - - return name.replace(/Module$/, ''); -} - -function extractModuleFieldName(module: DeclarationReflection): string { - const moduleName = extractModuleName(module); - return moduleName.substring(0, 1).toLowerCase() + moduleName.substring(1); -} - /** * Analyzes and writes the documentation for a module and its methods such as `faker.animal.cat()`. * @@ -69,11 +41,10 @@ function processModuleMethod(module: DeclarationReflection): PageIndex { const methods: Method[] = []; // Generate method section - for (const method of module.getChildrenByKind(ReflectionKind.Method)) { - const methodName = method.name; + for (const [methodName, signature] of Object.entries( + selectApiMethodSignatures(module) + )) { console.debug(`- ${methodName}`); - const signatures = method.signatures; - const signature = signatures[signatures.length - 1]; methods.push(analyzeSignature(signature, moduleFieldName, methodName)); } diff --git a/scripts/apidoc/signature.ts b/scripts/apidoc/signature.ts index f2a4bd85..d34abf18 100644 --- a/scripts/apidoc/signature.ts +++ b/scripts/apidoc/signature.ts @@ -18,15 +18,15 @@ import type { } from '../../docs/.vitepress/components/api-docs/method'; import vitepressConfig from '../../docs/.vitepress/config'; import { faker } from '../../src'; +import { formatTypescript } from './format'; import { extractRawExamples, extractSeeAlsos, extractSince, - formatTypescript, isDeprecated, joinTagParts, - pathOutputDir, -} from './utils'; +} from './typedoc'; +import { pathOutputDir } from './utils'; export function prettifyMethodName(method: string): string { return ( diff --git a/scripts/apidoc/typedoc.ts b/scripts/apidoc/typedoc.ts new file mode 100644 index 00000000..d9b34a0b --- /dev/null +++ b/scripts/apidoc/typedoc.ts @@ -0,0 +1,210 @@ +import type { + CommentDisplayPart, + CommentTag, + DeclarationReflection, + ProjectReflection, + SignatureReflection, +} from 'typedoc'; +import { + Application, + Converter, + ReflectionKind, + TSConfigReader, +} from 'typedoc'; +import { faker } from '../../src'; +import { + DefaultParameterAwareSerializer, + parameterDefaultReader, + patchProjectParameterDefaults, +} from './parameterDefaults'; +import { mapByName } from './utils'; + +/** + * Creates and configures a new typedoc application. + */ +export function newTypeDocApp(): Application { + const app = new Application(); + + app.options.addReader(new TSConfigReader()); + // If you want TypeDoc to load typedoc.json files + //app.options.addReader(new TypeDoc.TypeDocReader()); + + // Read parameter defaults + app.converter.on(Converter.EVENT_CREATE_DECLARATION, parameterDefaultReader); + // Add to debug json output + app.serializer.addSerializer(new DefaultParameterAwareSerializer()); + + return app; +} + +/** + * Apply our patches to the generated typedoc data. + * + * This is moved to a separate method to allow printing/saving the original content before patching it. + * + * @param project The project to patch. + */ +export function patchProject(project: ProjectReflection): void { + patchProjectParameterDefaults(project); +} + +/** + * Selects the modules from the project that needs to be documented. + * + * @param project The project to extract the modules from. + * @returns The modules to document. + */ +export function selectApiModules( + project: ProjectReflection, + includeTestModules = false +): DeclarationReflection[] { + return project + .getChildrenByKind(ReflectionKind.Class) + .filter( + (module) => + faker[extractModuleFieldName(module)] != null || includeTestModules + ); +} + +/** + * Selects the methods from the module that needs to be documented. + * + * @param module The module to extract the methods from. + * @returns The methods to document. + */ +export function selectApiMethods( + module: DeclarationReflection +): DeclarationReflection[] { + return module.getChildrenByKind(ReflectionKind.Method); +} + +/** + * Selects the signature from the method that needs to be documented. + * + * @param method The method to extract the signature from. + * @returns The signature to document. + */ +export function selectApiSignature( + method: DeclarationReflection +): SignatureReflection { + const signatures = method.signatures; + if (signatures == null || signatures.length === 0) { + throw new Error(`Method ${method.name} has no signature.`); + } + + return signatures[signatures.length - 1]; +} + +/** + * Selects the method signatures from the module that needs to be documented. + * Method-Name -> Method-Signature + * + * @param method The module to extract the method signatures from. + * @returns The method signatures to document. + */ +export function selectApiMethodSignatures( + module: DeclarationReflection +): Record { + return mapByName(selectApiMethods(module), selectApiSignature); +} + +export function extractModuleName(module: DeclarationReflection): string { + const { name } = module; + // TODO @ST-DDT 2022-10-16: Remove in v10. + // Typedoc prefers the name of the module that is exported first. + if (name === 'AddressModule') { + return 'Location'; + } else if (name === 'NameModule') { + return 'Person'; + } + + return name.replace(/Module$/, ''); +} + +export function extractModuleFieldName(module: DeclarationReflection): string { + const moduleName = extractModuleName(module); + return moduleName.substring(0, 1).toLowerCase() + moduleName.substring(1); +} + +/** + * Extracts the text (md) from a jsdoc tag. + * + * @param tag The tag to extract the text from. + * @param signature The signature to extract the text from. + */ +export function extractTagContent( + tag: `@${string}`, + signature?: SignatureReflection, + tagProcessor: (tag: CommentTag) => string[] = joinTagContent +): string[] { + return signature?.comment?.getTags(tag).flatMap(tagProcessor) ?? []; +} + +/** + * Extracts the examples from the jsdocs without the surrounding md code block. + * + * @param signature The signature to extract the examples from. + */ +export function extractRawExamples(signature?: SignatureReflection): string[] { + return extractTagContent('@example', signature).map((tag) => + tag.replace(/^```ts\n/, '').replace(/\n```$/, '') + ); +} + +/** + * Extracts all the `@see` references from the jsdocs separately. + * + * @param signature The signature to extract the see also references from. + */ +export function extractSeeAlsos(signature?: SignatureReflection): string[] { + return extractTagContent('@see', signature, (tag) => + // If the @see tag contains code in backticks, the content is split into multiple parts. + // So we join together, split on newlines and filter out empty tags. + joinTagParts(tag.content) + .split('\n') + .map((link) => { + link = link.trim(); + if (link.startsWith('-')) { + link = link.slice(1).trim(); + } + + return link; + }) + .filter((link) => link) + ); +} + +/** + * Joins the parts of the given jsdocs tag. + */ +export function joinTagContent(tag: CommentTag): string[] { + return [joinTagParts(tag?.content)]; +} + +export function joinTagParts(parts: CommentDisplayPart[]): string; +export function joinTagParts(parts?: CommentDisplayPart[]): string | undefined; +export function joinTagParts(parts?: CommentDisplayPart[]): string | undefined { + return parts?.map((part) => part.text).join(''); +} + +/** + * Checks if the given signature is deprecated. + * + * @param signature The signature to check. + * + * @returns `true` if it is deprecated, otherwise `false`. + */ +export function isDeprecated(signature: SignatureReflection): boolean { + return extractTagContent('@deprecated', signature).length > 0; +} + +/** + * Extracts the "since" tag from the provided signature. + * + * @param signature The signature to check. + * + * @returns the contents of the @since tag + */ +export function extractSince(signature: SignatureReflection): string { + return extractTagContent('@since', signature).join().trim(); +} diff --git a/scripts/apidoc/utils.ts b/scripts/apidoc/utils.ts index 9dad1b14..7939adb1 100644 --- a/scripts/apidoc/utils.ts +++ b/scripts/apidoc/utils.ts @@ -1,165 +1,24 @@ import { resolve } from 'node:path'; -import type { Options } from 'prettier'; -import { format } from 'prettier'; -import type { - CommentDisplayPart, - CommentTag, - SignatureReflection, -} from 'typedoc'; -import * as TypeDoc from 'typedoc'; -import prettierConfig from '../../.prettierrc.cjs'; -import { - DefaultParameterAwareSerializer, - parameterDefaultReader, - patchProjectParameterDefaults, -} from './parameterDefaults'; + +// Types export type Page = { text: string; link: string }; export type PageIndex = Array; +// Paths + const pathRoot = resolve(__dirname, '..', '..'); export const pathDocsDir = resolve(pathRoot, 'docs'); export const pathOutputDir = resolve(pathDocsDir, 'api'); -/** - * Creates and configures a new typedoc application. - */ -export function newTypeDocApp(): TypeDoc.Application { - const app = new TypeDoc.Application(); +// Functions - app.options.addReader(new TypeDoc.TSConfigReader()); - // If you want TypeDoc to load typedoc.json files - //app.options.addReader(new TypeDoc.TypeDocReader()); - - // Read parameter defaults - app.converter.on( - TypeDoc.Converter.EVENT_CREATE_DECLARATION, - parameterDefaultReader +export function mapByName( + input: Array, + valueExtractor: (item: T) => V +): Record { + return input.reduce( + (acc, item) => ({ ...acc, [item.name]: valueExtractor(item) }), + {} ); - // Add to debug json output - app.serializer.addSerializer(new DefaultParameterAwareSerializer()); - - return app; -} - -/** - * Apply our patches to the generated typedoc data. - * - * This is moved to a separate method to allow printing/saving the original content before patching it. - * - * @param project The project to patch. - */ -export function patchProject(project: TypeDoc.ProjectReflection): void { - patchProjectParameterDefaults(project); -} - -/** - * Formats markdown contents. - * - * @param text The text to format. - */ -export function formatMarkdown(text: string): string { - return format(text, prettierMarkdown); -} - -/** - * Formats typedoc contents. - * - * @param text The text to format. - */ -export function formatTypescript(text: string): string { - return format(text, prettierTypescript); -} - -const prettierMarkdown: Options = { - ...prettierConfig, - parser: 'markdown', -}; - -const prettierTypescript: Options = { - ...prettierConfig, - parser: 'typescript', -}; - -/** - * Extracts the text (md) from a jsdoc tag. - * - * @param tag The tag to extract the text from. - * @param signature The signature to extract the text from. - */ -export function extractTagContent( - tag: `@${string}`, - signature?: SignatureReflection, - tagProcessor: (tag: CommentTag) => string[] = joinTagContent -): string[] { - return signature?.comment?.getTags(tag).flatMap(tagProcessor) ?? []; -} - -/** - * Extracts the examples from the jsdocs without the surrounding md code block. - * - * @param signature The signature to extract the examples from. - */ -export function extractRawExamples(signature?: SignatureReflection): string[] { - return extractTagContent('@example', signature).map((tag) => - tag.replace(/^```ts\n/, '').replace(/\n```$/, '') - ); -} - -/** - * Extracts all the `@see` references from the jsdocs separately. - * - * @param signature The signature to extract the see also references from. - */ -export function extractSeeAlsos(signature?: SignatureReflection): string[] { - return extractTagContent('@see', signature, (tag) => - // If the @see tag contains code in backticks, the content is split into multiple parts. - // So we join together, split on newlines and filter out empty tags. - joinTagParts(tag.content) - .split('\n') - .map((link) => { - link = link.trim(); - if (link.startsWith('-')) { - link = link.slice(1).trim(); - } - - return link; - }) - .filter((link) => link) - ); -} - -/** - * Joins the parts of the given jsdocs tag. - */ -export function joinTagContent(tag: CommentTag): string[] { - return [joinTagParts(tag?.content)]; -} - -export function joinTagParts(parts: CommentDisplayPart[]): string; -export function joinTagParts(parts?: CommentDisplayPart[]): string | undefined; -export function joinTagParts(parts?: CommentDisplayPart[]): string | undefined { - return parts?.map((part) => part.text).join(''); -} - -/** - * Checks if the given signature is deprecated. - * - * @param signature The signature to check. - * - * @returns `true` if it is deprecated, otherwise `false`. - */ -export function isDeprecated(signature: SignatureReflection): boolean { - return extractTagContent('@deprecated', signature).length > 0; -} - -/** - * Extracts the "since" tag from the provided signature. - * - * @param signature The signature to check. - * - * @returns the contents of the @since tag - */ -export function extractSince(signature: SignatureReflection): string { - return extractTagContent('@since', signature).join().trim(); } -- cgit v1.2.3